perfectsched 0.8.0 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
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