perfectsched 0.7.19 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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