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,45 +0,0 @@
1
-
2
- module PerfectSched
3
-
4
-
5
- class NullBackend < Backend
6
- def list(&block)
7
- nil
8
- end
9
-
10
- def acquire(timeout, now=Time.now.to_i)
11
- nil
12
- end
13
-
14
- def finish(token, next_time, timeout)
15
- true
16
- end
17
-
18
- def add_checked(id, cron, delay, data, next_time, timeout, timezone)
19
- true
20
- end
21
-
22
- def delete(id)
23
- true
24
- end
25
-
26
- def get(id)
27
- nil
28
- end
29
-
30
- def modify_checked(id, cron, delay, data, timezone)
31
- true
32
- end
33
-
34
- def modify_sched_checked(id, cron, delay)
35
- true
36
- end
37
-
38
- def modify_data_checked(id, data)
39
- true
40
- end
41
- end
42
-
43
-
44
- end
45
-
@@ -1,165 +0,0 @@
1
-
2
- module PerfectSched
3
-
4
-
5
- class RDBBackend < Backend
6
- def initialize(uri, table, config={})
7
- super()
8
- require 'sequel'
9
- require 'uri'
10
- @uri = uri
11
- @table = table
12
-
13
- u = URI.parse(@uri)
14
- options = {
15
- max_connections: 1,
16
- user: u.user,
17
- password: u.password,
18
- host: u.host,
19
- port: u.port ? u.port.to_i : 3306
20
- }
21
- options[:sslca] = config[:sslca] if config[:sslca]
22
- db_name = u.path.split('/')[1]
23
- @db = Sequel.mysql2(db_name, options)
24
-
25
- #init_db(@uri.split('//',2)[0])
26
- connect {
27
- # connection test
28
- }
29
- end
30
-
31
- def create_tables
32
- sql = ''
33
- sql << "CREATE TABLE IF NOT EXISTS `#{@table}` ("
34
- sql << " id VARCHAR(256) NOT NULL,"
35
- sql << " timeout INT NOT NULL,"
36
- sql << " next_time INT NOT NULL,"
37
- sql << " cron VARCHAR(128) NOT NULL,"
38
- sql << " delay INT NOT NULL,"
39
- sql << " data BLOB NOT NULL,"
40
- sql << " timezone VARCHAR(256) NULL,"
41
- sql << " PRIMARY KEY (id)"
42
- sql << ");"
43
- # TODO index
44
- connect {
45
- @db.run sql
46
- }
47
- end
48
-
49
- private
50
- def connect(&block)
51
- begin
52
- block.call
53
- ensure
54
- @db.disconnect
55
- end
56
- end
57
-
58
- public
59
- def list(&block)
60
- @db.fetch("SELECT id, timeout, next_time, cron, delay, data, timezone FROM `#{@table}` ORDER BY timeout ASC") {|row|
61
- yield row[:id], row[:cron], row[:delay], row[:data], row[:next_time], row[:timeout], row[:timezone]
62
- }
63
- end
64
-
65
- MAX_SELECT_ROW = 4
66
-
67
- def acquire(timeout, now=Time.now.to_i)
68
- connect {
69
- while true
70
- rows = 0
71
- @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|
72
-
73
- n = @db["UPDATE `#{@table}` SET timeout=? WHERE id=? AND timeout=?;", timeout, row[:id], row[:timeout]].update
74
- salt = timeout
75
- if n > 0
76
- return [row[:id],salt], Task.new(row[:id], row[:next_time], row[:cron], row[:delay], row[:data], row[:timezone])
77
- end
78
-
79
- rows += 1
80
- }
81
- if rows < MAX_SELECT_ROW
82
- return nil
83
- end
84
- end
85
- }
86
- end
87
-
88
- def finish(token, next_time, timeout)
89
- connect {
90
- id, salt = *token
91
- n = @db["UPDATE `#{@table}` SET timeout=?, next_time=? WHERE id=? AND timeout=?;", timeout, next_time, id, salt].update
92
- return n > 0
93
- }
94
- end
95
-
96
- def add_checked(id, cron, delay, data, next_time, timeout, timezone)
97
- connect {
98
- begin
99
- n = @db["INSERT INTO `#{@table}` (id, timeout, next_time, cron, delay, data, timezone) VALUES (?, ?, ?, ?, ?, ?, ?);", id, timeout, next_time, cron, delay, data, timezone].insert
100
- return true
101
- rescue Sequel::DatabaseError
102
- return nil
103
- end
104
- }
105
- end
106
-
107
- def delete(id)
108
- connect {
109
- n = @db["DELETE FROM `#{@table}` WHERE id=?;", id].delete
110
- return n > 0
111
- }
112
- end
113
-
114
- def get(id)
115
- connect {
116
- @db.fetch("SELECT id, timeout, next_time, cron, delay, data, timezone FROM `#{@table}` WHERE id=?;", id) {|row|
117
- return row[:cron], row[:delay], row[:data], row[:timezone], row[:next_time]
118
- }
119
- return nil
120
- }
121
- end
122
-
123
- # Overrides parent method in order to pass the calculated next run time into modify_checked
124
- def modify(id, cron, delay, data, timezone)
125
- cron = cron.strip
126
- next_time = @croncalc.next_time(cron, Time.now.to_i/60*60, timezone)
127
- modify_checked(id, cron, delay, data, timezone, next_time)
128
- return next_time, next_time + delay
129
- end
130
-
131
- # Overrides parent method in order to pass the calculated next run time into modify_sched_checked
132
- def modify_sched(id, cron, delay)
133
- cron_, delay_, data_, timezone, next_time = get(id)
134
- cron = cron.strip
135
- next_time = @croncalc.next_time(cron, Time.now.to_i/60*60, timezone)
136
- modify_sched_checked(id, cron, delay, next_time)
137
- return next_time, next_time + delay
138
- end
139
-
140
- def modify_checked(id, cron, delay, data, timezone, next_time)
141
- connect {
142
- n = @db["UPDATE `#{@table}` SET cron=?, delay=?, data=?, timezone=?, next_time=?, timeout=? WHERE id=?;", cron, delay, data, timezone, next_time, next_time + delay, id].update
143
- return n > 0
144
- }
145
- end
146
-
147
- def modify_sched_checked(id, cron, delay, next_time)
148
- connect {
149
- n = @db["UPDATE `#{@table}` SET cron=?, delay=?, next_time=?, timeout=? WHERE id=?;", cron, delay, next_time, next_time + delay, id].update
150
- return n > 0
151
- }
152
- end
153
-
154
- def modify_data_checked(id, data)
155
- connect {
156
- n = @db["UPDATE `#{@table}` SET data=? WHERE id=?;", data, id].update
157
- return n > 0
158
- }
159
- end
160
-
161
- end
162
-
163
-
164
- end
165
-
@@ -1,174 +0,0 @@
1
-
2
- module PerfectSched
3
-
4
-
5
- class SimpleDBBackend < Backend
6
- def initialize(key_id, secret_key, domain)
7
- super()
8
- require 'aws-sdk'
9
- @consistent_read = false
10
-
11
- @db = AWS::SimpleDB.new(
12
- :access_key_id => key_id,
13
- :secret_access_key => secret_key)
14
-
15
- @domain_name = domain
16
- @domain = @db.domains[@domain_name]
17
- unless @domain.exists?
18
- @domain = @db.domains.create(@domain_name)
19
- end
20
- end
21
-
22
- attr_accessor :consistent_read
23
-
24
- def use_consistent_read(b=true)
25
- @consistent_read = b
26
- self
27
- end
28
-
29
- def list(&block)
30
- rows = 0
31
- @domain.items.select('timeout', 'next_time', 'cron', 'delay', 'data', 'timezone',
32
- :where => "timeout > '#{int_encode(0)}'", # required by SimpleDB
33
- :order => [:timeout, :asc],
34
- :consistent_read => @consistent_read,
35
- :limit => MAX_SELECT_ROW) {|itemdata|
36
- id = itemdata.name
37
- attrs = itemdata.attributes
38
-
39
- next_time = int_decode(attrs['next_time'].first)
40
- cron = attrs['cron'].first
41
- delay = int_decode(attrs['delay'].first)
42
- data = attrs['data'].first
43
- timezone = attrs['timezone'].first
44
- timeout = int_decode(attrs['timeout'].first)
45
-
46
- yield id, cron, delay, data, next_time, timeout, timezone
47
- }
48
- end
49
-
50
- MAX_SELECT_ROW = 4
51
-
52
- def acquire(timeout, now=Time.now.to_i)
53
- while true
54
- rows = 0
55
- @domain.items.select('timeout', 'next_time', 'cron', 'delay', 'data', 'timezone',
56
- :where => "timeout <= '#{int_encode(now)}'",
57
- :order => [:timeout, :asc],
58
- :consistent_read => @consistent_read,
59
- :limit => MAX_SELECT_ROW) {|itemdata|
60
- begin
61
- id = itemdata.name
62
- attrs = itemdata.attributes
63
-
64
- @domain.items[id].attributes.replace('timeout'=>int_encode(timeout),
65
- :if=>{'timeout'=>attrs['timeout'].first})
66
-
67
- next_time = int_decode(attrs['next_time'].first)
68
- cron = attrs['cron'].first
69
- delay = int_decode(attrs['delay'].first)
70
- data = attrs['data'].first
71
- timezone = attrs['timezone'].first
72
- salt = int_encode(timeout)
73
-
74
- return [id,salt], Task.new(id, next_time, cron, delay, data, timezone)
75
-
76
- rescue AWS::SimpleDB::Errors::ConditionalCheckFailed, AWS::SimpleDB::Errors::AttributeDoesNotExist
77
- end
78
-
79
- rows += 1
80
- }
81
- if rows < MAX_SELECT_ROW
82
- return nil
83
- end
84
- end
85
- end
86
-
87
- def finish(token, next_time, timeout)
88
- begin
89
- id, salt = *token
90
- @domain.items[id].attributes.replace('timeout'=>int_encode(timeout), 'next_time'=>int_encode(next_time),
91
- :if=>{'timeout'=>salt})
92
- return true
93
- rescue AWS::SimpleDB::Errors::ConditionalCheckFailed, AWS::SimpleDB::Errors::AttributeDoesNotExist
94
- return false
95
- end
96
- end
97
-
98
- def add_checked(id, cron, delay, data, next_time, timeout, timezone)
99
- begin
100
- hash = {}
101
- hash['timeout'] = int_encode(timeout)
102
- hash['next_time'] = int_encode(next_time)
103
- hash['cron'] = cron
104
- hash['delay'] = int_encode(delay)
105
- hash['data'] = data
106
- hash['timezone'] = timezone if timezone
107
- hash[:unless] = 'timeout'
108
- @domain.items[id].attributes.replace()
109
- return true
110
- rescue AWS::SimpleDB::Errors::ConditionalCheckFailed, AWS::SimpleDB::Errors::ExistsAndExpectedValue
111
- return nil
112
- end
113
- end
114
-
115
- def delete(id)
116
- # TODO return value
117
- begin
118
- @domain.items[id].delete
119
- return true
120
- rescue AWS::SimpleDB::Errors::ConditionalCheckFailed, AWS::SimpleDB::Errors::AttributeDoesNotExist
121
- return false
122
- end
123
- end
124
-
125
- def get(id)
126
- attrs = @domain.items[id].data.attributes
127
- cron = attrs['cron'].first
128
- unless cron
129
- return nil
130
- end
131
- delay = int_decode(attrs['delay'].first)
132
- data = attrs['data'].first
133
- timezone = attrs['timezone'].first
134
- next_time = int_decode(attrs['next_time'].first)
135
- return cron, delay, data, timezone, next_time
136
- end
137
-
138
- def modify_checked(id, cron, delay, data, timezone)
139
- unless get(id)
140
- return false
141
- end
142
- @domain.items[id].attributes.replace('cron'=>cron, 'delay'=>int_encode(delay), 'data'=>data, 'timezone'=>timezone)
143
- return true
144
- end
145
-
146
- def modify_sched_checked(id, cron, delay)
147
- unless get(id)
148
- return false
149
- end
150
- @domain.items[id].attributes.replace('cron'=>cron, 'delay'=>int_encode(delay))
151
- return true
152
- end
153
-
154
- def modify_data_checked(id, data)
155
- unless get(id)
156
- return false
157
- end
158
- @domain.items[id].attributes.replace('data'=>data)
159
- return true
160
- end
161
-
162
- private
163
- def int_encode(num)
164
- "%08x" % num
165
- end
166
-
167
- def int_decode(str)
168
- str.to_i(16)
169
- end
170
- end
171
-
172
-
173
- end
174
-
@@ -1,29 +0,0 @@
1
-
2
- module PerfectSched
3
-
4
-
5
- class CronCalc
6
- def initialize
7
- require 'cron-spec'
8
- require 'tzinfo'
9
- # TODO optimize
10
- end
11
-
12
- def next_time(cron, time, timezone)
13
- tab = CronSpec::CronSpecification.new(cron)
14
- tz = TZInfo::Timezone.get(timezone) if timezone
15
- while true
16
- time += 60
17
- t = Time.at(time)
18
- t = tz.utc_to_local(t.utc) if tz
19
- if tab.is_specification_in_effect?(t)
20
- return time
21
- end
22
- # FIXME break
23
- end
24
- end
25
- end
26
-
27
-
28
- end
29
-
data/test/backend_test.rb DELETED
@@ -1,217 +0,0 @@
1
- require File.dirname(__FILE__)+'/test_helper'
2
-
3
- class BackendTest < Test::Unit::TestCase
4
- SCHED = 120
5
- TIMEOUT = 60
6
- DB_PATH = File.dirname(__FILE__)+'/test.db'
7
- DB_URI = "sqlite://#{DB_PATH}"
8
-
9
- def clean_backend
10
- @key_prefix = "test-#{"%08x"%rand(2**32)}-"
11
- db = open_backend
12
- db.list {|id,cron,delay,data,next_time,timeout|
13
- db.delete(id)
14
- }
15
- FileUtils.rm_f DB_PATH
16
- end
17
-
18
- def open_backend
19
- #PerfectSched::SimpleDBBackend.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY'], 'perfectsched-test-1').use_consistent_read
20
- db = PerfectSched::RDBBackend.new(DB_URI, "perfectdb_test")
21
- db.create_tables
22
- db
23
- end
24
-
25
- it 'acquire' do
26
- clean_backend
27
-
28
- db1 = open_backend
29
- db2 = open_backend
30
- db3 = open_backend
31
-
32
- time = 0
33
-
34
- ok = db1.add(@key_prefix+'test1', "* * * * *", 0, 'data1', time)
35
- assert_equal true, ok
36
-
37
- token, task = db2.acquire(180, 60)
38
- assert_not_equal nil, task
39
- assert_equal @key_prefix+'test1', task.id
40
- assert_equal "* * * * *", task.cron
41
- assert_equal 0, task.delay
42
- assert_equal 'data1', task.data
43
- assert_equal 60, task.time
44
-
45
- token_, task_ = db3.acquire(180, 60)
46
- assert_equal nil, token_
47
-
48
- token_, task_ = db3.acquire(240, 120)
49
- assert_equal nil, token_
50
-
51
- ok = db2.finish(token, 120, 120)
52
- assert_equal true, ok
53
-
54
- token, task = db3.acquire(240, 120)
55
- assert_not_equal nil, task
56
- assert_equal @key_prefix+'test1', task.id
57
- assert_equal "* * * * *", task.cron
58
- assert_equal 0, task.delay
59
- assert_equal 'data1', task.data
60
- assert_equal 120, task.time
61
- end
62
-
63
- it 'timeout' do
64
- clean_backend
65
-
66
- db1 = open_backend
67
- db2 = open_backend
68
- db3 = open_backend
69
-
70
- time = 0
71
-
72
- ok = db1.add(@key_prefix+'test1', "* * * * *", 0, 'data1', time)
73
- assert_equal true, ok
74
-
75
- token, task = db2.acquire(180, 60)
76
- assert_not_equal nil, task
77
- assert_equal @key_prefix+'test1', task.id
78
- assert_equal "* * * * *", task.cron
79
- assert_equal 0, task.delay
80
- assert_equal 'data1', task.data
81
- assert_equal 60, task.time
82
-
83
- token, task = db3.acquire(240, 180)
84
- assert_not_equal nil, task
85
- assert_equal @key_prefix+'test1', task.id
86
- assert_equal "* * * * *", task.cron
87
- assert_equal 0, task.delay
88
- assert_equal 'data1', task.data
89
- assert_equal 60, task.time
90
- end
91
-
92
- it 'delay' do
93
- clean_backend
94
-
95
- db1 = open_backend
96
- db2 = open_backend
97
- db3 = open_backend
98
-
99
- time = 0
100
-
101
- ok = db1.add(@key_prefix+'test1', "* * * * *", 30, 'data1', time)
102
- assert_equal true, ok
103
-
104
- token_, task_ = db2.acquire(180, 60)
105
- assert_equal nil, token_
106
-
107
- token, task = db2.acquire(210, 90)
108
- assert_not_equal nil, task
109
- assert_equal @key_prefix+'test1', task.id
110
- assert_equal "* * * * *", task.cron
111
- assert_equal 30, task.delay
112
- assert_equal 'data1', task.data
113
- assert_equal 60, task.time
114
- end
115
-
116
- it 'invalid format' do
117
- clean_backend
118
-
119
- db1 = open_backend
120
-
121
- assert_raise(RuntimeError) do
122
- db1.add('k', '???', 0, 'data1', 0)
123
- end
124
-
125
- assert_raise(RuntimeError) do
126
- db1.add('k', '* * * * * *', 0, 'data1', 0)
127
- end
128
- end
129
-
130
- it 'unique id' do
131
- clean_backend
132
-
133
- db1 = open_backend
134
- time = 0
135
- key = @key_prefix+'test1'
136
-
137
- ok = db1.add(key, "* * * * *", 0, 'data1', time)
138
- assert_equal true, ok
139
-
140
- ok = db1.add(key, "* * * * *", 0, 'data1', time)
141
- assert_not_equal true, ok
142
-
143
- ok = db1.delete(key)
144
- assert_equal true, ok
145
-
146
- ok = db1.add(key, "* * * * *", 0, 'data1', time)
147
- assert_equal true, ok
148
- end
149
-
150
- it 'modify' do
151
- clean_backend
152
-
153
- db1 = open_backend
154
- time = 0
155
- key = @key_prefix+'test1'
156
-
157
- ok = db1.add(key, "* * * * *", 0, 'data1', time)
158
- assert_equal true, ok
159
-
160
- cron, delay, data, timezone, next_time = db1.get(key)
161
- assert_equal "* * * * *", cron
162
- assert_equal 0, delay
163
- assert_equal 'data1', data
164
- assert_equal time+60, next_time
165
-
166
- ok = db1.modify_sched(key, "* * * * 1", 10)
167
- assert_equal true, ok
168
-
169
- cron, delay, data, timezone, next_time = db1.get(key)
170
- assert_equal "* * * * 1", cron
171
- assert_equal 10, delay
172
- assert_equal 'data1', data
173
- assert_equal time+60, next_time
174
-
175
- ok = db1.modify_data(key, "data2")
176
- assert_equal true, ok
177
-
178
- cron, delay, data, timezone, next_time = db1.get(key)
179
- assert_equal "* * * * 1", cron
180
- assert_equal 10, delay
181
- assert_equal 'data2', data
182
- assert_equal time+60, next_time
183
-
184
- ok = db1.modify(key, "* * * * 2", 20, "data3", nil)
185
- assert_equal true, ok
186
-
187
- cron, delay, data, timezone, next_time = db1.get(key)
188
- assert_equal "* * * * 2", cron
189
- assert_equal 20, delay
190
- assert_equal 'data3', data
191
- assert_equal time+60, next_time
192
- end
193
-
194
- it 'timezone' do
195
- clean_backend
196
-
197
- db1 = open_backend
198
- time = 1323820800 # 2011-12-14 00:00:00 UTC
199
-
200
- ok = db1.add(@key_prefix+'test1', "0 0 * * *", 0, '', time-60, 'UTC')
201
- assert_equal true, ok
202
-
203
- ok = db1.add(@key_prefix+'test2', "0 0 * * *", 0, '', time-60, 'Asia/Tokyo')
204
- assert_equal true, ok
205
-
206
- token, task = db1.acquire(time+86400, time)
207
- assert_not_equal nil, task
208
- assert_equal @key_prefix+'test1', task.id
209
- assert_equal time, task.time
210
-
211
- token, task = db1.acquire(time+54000+86400, time+54000)
212
- assert_not_equal nil, task
213
- assert_equal @key_prefix+'test2', task.id
214
- assert_equal time+54000, task.time
215
- end
216
- end
217
-