perfectsched 0.7.19 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,87 +1,49 @@
1
+ #
2
+ # PerfectSched
3
+ #
4
+ # Copyright (C) 2012 FURUHASHI Sadayuki
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
1
18
 
2
19
  module PerfectSched
3
-
4
-
5
- class Task
6
- def initialize(id, time, cron, delay, data, timezone=nil)
7
- @id = id
8
- @time = time
9
- @cron = cron
10
- @delay = delay
11
- @data = data
12
- @timezone = timezone
13
- end
14
-
15
- attr_reader :id, :time, :cron, :delay, :data, :timezone
16
- end
17
-
18
-
19
- class Backend
20
- def initialize
21
- @croncalc = CronCalc.new
22
- end
23
-
24
- # => list {|id,cron,delay,data,next_time,timeout| ... }
25
- def list(&block)
26
- end
27
-
28
- # => token, task
29
- def acquire(timeout, now=Time.now.to_i)
30
- end
31
-
32
- # => true (success) or false (canceled)
33
- def finish(token, next_time)
34
- end
35
-
36
- # => true (success) or nil (already exists)
37
- def add(id, cron, delay, data, start_time, timezone=nil)
38
- timezone = TZInfo::Timezone.get(timezone).name if timezone # normalize
39
- first_time = @croncalc.next_time(cron, start_time.to_i, timezone)
40
- timeout = first_time + delay
41
- add_checked(id, cron, delay, data, first_time, timeout, timezone)
42
- end
43
-
44
- # => true (success) or nil (already exists)
45
- def add_checked(id, cron, delay, data, next_time, timeout, timezone)
46
- end
47
-
48
- # => true (success) or false (not found, canceled or finished)
49
- def delete(id)
50
- end
51
-
52
- # => true (success) or false (not found)
53
- def modify(id, cron, delay, data, timezone)
54
- cron = cron.strip
55
- @croncalc.next_time(cron, 0, timezone)
56
- modify_checked(id, cron, delay, data, timezone)
57
- end
58
-
59
- def modify_checked(id, cron, delay, data, timezone)
60
- end
61
-
62
- # => true (success) or false (not found)
63
- def modify_sched(id, cron, delay)
64
- cron_, delay_, data_, timezone, next_time = get(id)
65
- cron = cron.strip
66
- @croncalc.next_time(cron, 0, timezone)
67
- modify_sched_checked(id, cron, delay)
68
- end
69
-
70
- def modify_sched_checked(id, cron, delay)
20
+ module Backend
21
+ def self.new_backend(client, config)
22
+ case config[:type]
23
+ when nil
24
+ raise ConfigError, "'type' option is not set"
25
+ when 'rdb_compat'
26
+ require_backend('rdb_compat')
27
+ RDBCompatBackend.new(client, config)
28
+ end
29
+ end
30
+
31
+ def self.require_backend(fname)
32
+ require File.expand_path("backend/#{fname}", File.dirname(__FILE__))
33
+ end
34
+ end
35
+
36
+ module BackendHelper
37
+ def initialize(client, config)
38
+ @client = client
39
+ @config = config
40
+ end
41
+
42
+ attr_reader :client
43
+
44
+ def close
45
+ # do nothing by default
46
+ end
71
47
  end
72
-
73
- # => true (success) or false (not found)
74
- def modify_data(id, data)
75
- modify_data_checked(id, data)
76
- end
77
-
78
- def modify_data_checked(id, data)
79
- end
80
-
81
- def close
82
- end
83
- end
84
-
85
-
86
48
  end
87
49
 
