perfectsched 0.8.0 → 0.8.1

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.
data/ChangeLog CHANGED
@@ -1,4 +1,18 @@
1
1
 
2
+ == 2012-06-25 version 0.8.1
3
+
4
+ * Added autoload for PerfectSched::VERSION
5
+ * Fixed rdb_compat backend to use Integer type for time fields
6
+ * Added IdempotentAlreadyFinishedError and IdempotentAlreadyExistsError
7
+ which should be ignored for idempotency
8
+
9
+
10
+ == 2012-05-23 version 0.8.0
11
+
12
+ * New major version
13
+ * Redesigned API
14
+
15
+
2
16
  == 2012-02-21 version 0.7.10
3
17
 
4
18
  * Fixed NullBackend#modify_checked
@@ -71,7 +71,7 @@ module PerfectSched
71
71
 
72
72
  def get_schedule_metadata(key, options={})
73
73
  connect {
74
- row = @db.fetch("SELECT id, timeout, next_time, cron, delay, data, timezone FROM `#{@table}` LIMIT 1").first
74
+ row = @db.fetch("SELECT id, timeout, next_time, cron, delay, data, timezone FROM `#{@table}` WHERE id=? LIMIT 1", key).first
75
75
  unless row
76
76
  raise NotFoundError, "schedule key=#{key} does not exist"
77
77
  end
@@ -91,14 +91,14 @@ module PerfectSched
91
91
  end
92
92
 
93
93
  def add(key, type, cron, delay, timezone, data, next_time, next_run_time, options)
94
- data = data.dup
95
- data[:type] = type
94
+ data = data ? data.dup : {}
95
+ data['type'] = type
96
96
  connect {
97
97
  begin
98
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
99
  return Schedule.new(@client, key)
100
100
  rescue Sequel::DatabaseError
101
- raise AlreadyExistsError, "schedule key=#{key} already exists"
101
+ raise IdempotentAlreadyExistsError, "schedule key=#{key} already exists"
102
102
  end
103
103
  }
104
104
  end
@@ -107,7 +107,7 @@ module PerfectSched
107
107
  connect {
108
108
  n = @db["DELETE FROM `#{@table}` WHERE id=?;", key].delete
109
109
  if n <= 0
110
- raise NotFoundError, "schedule key=#{key} does no exist"
110
+ raise IdempotentNotFoundError, "schedule key=#{key} does no exist"
111
111
  end
112
112
  }
113
113
  end
@@ -150,7 +150,7 @@ module PerfectSched
150
150
 
151
151
  n = @db["UPDATE `#{@table}` SET timeout=? WHERE id=? AND timeout=?;", next_timeout, row[:id], row[:timeout]].update
152
152
  if n > 0
153
- scheduled_time = Time.at(row[:next_time]).utc
153
+ scheduled_time = row[:next_time]
154
154
  attributes = create_attributes(row)
155
155
  task_token = Token.new(row[:id], row[:next_time], attributes[:cron], attributes[:delay], attributes[:timezone])
156
156
  task = Task.new(@client, row[:id], attributes, scheduled_time, task_token)
@@ -189,7 +189,7 @@ module PerfectSched
189
189
  connect {
190
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
191
  if n < 0
192
- raise AlreadyFinishedError, "task time=#{Time.at(scheduled_time).utc} is already finished"
192
+ raise IdempotentAlreadyFinishedError, "task time=#{Time.at(scheduled_time).utc} is already finished"
193
193
  end
194
194
  }
195
195
  end
@@ -224,16 +224,24 @@ module PerfectSched
224
224
  timezone = row[:timezone] || 'UTC'
225
225
  delay = row[:delay] || 0
226
226
  cron = row[:cron]
227
- next_time = Time.at(row[:next_time]).utc
228
- next_run_time = Time.at(row[:timeout]).utc
227
+ next_time = row[:next_time]
228
+ next_run_time = row[:timeout]
229
229
 
230
- begin
231
- data = JSON.parse(row[:data] || '{}')
232
- rescue
230
+ d = row[:data]
231
+ if d == nil || d == ''
233
232
  data = {}
233
+ else
234
+ begin
235
+ data = JSON.parse(d)
236
+ rescue
237
+ data = {}
238
+ end
234
239
  end
235
240
 
236
- type = data.delete('type') || ''
241
+ type = data.delete('type')
242
+ if type == nil || type.empty?
243
+ type = row[:id].split(/\./, 2)[0]
244
+ end
237
245
 
238
246
  attributes = {
239
247
  :timezone => timezone,
@@ -40,4 +40,20 @@ module PerfectSched
40
40
 
41
41
  class ProcessStopError < RuntimeError
42
42
  end
43
+
44
+ # Applications can ignore these errors to achieve idempotency
45
+ module IdempotentError
46
+ end
47
+
48
+ class IdempotentAlreadyFinishedError < AlreadyFinishedError
49
+ include IdempotentError
50
+ end
51
+
52
+ class IdempotentAlreadyExistsError < AlreadyExistsError
53
+ include IdempotentError
54
+ end
55
+
56
+ class IdempotentNotFoundError < NotFoundError
57
+ include IdempotentError
58
+ end
43
59
  end
@@ -1,3 +1,3 @@
1
1
  module PerfectSched
2
- VERSION = "0.8.0"
2
+ VERSION = "0.8.1"
3
3
  end
data/lib/perfectsched.rb CHANGED
@@ -38,6 +38,7 @@ module PerfectSched
38
38
  :SignalQueue => 'perfectsched/signal_queue',
39
39
  :Task => 'perfectsched/task',
40
40
  :Worker => 'perfectsched/worker',
41
+ :VERSION => 'perfectsched/version',
41
42
  }.each_pair {|k,v|
42
43
  autoload k, File.expand_path(v, File.dirname(__FILE__))
43
44
  }
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+ require 'perfectsched/backend/rdb_compat'
3
+
4
+ describe Backend::RDBCompatBackend do
5
+ let :sc do
6
+ FileUtils.rm_f 'spec/test.db'
7
+ sc = PerfectSched.open({:type=>'rdb_compat', :url=>'sqlite://spec/test.db', :table=>'test_scheds'})
8
+ sc.client.init_database
9
+ sc
10
+ end
11
+
12
+ let :client do
13
+ sc.client
14
+ end
15
+
16
+ let :backend do
17
+ client.backend
18
+ end
19
+
20
+ it 'backward compatibility 1' do
21
+ backend.db["INSERT INTO test_scheds (id, timeout, next_time, cron, delay, data, timezone) VALUES (?, ?, ?, ?, ?, ?, ?)", "maint_sched.1.do_hourly", 1339812000, 1339812000, "0 * * * *", 0, {"account_id"=>1}.to_json, "UTC"].insert
22
+ ts = backend.acquire(60, 1, {:now=>1339812003})
23
+ ts.should_not == nil
24
+ t = ts[0]
25
+ t.data.should == {'account_id'=>1}
26
+ t.type.should == 'maint_sched'
27
+ t.key.should == 'maint_sched.1.do_hourly'
28
+ t.next_time.should == 1339812000
29
+ end
30
+
31
+ it 'backward compatibility 2' do
32
+ backend.db["INSERT INTO test_scheds (id, timeout, next_time, cron, delay, data, timezone) VALUES (?, ?, ?, ?, ?, ?, ?)", "merge", 1339812060, 1339812000, "@hourly", 60, '', "Asia/Tokyo"].insert
33
+ ts = backend.acquire(60, 1, {:now=>1339812060})
34
+ t = ts[0]
35
+ t.data.should == {}
36
+ t.type.should == 'merge'
37
+ t.key.should == 'merge'
38
+ t.next_time.should == 1339812000
39
+ end
40
+ end
41
+
@@ -1,142 +1,142 @@
1
1
 
2
2
  describe ScheduleCollection do
3
- before do
4
- @sc = create_test_sched
3
+ let :sc do
4
+ create_test_sched
5
5
  end
6
6
 
7
7
  after do
8
- @sc.client.close
8
+ sc.client.close
9
9
  end
10
10
 
11
11
  it 'is a ScheduleCollection' do
12
- @sc.class.should == PerfectSched::ScheduleCollection
12
+ sc.class.should == PerfectSched::ScheduleCollection
13
13
  end
14
14
 
15
15
  it 'succeess add' do
16
- @sc.add('sched01', 't01', {:cron=>'* * * * *', :timezone=>'UTC'})
16
+ sc.add('sched01', 't01', {:cron=>'* * * * *', :timezone=>'UTC'})
17
17
  end
18
18
 
19
19
  it 'fail duplicated add' do
20
- @sc.add('sched01', 't01', {:cron=>'* * * * *', :timezone=>'UTC'})
20
+ sc.add('sched01', 't01', {:cron=>'* * * * *', :timezone=>'UTC'})
21
21
 
22
22
  lambda {
23
- @sc.add('sched01', 't01', {:cron=>'* * * * *', :timezone=>'UTC'})
23
+ sc.add('sched01', 't01', {:cron=>'* * * * *', :timezone=>'UTC'})
24
24
  }.should raise_error AlreadyExistsError
25
25
 
26
- @sc['sched01'].delete!
26
+ sc['sched01'].delete!
27
27
 
28
- @sc.add('sched01', 't01', {:cron=>'* * * * *', :timezone=>'UTC'})
28
+ sc.add('sched01', 't01', {:cron=>'* * * * *', :timezone=>'UTC'})
29
29
  end
30
30
 
31
31
  it 'acquire' do
32
32
  time = 1323820800 # 2011-12-14 00:00:00 UTC
33
33
 
34
- s01 = @sc.add('sched01', 't01', :cron=>"* * * * *", :data=>{'k'=>1}, :next_time=>time)
34
+ s01 = sc.add('sched01', 't01', :cron=>"* * * * *", :data=>{'k'=>1}, :next_time=>time)
35
35
 
36
- t01 = @sc.poll(:alive_time=>120, :now=>time)
36
+ t01 = sc.poll(:alive_time=>120, :now=>time)
37
37
  t01.key.should == 'sched01'
38
38
  t01.type.should == 't01'
39
39
  t01.cron.should == "* * * * *"
40
40
  t01.delay.should == 0
41
41
  t01.data.should == {'k'=>1}
42
- t01.scheduled_time.should == Time.at(time).utc
42
+ t01.scheduled_time.should == time
43
43
 
44
- t02 = @sc.poll(:alive_time=>120, :now=>time+60)
44
+ t02 = sc.poll(:alive_time=>120, :now=>time+60)
45
45
  t02.should == nil
46
46
 
47
47
  t01.finish!
48
48
 
49
- t04 = @sc.poll(:alive_time=>120, :now=>time+60)
49
+ t04 = sc.poll(:alive_time=>120, :now=>time+60)
50
50
  t04.key.should == 'sched01'
51
51
  t01.type.should == 't01'
52
52
  t04.cron.should == "* * * * *"
53
53
  t04.delay.should == 0
54
54
  t04.data.should == {'k'=>1}
55
- t04.scheduled_time.should == Time.at(time+60).utc
55
+ t04.scheduled_time.should == time+60
56
56
  end
57
57
 
58
58
  it 'timezone' do
59
59
  time = 1323820800 # 2011-12-14 00:00:00 UTC
60
60
 
61
- s01 = @sc.add('sched01', 't01', :cron=>"0 0 * * *", :next_time=>time-60, :timezone=>'UTC')
61
+ s01 = sc.add('sched01', 't01', :cron=>"0 0 * * *", :next_time=>time-60, :timezone=>'UTC')
62
62
  #s01.class.should == Schedule
63
63
  #s01.key.should == 'sched01'
64
64
 
65
- s02 = @sc.add('sched02', 't01', :cron=>"0 0 * * *", :next_time=>time-60, :timezone=>'Asia/Tokyo')
65
+ s02 = sc.add('sched02', 't01', :cron=>"0 0 * * *", :next_time=>time-60, :timezone=>'Asia/Tokyo')
66
66
  #s02.class.should == Schedule
67
67
  #s02.key.should == 'sched02'
68
68
 
69
- t01 = @sc.poll(:alive_time=>86400, :now=>time)
69
+ t01 = sc.poll(:alive_time=>86400, :now=>time)
70
70
  t01.class.should == Task
71
71
  t01.key.should == 'sched01'
72
72
  t01.type.should == 't01'
73
- t01.scheduled_time.should == Time.at(time).utc
73
+ t01.scheduled_time.should == time
74
74
 
75
- t02 = @sc.poll(:alive_time=>86400, :now=>time+54000)
75
+ t02 = sc.poll(:alive_time=>86400, :now=>time+54000)
76
76
  t02.class.should == Task
77
77
  t02.key.should == 'sched02'
78
78
  t02.type.should == 't01'
79
- t02.scheduled_time.should == Time.at(time+54000).utc
79
+ t02.scheduled_time.should == time+54000
80
80
  end
81
81
 
82
82
  it 'delay' do
83
83
  time = 1323820800 # 2011-12-14 00:00:00 UTC
84
84
 
85
- s01 = @sc.add('sched01', 't01', :cron=>"0 * * * *", :delay=>30, :next_time=>time, :timezone=>'UTC')
85
+ s01 = sc.add('sched01', 't01', :cron=>"0 * * * *", :delay=>30, :next_time=>time, :timezone=>'UTC')
86
86
 
87
- t01 = @sc.poll(:alive_time=>86400, :now=>time)
87
+ t01 = sc.poll(:alive_time=>86400, :now=>time)
88
88
  t01.should == nil
89
89
 
90
- t02 = @sc.poll(:alive_time=>86400, :now=>time+30)
90
+ t02 = sc.poll(:alive_time=>86400, :now=>time+30)
91
91
  t02.class.should == Task
92
92
  t02.key.should == 'sched01'
93
93
  t02.type.should == 't01'
94
- t02.scheduled_time.should == Time.at(time).utc
94
+ t02.scheduled_time.should == time
95
95
  t02.delay.should == 30
96
96
 
97
97
  t02.finish!
98
98
 
99
- t03 = @sc.poll(:alive_time=>86400, :now=>time+3600)
99
+ t03 = sc.poll(:alive_time=>86400, :now=>time+3600)
100
100
  t03.should == nil
101
101
 
102
- t04 = @sc.poll(:alive_time=>86400, :now=>time+3630)
102
+ t04 = sc.poll(:alive_time=>86400, :now=>time+3630)
103
103
  t04.class.should == Task
104
104
  t04.key.should == 'sched01'
105
105
  t04.type.should == 't01'
106
- t04.scheduled_time.should == Time.at(time+3600).utc
106
+ t04.scheduled_time.should == time+3600
107
107
  t04.delay.should == 30
108
108
  end
109
109
 
110
110
  it 'invalid cron format' do
111
111
  lambda {
112
- @sc.add('sched01', 't01', :cron=>'???')
112
+ sc.add('sched01', 't01', :cron=>'???')
113
113
  }.should raise_error ArgumentError
114
114
 
115
115
  lambda {
116
- @sc.add('sched01', 't01', :cron=>'* * * * * *')
116
+ sc.add('sched01', 't01', :cron=>'* * * * * *')
117
117
  }.should raise_error ArgumentError
118
118
  end
119
119
 
120
120
  it 'fail duplicated add' do
121
- @sc.add('sched01', 't01', :cron=>"0 * * * *")
121
+ sc.add('sched01', 't01', :cron=>"0 * * * *")
122
122
  lambda {
123
- @sc.add('sched01', 't01', :cron=>"0 * * * *")
123
+ sc.add('sched01', 't01', :cron=>"0 * * * *")
124
124
  }.should raise_error AlreadyExistsError
125
125
 
126
- @sc['sched01'].delete!
126
+ sc['sched01'].delete!
127
127
 
128
- @sc.add('sched01', 't01', :cron=>"0 * * * *")
128
+ sc.add('sched01', 't01', :cron=>"0 * * * *")
129
129
  end
130
130
 
131
131
  it 'list' do
132
132
  time = 1323820800 # 2011-12-14 00:00:00 UTC
133
133
 
134
- @sc.add('sched01', 't01', :cron=>"0 * * * *", :next_time=>time, :delay=>1)
135
- @sc.add('sched02', 't02', :cron=>"0 * * * *", :next_time=>time, :delay=>2)
136
- @sc.add('sched03', 't03', :cron=>"0 * * * *", :next_time=>time, :delay=>3, :next_run_time=>time+3600)
134
+ sc.add('sched01', 't01', :cron=>"0 * * * *", :next_time=>time, :delay=>1)
135
+ sc.add('sched02', 't02', :cron=>"0 * * * *", :next_time=>time, :delay=>2)
136
+ sc.add('sched03', 't03', :cron=>"0 * * * *", :next_time=>time, :delay=>3, :next_run_time=>time+3600)
137
137
 
138
138
  a = []
139
- @sc.list {|s|
139
+ sc.list {|s|
140
140
  a << s
141
141
  }
142
142
  a.sort_by! {|s| s.key }
@@ -147,8 +147,8 @@ describe ScheduleCollection do
147
147
  s01.type.should == 't01'
148
148
  s01.cron.should == '0 * * * *'
149
149
  s01.delay.should == 1
150
- s01.next_time.should == Time.at(time).utc
151
- s01.next_run_time.should == Time.at(time+1).utc
150
+ s01.next_time.should == time
151
+ s01.next_run_time.should == time+1
152
152
 
153
153
  s02 = a.shift
154
154
  s02.class.should == ScheduleWithMetadata
@@ -156,8 +156,8 @@ describe ScheduleCollection do
156
156
  s02.type.should == 't02'
157
157
  s02.cron.should == '0 * * * *'
158
158
  s02.delay.should == 2
159
- s02.next_time.should == Time.at(time).utc
160
- s02.next_run_time.should == Time.at(time+2).utc
159
+ s02.next_time.should == time
160
+ s02.next_run_time.should == time+2
161
161
 
162
162
  s03 = a.shift
163
163
  s03.class.should == ScheduleWithMetadata
@@ -165,8 +165,8 @@ describe ScheduleCollection do
165
165
  s03.type.should == 't03'
166
166
  s03.cron.should == '0 * * * *'
167
167
  s03.delay.should == 3
168
- s03.next_time.should == Time.at(time).utc
169
- s03.next_run_time.should == Time.at(time+3600).utc
168
+ s03.next_time.should == time
169
+ s03.next_run_time.should == time+3600
170
170
  end
171
171
  end
172
172
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: perfectsched
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.8.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-23 00:00:00.000000000Z
12
+ date: 2012-06-25 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: cron-spec
16
- requirement: &70253331590900 !ruby/object:Gem::Requirement
16
+ requirement: &70165887081720 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -24,10 +24,10 @@ dependencies:
24
24
  version: 0.1.2
25
25
  type: :runtime
26
26
  prerelease: false
27
- version_requirements: *70253331590900
27
+ version_requirements: *70165887081720
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: sequel
30
- requirement: &70253331589420 !ruby/object:Gem::Requirement
30
+ requirement: &70165887079760 !ruby/object:Gem::Requirement
31
31
  none: false
32
32
  requirements:
33
33
  - - ~>
@@ -35,10 +35,10 @@ dependencies:
35
35
  version: 3.26.0
36
36
  type: :runtime
37
37
  prerelease: false
38
- version_requirements: *70253331589420
38
+ version_requirements: *70165887079760
39
39
  - !ruby/object:Gem::Dependency
40
40
  name: tzinfo
41
- requirement: &70253331588280 !ruby/object:Gem::Requirement
41
+ requirement: &70165887078760 !ruby/object:Gem::Requirement
42
42
  none: false
43
43
  requirements:
44
44
  - - ~>
@@ -46,10 +46,10 @@ dependencies:
46
46
  version: 0.3.29
47
47
  type: :runtime
48
48
  prerelease: false
49
- version_requirements: *70253331588280
49
+ version_requirements: *70165887078760
50
50
  - !ruby/object:Gem::Dependency
51
51
  name: perfectqueue
52
- requirement: &70253331587400 !ruby/object:Gem::Requirement
52
+ requirement: &70165887077360 !ruby/object:Gem::Requirement
53
53
  none: false
54
54
  requirements:
55
55
  - - ~>
@@ -57,10 +57,10 @@ dependencies:
57
57
  version: 0.8.0
58
58
  type: :runtime
59
59
  prerelease: false
60
- version_requirements: *70253331587400
60
+ version_requirements: *70165887077360
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: rake
63
- requirement: &70253331576220 !ruby/object:Gem::Requirement
63
+ requirement: &70165887076440 !ruby/object:Gem::Requirement
64
64
  none: false
65
65
  requirements:
66
66
  - - ~>
@@ -68,10 +68,10 @@ dependencies:
68
68
  version: 0.9.2
69
69
  type: :development
70
70
  prerelease: false
71
- version_requirements: *70253331576220
71
+ version_requirements: *70165887076440
72
72
  - !ruby/object:Gem::Dependency
73
73
  name: rspec
74
- requirement: &70253331573960 !ruby/object:Gem::Requirement
74
+ requirement: &70165887075580 !ruby/object:Gem::Requirement
75
75
  none: false
76
76
  requirements:
77
77
  - - ~>
@@ -79,10 +79,10 @@ dependencies:
79
79
  version: 2.10.0
80
80
  type: :development
81
81
  prerelease: false
82
- version_requirements: *70253331573960
82
+ version_requirements: *70165887075580
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: simplecov
85
- requirement: &70253331573320 !ruby/object:Gem::Requirement
85
+ requirement: &70165887074140 !ruby/object:Gem::Requirement
86
86
  none: false
87
87
  requirements:
88
88
  - - ~>
@@ -90,10 +90,10 @@ dependencies:
90
90
  version: 0.5.4
91
91
  type: :development
92
92
  prerelease: false
93
- version_requirements: *70253331573320
93
+ version_requirements: *70165887074140
94
94
  - !ruby/object:Gem::Dependency
95
95
  name: sqlite3
96
- requirement: &70253331572160 !ruby/object:Gem::Requirement
96
+ requirement: &70165887069320 !ruby/object:Gem::Requirement
97
97
  none: false
98
98
  requirements:
99
99
  - - ~>
@@ -101,7 +101,7 @@ dependencies:
101
101
  version: 1.3.3
102
102
  type: :development
103
103
  prerelease: false
104
- version_requirements: *70253331572160
104
+ version_requirements: *70165887069320
105
105
  description: Highly available distributed cron built on RDBMS
106
106
  email: frsyuki@gmail.com
107
107
  executables:
@@ -135,6 +135,7 @@ files:
135
135
  - lib/perfectsched/version.rb
136
136
  - lib/perfectsched/worker.rb
137
137
  - perfectsched.gemspec
138
+ - spec/rdb_compat_backend_spec.rb
138
139
  - spec/schedule_collection_spec.rb
139
140
  - spec/spec_helper.rb
140
141
  - spec/worker_spec.rb
@@ -163,6 +164,7 @@ signing_key:
163
164
  specification_version: 3
164
165
  summary: Highly available distributed cron built on RDBMS
165
166
  test_files:
167
+ - spec/rdb_compat_backend_spec.rb
166
168
  - spec/schedule_collection_spec.rb
167
169
  - spec/spec_helper.rb
168
170
  - spec/worker_spec.rb