perfectsched 0.7.19 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/ChangeLog +0 -36
- data/Gemfile +3 -0
- data/README.md +219 -0
- data/Rakefile +19 -0
- data/lib/perfectsched/application.rb +25 -0
- data/lib/perfectsched/backend/rdb_compat.rb +254 -0
- data/lib/perfectsched/backend.rb +44 -82
- data/lib/perfectsched/blocking_flag.rb +25 -0
- data/lib/perfectsched/client.rb +129 -0
- data/lib/perfectsched/command/perfectsched.rb +103 -290
- data/lib/perfectsched/daemons_logger.rb +24 -0
- data/lib/perfectsched/engine.rb +54 -54
- data/lib/perfectsched/error.rb +43 -0
- data/lib/perfectsched/model.rb +37 -0
- data/lib/perfectsched/runner.rb +35 -0
- data/lib/perfectsched/schedule.rb +65 -0
- data/lib/perfectsched/schedule_collection.rb +62 -0
- data/lib/perfectsched/schedule_metadata.rb +76 -0
- data/lib/perfectsched/signal_queue.rb +25 -0
- data/lib/perfectsched/task.rb +46 -0
- data/lib/perfectsched/version.rb +1 -3
- data/lib/perfectsched/worker.rb +163 -0
- data/lib/perfectsched.rb +98 -4
- data/perfectsched.gemspec +27 -0
- data/spec/schedule_collection_spec.rb +172 -0
- data/spec/spec_helper.rb +46 -0
- data/spec/worker_spec.rb +51 -0
- metadata +98 -52
- checksums.yaml +0 -7
- data/README.rdoc +0 -137
- data/lib/perfectsched/backend/null.rb +0 -45
- data/lib/perfectsched/backend/rdb.rb +0 -165
- data/lib/perfectsched/backend/simpledb.rb +0 -174
- data/lib/perfectsched/croncalc.rb +0 -29
- data/test/backend_test.rb +0 -217
- data/test/test_helper.rb +0 -19
data/.gitignore
ADDED
data/ChangeLog
CHANGED
@@ -1,39 +1,3 @@
|
|
1
|
-
== 2016-08-02 version 0.7.19
|
2
|
-
|
3
|
-
* re-package
|
4
|
-
|
5
|
-
== 2016-08-02 version 0.7.17
|
6
|
-
|
7
|
-
* Upgraded perfectqueue v0.8.x
|
8
|
-
* Upgraded sequel v3.48.0
|
9
|
-
* Upgraded tzinfo v1.2.2
|
10
|
-
|
11
|
-
|
12
|
-
== 2014-10-08 version 0.7.16
|
13
|
-
|
14
|
-
* Upgraded perfectqueue v0.7.31
|
15
|
-
* Upgraded sequel v3.48.0
|
16
|
-
|
17
|
-
|
18
|
-
== 2014-06-10 version 0.7.15
|
19
|
-
|
20
|
-
* RDBBackend: fixed modify_checked to update timeout and next_time
|
21
|
-
|
22
|
-
|
23
|
-
== 2014-04-25 version 0.7.14
|
24
|
-
|
25
|
-
* RDBBackend: fixed undefined local variable: 'url' to '@uri'
|
26
|
-
|
27
|
-
|
28
|
-
== 2014-04-25 version 0.7.13
|
29
|
-
|
30
|
-
* RDBBackend: fixed NoMethodError for @url
|
31
|
-
|
32
|
-
|
33
|
-
== 2012-02-21 version 0.7.12
|
34
|
-
|
35
|
-
* RDBBackend: added MySQL's SSL support
|
36
|
-
|
37
1
|
|
38
2
|
== 2012-02-21 version 0.7.10
|
39
3
|
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,219 @@
|
|
1
|
+
# PerfectSched
|
2
|
+
|
3
|
+
PerfectSched is a highly available distributed cron built on top of RDBMS.
|
4
|
+
|
5
|
+
It provides at-least-once semantics; Even if a worker node fails during process a task, the task is retried by another worker.
|
6
|
+
|
7
|
+
PerfectSched also guarantees that only one worker server processes a task if the server is alive.
|
8
|
+
|
9
|
+
All you have to consider is implementing idempotent worker programs. It's recommended to use [PerfectQueue](https://github.com/treasure-data/perfectqueue) with PerfectSched.
|
10
|
+
|
11
|
+
|
12
|
+
## API overview
|
13
|
+
|
14
|
+
```
|
15
|
+
# open a schedule collection
|
16
|
+
PerfectSched.open(config, &block) #=> #<ScheduleCollection>
|
17
|
+
|
18
|
+
# add a schedule
|
19
|
+
ScheduleCollection#add(task_id, type, options)
|
20
|
+
|
21
|
+
# poll a scheduled task
|
22
|
+
# (you don't have to use this method directly. see following sections)
|
23
|
+
ScheduleCollection#poll #=> #<Task>
|
24
|
+
|
25
|
+
# get data associated with a task
|
26
|
+
Task#data #=> #<Hash>
|
27
|
+
|
28
|
+
# finish a task
|
29
|
+
Task#finish!
|
30
|
+
|
31
|
+
# retry a task
|
32
|
+
Task#retry!
|
33
|
+
|
34
|
+
# create a schedule reference
|
35
|
+
ScheduleCollection#[](key) #=> #<Schedule>
|
36
|
+
|
37
|
+
# chack the existance of the schedule
|
38
|
+
Schedule#exists?
|
39
|
+
|
40
|
+
# delete a schedule
|
41
|
+
Schedule#delete!
|
42
|
+
```
|
43
|
+
|
44
|
+
### Error classes
|
45
|
+
|
46
|
+
```
|
47
|
+
ScheduleError < StandardError
|
48
|
+
|
49
|
+
##
|
50
|
+
# Workers may get these errors:
|
51
|
+
#
|
52
|
+
|
53
|
+
AlreadyFinishedError < ScheduleError
|
54
|
+
|
55
|
+
NotFoundError < ScheduleError
|
56
|
+
|
57
|
+
PreemptedError < ScheduleError
|
58
|
+
|
59
|
+
ProcessStopError < RuntimeError
|
60
|
+
|
61
|
+
##
|
62
|
+
# Client or other situation:
|
63
|
+
#
|
64
|
+
|
65
|
+
ConfigError < RuntimeError
|
66
|
+
|
67
|
+
AlreadyExistsError < ScheduleError
|
68
|
+
|
69
|
+
NotSupportedError < ScheduleError
|
70
|
+
```
|
71
|
+
|
72
|
+
|
73
|
+
### Example
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
# submit a task
|
77
|
+
PerfectSched.open(config) {|sc|
|
78
|
+
data = {'key'=>"value"}
|
79
|
+
options = {
|
80
|
+
:cron => '0 * * * *',
|
81
|
+
:delay => 30,
|
82
|
+
:timezone => 'Asia/Tokyo',
|
83
|
+
:next_time => Time.parse('2013-01-01 00:00:00 +0900').to_i,
|
84
|
+
:data => data,
|
85
|
+
}
|
86
|
+
sc.submit("sched-id", "type1", options)
|
87
|
+
}
|
88
|
+
```
|
89
|
+
|
90
|
+
|
91
|
+
## Writing a worker application
|
92
|
+
|
93
|
+
### 1. Implement PerfectSched::Application::Base
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
class TestHandler < PerfectSched::Application::Base
|
97
|
+
# implement run method
|
98
|
+
def run
|
99
|
+
# do something ...
|
100
|
+
puts "acquired task: #{task.inspect}"
|
101
|
+
|
102
|
+
# call task.finish!, task.retry! or task.release!
|
103
|
+
task.finish!
|
104
|
+
end
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
108
|
+
### 2. Implement PerfectSched::Application::Dispatch
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
class Dispatch < PerfectSched::Application::Dispatch
|
112
|
+
# describe routing
|
113
|
+
route "type1" => TestHandler
|
114
|
+
route /^regexp-.*$/ => :TestHandler # String or Regexp => Class or Symbol
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
118
|
+
### 3. Run the worker
|
119
|
+
|
120
|
+
In a launcher script or rake file:
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
system('perfectsched run -I. -rapp/schedules/dispatch Dispatch')
|
124
|
+
```
|
125
|
+
|
126
|
+
or:
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
require 'perfectsched'
|
130
|
+
require 'app/schedules/dispatch'
|
131
|
+
|
132
|
+
PerfectSched::Worker.run(Dispatch) {
|
133
|
+
# this method is called when the worker process is restarted
|
134
|
+
raw = File.read('config/perfectsched.yml')
|
135
|
+
yml = YAJL.load(raw)
|
136
|
+
yml[ENV['RAILS_ENV'] || 'development']
|
137
|
+
}
|
138
|
+
```
|
139
|
+
|
140
|
+
### Signal handlers
|
141
|
+
|
142
|
+
- **TERM**,**INT**,**QUIT:** shutdown
|
143
|
+
- **USR1**,**HUP:** restart
|
144
|
+
- **USR2:** reopen log files
|
145
|
+
|
146
|
+
## Configuration
|
147
|
+
|
148
|
+
- **type:** backend type (required; see following sections)
|
149
|
+
- **log:** log file path (default: use stderr)
|
150
|
+
- **poll\_interval:** interval to poll tasks in seconds (default: 1.0 sec)
|
151
|
+
- **timezone:** default timezone (default: 'UTC')
|
152
|
+
- **alive\_time:** duration to continue a heartbeat request (default: 300 sec)
|
153
|
+
- **retry\_wait:** duration to retry a retried task (default: 300 sec)
|
154
|
+
|
155
|
+
## Backend types
|
156
|
+
|
157
|
+
### rdb\_compat
|
158
|
+
|
159
|
+
additional configuration:
|
160
|
+
|
161
|
+
- **url:** URL to the RDBMS (example: 'mysql://user:password@host:port/database')
|
162
|
+
- **table:** name of the table to use
|
163
|
+
|
164
|
+
### rdb
|
165
|
+
|
166
|
+
Not implemented yet.
|
167
|
+
|
168
|
+
|
169
|
+
## Command line management tool
|
170
|
+
|
171
|
+
```
|
172
|
+
Usage: perfectsched [options] <command>
|
173
|
+
|
174
|
+
commands:
|
175
|
+
list Show list of registered schedules
|
176
|
+
add <key> <type> <cron> <data> Register a new schedule
|
177
|
+
delete <key> Delete a registered schedule
|
178
|
+
run <class> Run a worker process
|
179
|
+
init Initialize a backend database
|
180
|
+
|
181
|
+
options:
|
182
|
+
-e, --environment ENV Framework environment (default: development)
|
183
|
+
-c, --config PATH.yml Path to a configuration file (default: config/perfectsched.yml)
|
184
|
+
|
185
|
+
options for add:
|
186
|
+
-d, --delay SEC Delay time before running a schedule (default: 0)
|
187
|
+
-t, --timezone NAME Set timezone (default: UTC)
|
188
|
+
-s, --start UNIXTIME Set the first schedule time (default: now)
|
189
|
+
-a, --at UNIXTIME Set the first run time (default: start+delay)
|
190
|
+
|
191
|
+
options for run:
|
192
|
+
-I, --include PATH Add $LOAD_PATH directory
|
193
|
+
-r, --require PATH Require files before starting
|
194
|
+
```
|
195
|
+
|
196
|
+
### initializing a database
|
197
|
+
|
198
|
+
# assume that the config/perfectsched.yml exists
|
199
|
+
$ perfectsched init
|
200
|
+
|
201
|
+
### submitting a task
|
202
|
+
|
203
|
+
$ perfectsched add s1 user_task '* * * * *' '{}'
|
204
|
+
|
205
|
+
### listing tasks
|
206
|
+
|
207
|
+
$ perfectsched list
|
208
|
+
key type cron delay timezone next_time next_run_time data
|
209
|
+
s1 user_task * * * * * 0 UTC 2012-05-18 22:04:00 UTC 2012-05-18 22:04:00 UTC {}
|
210
|
+
1 entries.
|
211
|
+
|
212
|
+
### delete a schedule
|
213
|
+
|
214
|
+
$ perfectsched delete s1
|
215
|
+
|
216
|
+
### running a worker
|
217
|
+
|
218
|
+
$ perfectsched run -I. -Ilib -rconfig/boot.rb -rapps/schedules/schedule_dispatch.rb ScheduleDispatch
|
219
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rspec/core'
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
|
7
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
8
|
+
t.rspec_opts = ["-c", "-f progress", "-r ./spec/spec_helper.rb"]
|
9
|
+
t.pattern = 'spec/**/*_spec.rb'
|
10
|
+
t.verbose = true
|
11
|
+
end
|
12
|
+
|
13
|
+
task :coverage do |t|
|
14
|
+
ENV['SIMPLE_COV'] = '1'
|
15
|
+
Rake::Task["spec"].invoke
|
16
|
+
end
|
17
|
+
|
18
|
+
task :default => :build
|
19
|
+
|
@@ -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/application'
|
22
|
+
Application = PerfectQueue::Application
|
23
|
+
|
24
|
+
end
|
25
|
+
|
@@ -0,0 +1,254 @@
|
|
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
|
+
module Backend
|
21
|
+
class RDBCompatBackend
|
22
|
+
include BackendHelper
|
23
|
+
|
24
|
+
class Token < Struct.new(:row_id, :scheduled_time, :cron, :delay, :timezone)
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(client, config)
|
28
|
+
super
|
29
|
+
|
30
|
+
require 'sequel'
|
31
|
+
url = config[:url]
|
32
|
+
unless url
|
33
|
+
raise ConfigError, "url option is required for the rdb_compat backend"
|
34
|
+
end
|
35
|
+
|
36
|
+
@table = config[:table]
|
37
|
+
unless @table
|
38
|
+
raise ConfigError, "table option is required for the rdb_compat backend"
|
39
|
+
end
|
40
|
+
|
41
|
+
#password = config[:password]
|
42
|
+
#user = config[:user]
|
43
|
+
@db = Sequel.connect(url, :max_connections=>1)
|
44
|
+
@mutex = Mutex.new
|
45
|
+
|
46
|
+
connect {
|
47
|
+
# connection test
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
MAX_SELECT_ROW = 4
|
52
|
+
|
53
|
+
attr_reader :db
|
54
|
+
|
55
|
+
def init_database(options)
|
56
|
+
sql = %[
|
57
|
+
CREATE TABLE IF NOT EXISTS `test_scheds` (
|
58
|
+
id VARCHAR(256) NOT NULL,
|
59
|
+
timeout INT NOT NULL,
|
60
|
+
next_time INT NOT NULL,
|
61
|
+
cron VARCHAR(128) NOT NULL,
|
62
|
+
delay INT NOT NULL,
|
63
|
+
data BLOB NOT NULL,
|
64
|
+
timezone VARCHAR(256) NULL,
|
65
|
+
PRIMARY KEY (id)
|
66
|
+
);]
|
67
|
+
connect {
|
68
|
+
@db.run sql
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def get_schedule_metadata(key, options={})
|
73
|
+
connect {
|
74
|
+
row = @db.fetch("SELECT id, timeout, next_time, cron, delay, data, timezone FROM `#{@table}` LIMIT 1").first
|
75
|
+
unless row
|
76
|
+
raise NotFoundError, "schedule key=#{key} does not exist"
|
77
|
+
end
|
78
|
+
attributes = create_attributes(row)
|
79
|
+
return ScheduleMetadata.new(@client, key, attributes)
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def list(options, &block)
|
84
|
+
connect {
|
85
|
+
@db.fetch("SELECT id, timeout, next_time, cron, delay, data, timezone FROM `#{@table}` ORDER BY timeout ASC") {|row|
|
86
|
+
attributes = create_attributes(row)
|
87
|
+
sched = ScheduleWithMetadata.new(@client, row[:id], attributes)
|
88
|
+
yield sched
|
89
|
+
}
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
def add(key, type, cron, delay, timezone, data, next_time, next_run_time, options)
|
94
|
+
data = data.dup
|
95
|
+
data[:type] = type
|
96
|
+
connect {
|
97
|
+
begin
|
98
|
+
n = @db["INSERT INTO `#{@table}` (id, timeout, next_time, cron, delay, data, timezone) VALUES (?, ?, ?, ?, ?, ?, ?);", key, next_run_time, next_time, cron, delay, data.to_json, timezone].insert
|
99
|
+
return Schedule.new(@client, key)
|
100
|
+
rescue Sequel::DatabaseError
|
101
|
+
raise AlreadyExistsError, "schedule key=#{key} already exists"
|
102
|
+
end
|
103
|
+
}
|
104
|
+
end
|
105
|
+
|
106
|
+
def delete(key, options)
|
107
|
+
connect {
|
108
|
+
n = @db["DELETE FROM `#{@table}` WHERE id=?;", key].delete
|
109
|
+
if n <= 0
|
110
|
+
raise NotFoundError, "schedule key=#{key} does no exist"
|
111
|
+
end
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
def modify(key, options)
|
116
|
+
ks = []
|
117
|
+
vs = []
|
118
|
+
[:cron, :delay, :timezone].each {|k|
|
119
|
+
# TODO type and data are not supported
|
120
|
+
if v = options[k]
|
121
|
+
ks << k
|
122
|
+
vs << v
|
123
|
+
end
|
124
|
+
}
|
125
|
+
return nil if ks.empty?
|
126
|
+
|
127
|
+
sql = "UPDATE `#{@table}` SET "
|
128
|
+
sql << ks.map {|k| "#{k}=?" }.join(', ')
|
129
|
+
sql << " WHERE id=?"
|
130
|
+
|
131
|
+
args = [sql].concat(vs)
|
132
|
+
args << key
|
133
|
+
|
134
|
+
connect {
|
135
|
+
n = @db[*args].update
|
136
|
+
if n <= 0
|
137
|
+
raise NotFoundError, "schedule key=#{key} does not exist"
|
138
|
+
end
|
139
|
+
}
|
140
|
+
end
|
141
|
+
|
142
|
+
def acquire(alive_time, max_acquire, options)
|
143
|
+
now = (options[:now] || Time.now).to_i
|
144
|
+
next_timeout = now + alive_time
|
145
|
+
|
146
|
+
connect {
|
147
|
+
while true
|
148
|
+
rows = 0
|
149
|
+
@db.fetch("SELECT id, timeout, next_time, cron, delay, data, timezone FROM `#{@table}` WHERE timeout <= ? ORDER BY timeout ASC LIMIT #{MAX_SELECT_ROW};", now) {|row|
|
150
|
+
|
151
|
+
n = @db["UPDATE `#{@table}` SET timeout=? WHERE id=? AND timeout=?;", next_timeout, row[:id], row[:timeout]].update
|
152
|
+
if n > 0
|
153
|
+
scheduled_time = Time.at(row[:next_time]).utc
|
154
|
+
attributes = create_attributes(row)
|
155
|
+
task_token = Token.new(row[:id], row[:next_time], attributes[:cron], attributes[:delay], attributes[:timezone])
|
156
|
+
task = Task.new(@client, row[:id], attributes, scheduled_time, task_token)
|
157
|
+
return [task]
|
158
|
+
end
|
159
|
+
|
160
|
+
rows += 1
|
161
|
+
}
|
162
|
+
if rows < MAX_SELECT_ROW
|
163
|
+
return nil
|
164
|
+
end
|
165
|
+
end
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
def heartbeat(task_token, alive_time, options)
|
170
|
+
now = (options[:now] || Time.now).to_i
|
171
|
+
row_id = task_token.row_id
|
172
|
+
scheduled_time = task_token.scheduled_time
|
173
|
+
next_run_time = now + alive_time
|
174
|
+
|
175
|
+
connect {
|
176
|
+
n = @db["UPDATE `#{@table}` SET timeout=? WHERE id=? AND next_time=?;", next_run_time, row_id, scheduled_time].update
|
177
|
+
if n < 0
|
178
|
+
raise AlreadyFinishedError, "task time=#{Time.at(scheduled_time).utc} is already finished"
|
179
|
+
end
|
180
|
+
}
|
181
|
+
end
|
182
|
+
|
183
|
+
def finish(task_token, options)
|
184
|
+
row_id = task_token.row_id
|
185
|
+
scheduled_time = task_token.scheduled_time
|
186
|
+
next_time = PerfectSched.next_time(task_token.cron, scheduled_time, task_token.timezone)
|
187
|
+
next_run_time = next_time + task_token.delay
|
188
|
+
|
189
|
+
connect {
|
190
|
+
n = @db["UPDATE `#{@table}` SET timeout=?, next_time=? WHERE id=? AND next_time=?;", next_run_time, next_time, row_id, scheduled_time].update
|
191
|
+
if n < 0
|
192
|
+
raise AlreadyFinishedError, "task time=#{Time.at(scheduled_time).utc} is already finished"
|
193
|
+
end
|
194
|
+
}
|
195
|
+
end
|
196
|
+
|
197
|
+
protected
|
198
|
+
def connect(&block)
|
199
|
+
@mutex.synchronize do
|
200
|
+
retry_count = 0
|
201
|
+
begin
|
202
|
+
block.call
|
203
|
+
rescue
|
204
|
+
# workaround for "Mysql2::Error: Deadlock found when trying to get lock; try restarting transaction" error
|
205
|
+
if $!.to_s.include?('try restarting transaction')
|
206
|
+
err = ([$!] + $!.backtrace.map {|bt| " #{bt}" }).join("\n")
|
207
|
+
retry_count += 1
|
208
|
+
if retry_count < MAX_RETRY
|
209
|
+
STDERR.puts err + "\n retrying."
|
210
|
+
sleep 0.5
|
211
|
+
retry
|
212
|
+
else
|
213
|
+
STDERR.puts err + "\n abort."
|
214
|
+
end
|
215
|
+
end
|
216
|
+
raise
|
217
|
+
ensure
|
218
|
+
@db.disconnect
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def create_attributes(row)
|
224
|
+
timezone = row[:timezone] || 'UTC'
|
225
|
+
delay = row[:delay] || 0
|
226
|
+
cron = row[:cron]
|
227
|
+
next_time = Time.at(row[:next_time]).utc
|
228
|
+
next_run_time = Time.at(row[:timeout]).utc
|
229
|
+
|
230
|
+
begin
|
231
|
+
data = JSON.parse(row[:data] || '{}')
|
232
|
+
rescue
|
233
|
+
data = {}
|
234
|
+
end
|
235
|
+
|
236
|
+
type = data.delete('type') || ''
|
237
|
+
|
238
|
+
attributes = {
|
239
|
+
:timezone => timezone,
|
240
|
+
:delay => delay,
|
241
|
+
:cron => cron,
|
242
|
+
:data => data,
|
243
|
+
:next_time => next_time,
|
244
|
+
:next_run_time => next_run_time,
|
245
|
+
:type => type,
|
246
|
+
:message => nil, # not supported
|
247
|
+
:node => nil, # not supported
|
248
|
+
}
|
249
|
+
end
|
250
|
+
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|