@@ -0,0 +1,25 @@
1
+ #
2
+ # PerfectSched
3
+ #
4
+ # Copyright (C) 2012 FURUHASHI Sadayuki
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ module PerfectSched
20
+
21
+ require 'perfectqueue/blocking_flag'
22
+ BlockingFlag = PerfectQueue::BlockingFlag
23
+
24
+ end
25
+
@@ -0,0 +1,129 @@
1
+ #
2
+ # PerfectSched
3
+ #
4
+ # Copyright (C) 2012 FURUHASHI Sadayuki
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ module PerfectSched
20
+ class Client
21
+ def initialize(config)
22
+ @config = {}
23
+ config.each_pair {|k,v| @config[k.to_sym] = v }
24
+
25
+ @backend = Backend.new_backend(self, @config)
26
+
27
+ @timezone = @config[:timezone] || 'UTC'
28
+ @max_acquire = @config[:max_acquire] || 1
29
+ @alive_time = @config[:alive_time] || 300
30
+ @retry_wait = @config[:retry_wait] || 300 # TODO retry wait algorithm
31
+ end
32
+
33
+ attr_reader :backend
34
+ attr_reader :config
35
+
36
+ def init_database(options={})
37
+ @backend.init_database(options)
38
+ end
39
+
40
+ def get_schedule_metadata(key, options={})
41
+ @backend.get_schedule_metadata(key, options)
42
+ end
43
+
44
+ # :next_time => Time.now
45
+ # :next_run_time => Time.now
46
+ # :cron
47
+ # :data
48
+ # :delay => 0
49
+ # :timezone => UTC
50
+ def add(key, type, options={})
51
+ cron = options[:cron]
52
+
53
+ raise ArgumentError, ":cron option is required" unless cron
54
+
55
+ delay = options[:delay] || 0
56
+ timezone = options[:timezone] || @timezone
57
+ data = options[:data] || {}
58
+
59
+ next_time = options[:next_time] || Time.now.to_i
60
+ next_time = PerfectSched.cron_time(cron, next_time.to_i, timezone)
61
+
62
+ next_run_time = options[:next_run_time]
63
+ if next_run_time
64
+ next_run_time = next_run_time.to_i
65
+ else
66
+ next_run_time = next_time + delay
67
+ end
68
+
69
+ @backend.add(key, type, cron, delay, timezone, data, next_time, next_run_time, options)
70
+
71
+ # TODO return value
72
+ return next_time, next_run_time
73
+ end
74
+
75
+ def delete(key, options={})
76
+ @backend.delete(key, options)
77
+ end
78
+
79
+ # :next_time => nil
80
+ # :next_run_time => nil
81
+ # :cron => nil
82
+ # :delay => nil
83
+ # :timezone => nil
84
+ def modify(key, options={})
85
+ @backend.modify(key, options)
86
+ end
87
+
88
+ def list(options={}, &block)
89
+ @backend.list(options, &block)
90
+ end
91
+
92
+ # :now => Time.now
93
+ # :max_acquire
94
+ def acquire(options={})
95
+ alive_time = options[:alive_time] || @alive_time
96
+ max_acquire = options[:max_acquire] || 1
97
+
98
+ @backend.acquire(alive_time, max_acquire, options)
99
+ end
100
+
101
+ def release(task_token, options={})
102
+ alive_time = options[:alive_time] || @alive_time
103
+
104
+ @backend.release(task_token, alive_time, options)
105
+ end
106
+
107
+ # :alive_time => nil
108
+ def heartbeat(task_token, options={})
109
+ alive_time = options[:alive_time] || @alive_time
110
+
111
+ @backend.heartbeat(task_token, alive_time, options)
112
+ end
113
+
114
+ def retry(task_token, options={})
115
+ alive_time = options[:retry_wait] || @retry_wait
116
+
117
+ @backend.heartbeat(task_token, alive_time, options)
118
+ end
119
+
120
+ def finish(task_token, options={})
121
+ @backend.finish(task_token, options)
122
+ end
123
+
124
+ def close
125
+ @backend.close
126
+ end
127
+ end
128
+ end
129
+
@@ -3,355 +3,168 @@ require 'perfectsched/version'
3
3
 
4
4
  op = OptionParser.new
5
5
 
6
- op.banner += ""
7
- op.version = PerfectSched::VERSION
8
-
9
- type = nil
10
- id = nil
11
- confout = nil
12
-
13
- conf = {
14
- :timeout => 600,
15
- :poll_interval => 1,
16
- #:expire => 345600,
17
- }
6
+ op.banner += %[ <command>
18
7
 
19
- add_conf = {
20
- :delay => 0,
21
- }
8
+ commands:
9
+ list Show list of registered schedules
10
+ add <key> <type> <cron> <data> Register a new schedule
11
+ delete <key> Delete a registered schedule
12
+ run <class> Run a worker process
13
+ init Initialize a backend database
22
14
 
15
+ ]
16
+ op.version = PerfectSched::VERSION
23
17
 
24
- op.on('--setup PATH.yaml', 'Write example configuration file') {|s|
25
- type = :conf
26
- confout = s
27
- }
18
+ env = ENV['RAILS_ENV'] || 'development'
19
+ config_path = 'config/perfectsched.yml'
20
+ include_dirs = []
21
+ require_files = []
28
22
 
29
- op.on('-f', '--file PATH.yaml', 'Set path to the configuration file') {|s|
30
- (conf[:files] ||= []) << s
23
+ add_options = {
24
+ :delay => 0,
25
+ :timezone => 'UTC',
26
+ :next_time => nil,
27
+ :next_run_time => nil,
31
28
  }
32
29
 
33
- op.separator("")
30
+ op.separator("options:")
34
31
 
35
- op.on('--list', 'Show registered schedule', TrueClass) {|b|
36
- type = :list
32
+ op.on('-e', '--environment ENV', 'Framework environment (default: development)') {|s|
33
+ env = s
37
34
  }
38
35
 
39
- op.on('--delete ID', 'Delete a registered schedule') {|s|
40
- type = :delete
41
- id = s
36
+ op.on('-c', '--config PATH.yml', 'Path to a configuration file (default: config/perfectsched.yml)') {|s|
37
+ config_path = s
42
38
  }
43
39
 
44
- op.separator("")
45
-
46
- op.on('--add <ID> <CRON> <DATA>', 'Register a schedule') {|s|
47
- type = :add
48
- id = s
49
- }
40
+ op.separator("\noptions for add:")
50
41
 
51
42
  op.on('-d', '--delay SEC', 'Delay time before running a schedule (default: 0)', Integer) {|i|
52
- add_conf[:delay] = i
43
+ add_options[:delay] = i
53
44
  }
54
45
 
55
- op.on('-t', '--timezone NAME', 'Set timezone (default: localtime)') {|s|
56
- add_conf[:timezone] = s
46
+ op.on('-t', '--timezone NAME', 'Set timezone (default: UTC)') {|s|
47
+ add_options[:timezone] = s
57
48
  }
58
49
 
59
- op.on('-s', '--start UNIXTIME', 'Start time to run a schedule (default: now)', Integer) {|i|
60
- add_conf[:start] = i
50
+ op.on('-s', '--start UNIXTIME', 'Set the first schedule time (default: now)', Integer) {|i|
51
+ add_options[:next_time] = i
61
52
  }
62
53
 
63
- op.separator("")
64
-
65
- op.on('-S', '--modify-sched <ID> <CRON>', 'Modify schedule of a registered schedule') {|s|
66
- type = :modify_sched
67
- id = s
68
- }
69
-
70
- op.on('-D', '--modify-delay <ID> <DELAY>', 'Modify delay of a registered schedule') {|s|
71
- type = :modify_delay
72
- id = s
54
+ op.on('-a', '--at UNIXTIME', 'Set the first run time (default: start+delay)', Integer) {|i|
55
+ add_options[:next_run_time] = i
73
56
  }
74
57
 
75
- op.on('-J', '--modify-data <ID> <DATA>', 'Modify data of a registered schedule') {|s|
76
- type = :modify_data
77
- id = s
78
- }
79
-
80
- op.separator("")
58
+ op.separator("\noptions for run:")
81
59
 
82
- op.on('-b', '--daemon PIDFILE', 'Daemonize (default: foreground)') {|s|
83
- conf[:daemon] = s
60
+ op.on('-I', '--include PATH', 'Add $LOAD_PATH directory') {|s|
61
+ include_dirs << s
84
62
  }
85
63
 
86
- op.on('-o', '--log PATH', "log file path") {|s|
87
- conf[:log] = s
64
+ op.on('-r', '--require PATH', 'Require files before starting') {|s|
65
+ require_files << s
88
66
  }
89
67
 
90
- op.on('-v', '--verbose', "verbose mode", TrueClass) {|b|
91
- conf[:verbose] = true
92
- }
93
-
94
-
95
68
  (class<<self;self;end).module_eval do
96
69
  define_method(:usage) do |msg|
97
70
  puts op.to_s
98
- puts "error: #{msg}" if msg
71
+ puts "\nerror: #{msg}" if msg
99
72
  exit 1
100
73
  end
101
74
  end
102
75
 
103
-
104
76
  begin
105
77
  op.parse!(ARGV)
106
78
 
107
- type ||= :run
108
-
109
- case type
110
- when :add
111
- if ARGV.length != 2
112
- usage nil
113
- end
114
- add_conf[:cron] = ARGV[0]
115
- add_conf[:data] = ARGV[1]
116
-
117
- when :modify_sched
118
- if ARGV.length != 1
119
- usage nil
120
- end
121
- add_conf[:cron] = ARGV[0]
122
-
123
- when :modify_data
124
- if ARGV.length != 1
125
- usage nil
126
- end
127
- add_conf[:data] = ARGV[0]
128
-
129
- when :modify_delay
130
- if ARGV.length != 1 || ARGV[0].to_i.to_s != ARGV[0]
131
- usage nil
132
- end
133
- add_conf[:delay] = ARGV[0].to_i
134
-
135
- else
136
- if ARGV.length != 0
137
- usage nil
138
- end
139
- end
140
-
141
- if confout
142
- require 'yaml'
143
-
144
- File.open(confout, "w") {|f|
145
- f.write <<EOF
146
- ---
147
- timeout: 300
148
- poll_interval: 1
149
- backend:
150
- database: "mysql2://user:password@localhost/mydb"
151
- table: "perfectsched"
152
- #simpledb: your-simpledb-domain-name-for-scheduler
153
- #aws_key_id: "AWS_ACCESS_KEY_ID"
154
- #aws_secret_key: "AWS_SECRET_ACCESS_KEY"
155
- queue:
156
- database: "mysql2://user:password@localhost/mydb"
157
- table: "perfectqueue"
158
- #simpledb: your-simpledb-domain-name-for-queue
159
- #aws_key_id: "AWS_ACCESS_KEY_ID"
160
- #aws_secret_key: "AWS_SECRET_ACCESS_KEY"
161
- EOF
162
- }
163
- exit 0
164
- end
79
+ usage nil if ARGV.empty?
165
80
 
166
- unless conf[:files]
167
- raise "-f, --file PATH.yaml option is required"
168
- end
81
+ cmd = ARGV.shift
82
+ case cmd
83
+ when 'list'
84
+ cmd = :list
85
+ usage nil unless ARGV.length == 0
169
86
 
170
- rescue
171
- usage $!.to_s
172
- end
87
+ when 'delete'
88
+ cmd = :delete
89
+ usage nil unless ARGV.length == 1
90
+ key = ARGV[0]
173
91
 
92
+ when 'add'
93
+ cmd = :add
94
+ usage nil unless ARGV.length == 4
95
+ key, type, cron, data = *ARGV
96
+ require 'json'
97
+ data = JSON.load(data)
174
98
 
175
- require 'perfectsched'
176
- require 'perfectqueue'
99
+ when 'run'
100
+ cmd = :run
101
+ usage nil unless ARGV.length == 1
102
+ klass = ARGV[0]
177
103
 
178
- require 'yaml'
179
- docs = ''
180
- conf[:files].each {|file|
181
- docs << File.read(file)
182
- }
183
- YAML.load_documents(docs) {|yaml|
184
- yaml.each_pair {|k,v| conf[k.to_sym] = v }
185
- }
104
+ when 'init'
105
+ cmd = :init
106
+ usage nil unless ARGV.length == 0
186
107
 
187
- conf[:timeout] ||= 60
188
- conf[:poll_interval] ||= 1
189
-
190
- # backend
191
- bconf = conf[:backend]
192
- if domain = bconf['simpledb']
193
- require 'perfectsched/backend/simpledb'
194
- key_id = bconf['aws_key_id'] || ENV['AWS_ACCESS_KEY_ID']
195
- secret_key = bconf['aws_secret_key'] || ENV['AWS_SECRET_ACCESS_KEY']
196
- backend = PerfectSched::SimpleDBBackend.new(key_id, secret_key, domain)
197
- if type != :run
198
- backend.use_consistent_read
108
+ else
109
+ raise "unknown command: '#{cmd}'"
199
110
  end
200
111
 
201
- elsif uri = bconf['database']
202
- require 'perfectsched/backend/rdb'
203
- table = bconf['table'] || "perfectsched"
204
- backend = PerfectSched::RDBBackend.new(uri, table)
205
-
206
- else
207
- $stderr.puts "Invalid configuration file: backend section is required"
208
- exit 1
112
+ rescue
113
+ usage $!.to_s
209
114
  end
210
115
 
211
- # queue
212
- make_queue = Proc.new do
213
- bconf = conf[:queue]
214
- if domain = bconf['simpledb']
215
- require 'perfectqueue/backend/simpledb'
216
- key_id = bconf['aws_key_id'] || ENV['AWS_ACCESS_KEY_ID']
217
- secret_key = bconf['aws_secret_key'] || ENV['AWS_SECRET_ACCESS_KEY']
218
- queue = PerfectQueue::SimpleDBBackend.new(key_id, secret_key, domain)
219
-
220
- elsif uri = bconf['database']
221
- require 'perfectqueue/backend/rdb'
222
- table = bconf['table'] || "perfectqueue"
223
- queue = PerfectQueue::RDBBackend.new(uri, table)
116
+ require 'yaml'
117
+ require 'perfectsched'
224
118
 
225
- else
226
- $stderr.puts "Invalid configuration file: queue section is required"
227
- exit 1
119
+ config_load_proc = Proc.new {
120
+ yaml = YAML.load(File.read(config_path))
121
+ conf = yaml[env]
122
+ unless conf
123
+ raise "Configuration file #{config_path} doesn't include configuration for environment '#{env}'"
228
124
  end
229
- end
230
-
231
- require 'logger'
125
+ conf
126
+ }
232
127
 
233
- case type
128
+ case cmd
234
129
  when :list
235
- format = "%26s %18s %8s %20s %20s %20s %s"
236
- puts format % ["id", "schedule", "delay", "next time", "next run", "timezone", "data"]
237
- time_format = "%Y-%m-%d %H:%M:%S"
238
130
  n = 0
239
- backend.list {|id,cron,delay,data,next_time,timeout,timezone|
240
- puts format % [id, cron, delay, Time.at(next_time).utc.strftime(time_format), Time.at(timeout).utc.strftime(time_format), timezone, data]
241
- n += 1
131
+ PerfectSched.open(config_load_proc.call) {|scheds|
132
+ format = "%30s %15s %18s %7s %11s %28s %28s %s"
133
+ puts format % ['key', 'type', 'cron', 'delay', 'timezone', 'next_time', 'next_run_time', 'data']
134
+ scheds.list {|sched|
135
+ next_time = sched.next_time ? Time.at(sched.next_time) : sched.next_time
136
+ next_run_time = sched.next_run_time ? Time.at(sched.next_run_time) : sched.next_run_time
137
+ puts format % [sched.key, sched.type, sched.cron, sched.delay, sched.timezone, next_time, next_run_time, sched.data]
138
+ n += 1
139
+ }
242
140
  }
243
141
  puts "#{n} entries."
244
142
 
245
143
  when :delete
246
- deleted = backend.delete(id)
247
- if deleted
248
- puts "Schedule id=#{id} is deleted."
249
- else
250
- puts "Schedule id=#{id} does not exist."
251
- exit 1
252
- end
144
+ PerfectSched.open(config_load_proc.call) {|scheds|
145
+ scheds[key].delete!
146
+ }
253
147
 
254
148
  when :add
255
- cron = add_conf[:cron]
256
- data = add_conf[:data]
257
- delay = add_conf[:delay]
258
- start = add_conf[:start] || Time.now.to_i
259
- timezone = add_conf[:timezone]
260
-
261
- added = backend.add(id, cron, delay, data, start, timezone)
262
- if added
263
- puts "Schedule id=#{id} is added."
264
- else
265
- puts "Schedule id=#{id} already exists."
266
- exit 1
267
- end
268
-
269
- when :modify_sched, :modify_delay, :modify_data
270
- cron, delay, data, timezone = backend.get(id)
271
- unless cron
272
- puts "Schedule id=#{id} does not exist."
273
- exit 1
274
- end
275
-
276
- case type
277
- when :modify_sched
278
- cron = add_conf[:cron]
279
- modified = backend.modify_sched(id, cron, delay)
280
-
281
- when :modify_delay
282
- delay = add_conf[:delay]
283
- modified = backend.modify_sched(id, cron, delay)
284
-
285
- when :modify_data
286
- data = add_conf[:data]
287
- modified = backend.modify_data(id, data)
288
- end
289
-
290
- if modified
291
- puts "Schedule id=#{id} is modified."
292
- else
293
- puts "Schedule id=#{id} does not exist."
294
- exit 1
295
- end
149
+ PerfectSched.open(config_load_proc.call) {|scheds|
150
+ add_options[:cron] = cron
151
+ add_options[:data] = data
152
+ scheds.add(key, type, add_options)
153
+ }
296
154
 
297
155
  when :run
298
- if conf[:daemon]
299
- exit!(0) if fork
300
- Process.setsid
301
- exit!(0) if fork
302
- File.umask(0)
303
- STDIN.reopen("/dev/null")
304
- STDOUT.reopen("/dev/null", "w")
305
- STDERR.reopen("/dev/null", "w")
306
- File.open(conf[:daemon], "w") {|f|
307
- f.write Process.pid.to_s
308
- }
309
- end
310
-
311
- if log_file = conf[:log]
312
- log_out = File.open(conf[:log], "a")
313
- else
314
- log_out = STDOUT
315
- end
316
- log_out.sync = true
317
-
318
- log = Logger.new(log_out)
319
- if conf[:verbose]
320
- log.level = Logger::DEBUG
321
- else
322
- log.level = Logger::INFO
323
- end
324
-
325
- queue = make_queue.call
326
- engine = PerfectSched::Engine.new(backend, queue, log, conf)
327
-
328
- trap :INT do
329
- log.info "shutting down..."
330
- engine.stop
331
- end
332
-
333
- trap :TERM do
334
- log.info "shutting down..."
335
- engine.stop
336
- end
337
-
338
- trap :HUP do
339
- if log_file
340
- log_out.reopen(log_file, "a")
341
- end
342
- end
343
-
344
- log.info "PerfectSched-#{PerfectSched::VERSION}"
156
+ include_dirs.each {|path|
157
+ $LOAD_PATH << File.expand_path(path)
158
+ }
159
+ require_files.each {|file|
160
+ require file
161
+ }
162
+ klass = Object.const_get(klass)
163
+ PerfectSched::Worker.run(klass, &config_load_proc)
345
164
 
346
- begin
347
- engine.run
348
- engine.shutdown
349
- rescue
350
- log.error $!.to_s
351
- $!.backtrace.each {|x|
352
- log.error " #{x}"
353
- }
354
- exit 1
355
- end
165
+ when :init
166
+ PerfectSched.open(config_load_proc.call) {|scheds|
167
+ scheds.client.init_database
168
+ }
356
169
  end
357
170