perfectqueue 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 PerfectQueue::VERSION
5
+ * Fixed backward compatiblity of the 'type' field in rdb_compat backend
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-01-24 version 0.7.20
3
17
 
4
18
  * Fixed MonitorThread
@@ -90,7 +90,7 @@ SQL
90
90
  now = (options[:now] || Time.now).to_i
91
91
 
92
92
  connect {
93
- row = @db.fetch("SELECT timeout, data, created_at, resource FROM `#{@table}` LIMIT 1").first
93
+ row = @db.fetch("SELECT timeout, data, created_at, resource FROM `#{@table}` WHERE id=? LIMIT 1", key).first
94
94
  unless row
95
95
  raise NotFoundError, "task key=#{key} does no exist"
96
96
  end
@@ -124,7 +124,7 @@ SQL
124
124
  run_at = (options[:run_at] || now).to_i
125
125
  user = options[:user]
126
126
  priority = options[:priority] # not supported
127
- data ||= {}
127
+ data = data ? data.dup : {}
128
128
  data['type'] = type
129
129
 
130
130
  connect {
@@ -132,7 +132,7 @@ SQL
132
132
  n = @db["INSERT INTO `#{@table}` (id, timeout, data, created_at, resource) VALUES (?, ?, ?, ?, ?);", key, run_at, data.to_json, now, user].insert
133
133
  return Task.new(@client, key)
134
134
  rescue Sequel::DatabaseError
135
- raise AlreadyExistsError, "task key=#{key} already exists"
135
+ raise IdempotentAlreadyExistsError, "task key=#{key} already exists"
136
136
  end
137
137
  }
138
138
  end
@@ -198,7 +198,7 @@ SQL
198
198
  connect {
199
199
  n = @db["UPDATE `#{@table}` SET timeout=?, created_at=NULL, resource=NULL WHERE id=? AND created_at IS NOT NULL;", delete_timeout, key].update
200
200
  if n <= 0
201
- raise AlreadyFinishedError, "task key=#{key} does not exist or already finished."
201
+ raise IdempotentAlreadyFinishedError, "task key=#{key} does not exist or already finished."
202
202
  end
203
203
  }
204
204
  nil
@@ -272,13 +272,21 @@ SQL
272
272
  status = TaskStatus::RUNNING
273
273
  end
274
274
 
275
- begin
276
- data = JSON.parse(row[:data] || '{}')
277
- rescue
275
+ d = row[:data]
276
+ if d == nil || d == ''
278
277
  data = {}
278
+ else
279
+ begin
280
+ data = JSON.parse(d)
281
+ rescue
282
+ data = {}
283
+ end
279
284
  end
280
285
 
281
- type = data['type'] || ''
286
+ type = data.delete('type')
287
+ if type == nil || type.empty?
288
+ type = row[:id].split(/\./, 2)[0]
289
+ end
282
290
 
283
291
  attributes = {
284
292
  :status => status,
@@ -49,5 +49,21 @@ module PerfectQueue
49
49
 
50
50
  class GracefulProcessStopError < ProcessStopError
51
51
  end
52
+
53
+ # Applications can ignore these errors to achieve idempotency
54
+ module IdempotentError
55
+ end
56
+
57
+ class IdempotentAlreadyFinishedError < AlreadyFinishedError
58
+ include IdempotentError
59
+ end
60
+
61
+ class IdempotentAlreadyExistsError < AlreadyExistsError
62
+ include IdempotentError
63
+ end
64
+
65
+ class IdempotentNotFoundError < NotFoundError
66
+ include IdempotentError
67
+ end
52
68
  end
53
69
 
@@ -1,3 +1,3 @@
1
1
  module PerfectQueue
2
- VERSION = "0.8.0"
2
+ VERSION = "0.8.1"
3
3
  end
data/lib/perfectqueue.rb CHANGED
@@ -40,6 +40,7 @@ module PerfectQueue
40
40
  :TaskStatus => 'perfectqueue/task_status',
41
41
  :Worker => 'perfectqueue/worker',
42
42
  :SignalQueue => 'perfectqueue/signal_queue',
43
+ :VERSION => 'perfectqueue/version',
43
44
  }.each_pair {|k,v|
44
45
  autoload k, File.expand_path(v, File.dirname(__FILE__))
45
46
  }
data/spec/queue_spec.rb CHANGED
@@ -1,44 +1,44 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Queue do
4
- before do
5
- @queue = create_test_queue
4
+ let :queue do
5
+ create_test_queue
6
6
  end
7
7
 
8
8
  after do
9
- @queue.client.close
9
+ queue.client.close
10
10
  end
11
11
 
12
12
  it 'is a Queue' do
13
- @queue.class.should == PerfectQueue::Queue
13
+ queue.class.should == PerfectQueue::Queue
14
14
  end
15
15
 
16
16
  it 'succeess submit' do
17
- @queue.submit('task01', 'type1', {})
17
+ queue.submit('task01', 'type1', {})
18
18
  end
19
19
 
20
20
  it 'fail duplicated submit' do
21
21
  now = Time.now.to_i
22
- @queue.submit('task01', 'type1', {}, :now=>now)
22
+ queue.submit('task01', 'type1', {}, :now=>now)
23
23
 
24
24
  lambda {
25
- @queue.submit('task01', 'type1', {}, :now=>now+1)
25
+ queue.submit('task01', 'type1', {}, :now=>now+1)
26
26
  }.should raise_error AlreadyExistsError
27
27
 
28
- @queue['task01'].cancel_request!(:now=>now+2)
28
+ queue['task01'].cancel_request!(:now=>now+2)
29
29
 
30
30
  lambda {
31
- @queue.submit('task01', 'type1', {}, :now=>now+10)
31
+ queue.submit('task01', 'type1', {}, :now=>now+10)
32
32
  }.should raise_error AlreadyExistsError
33
33
  end
34
34
 
35
35
  it 'list' do
36
- @queue.submit('task01', 'type1', {"a"=>1})
37
- @queue.submit('task02', 'type1', {"a"=>2})
38
- @queue.submit('task03', 'type1', {"a"=>3})
36
+ queue.submit('task01', 'type1', {"a"=>1})
37
+ queue.submit('task02', 'type1', {"a"=>2})
38
+ queue.submit('task03', 'type1', {"a"=>3})
39
39
 
40
40
  a = []
41
- @queue.each {|t| a << t }
41
+ queue.each {|t| a << t }
42
42
  a.sort_by! {|t| t.key }
43
43
 
44
44
  task01 = a.shift
@@ -64,170 +64,170 @@ describe Queue do
64
64
 
65
65
  it 'poll' do
66
66
  now = Time.now.to_i
67
- @queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
68
- @queue.submit('task02', 'type1', {"a"=>2}, :now=>now+1)
69
- @queue.submit('task03', 'type1', {"a"=>3}, :now=>now+2)
67
+ queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
68
+ queue.submit('task02', 'type1', {"a"=>2}, :now=>now+1)
69
+ queue.submit('task03', 'type1', {"a"=>3}, :now=>now+2)
70
70
 
71
- task01 = @queue.poll(:now=>now+10)
71
+ task01 = queue.poll(:now=>now+10)
72
72
  task01.key.should == 'task01'
73
73
 
74
- t2 = @queue.poll(:now=>now+10)
74
+ t2 = queue.poll(:now=>now+10)
75
75
  t2.key.should == 'task02'
76
76
 
77
- t3 = @queue.poll(:now=>now+10)
77
+ t3 = queue.poll(:now=>now+10)
78
78
  t3.key.should == 'task03'
79
79
 
80
- t4 = @queue.poll(:now=>now+10)
80
+ t4 = queue.poll(:now=>now+10)
81
81
  t4.should == nil
82
82
  end
83
83
 
84
84
  it 'release' do
85
85
  now = Time.now.to_i
86
- @queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
86
+ queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
87
87
 
88
- task01 = @queue.poll(:now=>now+10)
88
+ task01 = queue.poll(:now=>now+10)
89
89
  task01.key.should == 'task01'
90
90
 
91
- t2 = @queue.poll(:now=>now+10)
91
+ t2 = queue.poll(:now=>now+10)
92
92
  t2.should == nil
93
93
 
94
94
  task01.release!(:now=>now+10)
95
95
 
96
- t3 = @queue.poll(:now=>now+11)
96
+ t3 = queue.poll(:now=>now+11)
97
97
  t3.key.should == 'task01'
98
98
  end
99
99
 
100
100
  it 'timeout' do
101
101
  now = Time.now.to_i
102
- @queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
102
+ queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
103
103
 
104
- task01 = @queue.poll(:now=>now+10, :alive_time=>10)
104
+ task01 = queue.poll(:now=>now+10, :alive_time=>10)
105
105
  task01.key.should == 'task01'
106
106
 
107
- t2 = @queue.poll(:now=>now+15)
107
+ t2 = queue.poll(:now=>now+15)
108
108
  t2.should == nil
109
109
 
110
- t3 = @queue.poll(:now=>now+20)
110
+ t3 = queue.poll(:now=>now+20)
111
111
  t3.key.should == 'task01'
112
112
  end
113
113
 
114
114
  it 'heartbeat' do
115
115
  now = Time.now.to_i
116
- @queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
116
+ queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
117
117
 
118
- task01 = @queue.poll(:now=>now+10, :alive_time=>10)
118
+ task01 = queue.poll(:now=>now+10, :alive_time=>10)
119
119
  task01.key.should == 'task01'
120
120
 
121
121
  task01.heartbeat!(:alive_time=>15, :now=>now+10)
122
122
 
123
- t2 = @queue.poll(:now=>now+20)
123
+ t2 = queue.poll(:now=>now+20)
124
124
  t2.should == nil
125
125
 
126
- t3 = @queue.poll(:now=>now+30)
126
+ t3 = queue.poll(:now=>now+30)
127
127
  t3.key.should == 'task01'
128
128
  end
129
129
 
130
130
  it 'retry' do
131
131
  now = Time.now.to_i
132
- @queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
132
+ queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
133
133
 
134
- task01 = @queue.poll(:now=>now+10, :alive_time=>10)
134
+ task01 = queue.poll(:now=>now+10, :alive_time=>10)
135
135
  task01.key.should == 'task01'
136
136
 
137
137
  task01.retry!(:retry_wait=>15, :now=>now+10)
138
138
 
139
- t2 = @queue.poll(:now=>now+20)
139
+ t2 = queue.poll(:now=>now+20)
140
140
  t2.should == nil
141
141
 
142
- t3 = @queue.poll(:now=>now+30)
142
+ t3 = queue.poll(:now=>now+30)
143
143
  t3.key.should == 'task01'
144
144
  end
145
145
 
146
146
  it 'froce_finish' do
147
147
  now = Time.now.to_i
148
- @queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
148
+ queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
149
149
 
150
- task01 = @queue.poll(:now=>now+10)
150
+ task01 = queue.poll(:now=>now+10)
151
151
  task01.key.should == 'task01'
152
152
 
153
- @queue['task01'].metadata.running?.should == true
153
+ queue['task01'].metadata.running?.should == true
154
154
 
155
- @queue['task01'].force_finish!(:now=>now+11)
155
+ queue['task01'].force_finish!(:now=>now+11)
156
156
 
157
- @queue['task01'].metadata.finished?.should == true
157
+ queue['task01'].metadata.finished?.should == true
158
158
  end
159
159
 
160
160
  it 'status' do
161
161
  now = Time.now.to_i
162
- @queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
162
+ queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
163
163
 
164
164
  # rdb_backend backend can't distinguish running with waiting
165
- #@queue['task01'].metadata.finished?.should == false
166
- #@queue['task01'].metadata.running?.should == false
167
- #@queue['task01'].metadata.waiting?.should == true
168
- #@queue['task01'].metadata.cancel_requested?.should == false
165
+ #queue['task01'].metadata.finished?.should == false
166
+ #queue['task01'].metadata.running?.should == false
167
+ #queue['task01'].metadata.waiting?.should == true
168
+ #queue['task01'].metadata.cancel_requested?.should == false
169
169
 
170
- task01 = @queue.poll(:now=>now+10, :alive_time=>10)
170
+ task01 = queue.poll(:now=>now+10, :alive_time=>10)
171
171
  task01.key.should == 'task01'
172
172
 
173
- @queue['task01'].metadata.finished?.should == false
174
- @queue['task01'].metadata.running?.should == true
175
- @queue['task01'].metadata.waiting?.should == false
176
- @queue['task01'].metadata.cancel_requested?.should == false
173
+ queue['task01'].metadata.finished?.should == false
174
+ queue['task01'].metadata.running?.should == true
175
+ queue['task01'].metadata.waiting?.should == false
176
+ queue['task01'].metadata.cancel_requested?.should == false
177
177
 
178
178
  task01.cancel_request!
179
179
 
180
180
  # status of cancel_requested running tasks is cancel_requested
181
- @queue['task01'].metadata.finished?.should == false
182
- @queue['task01'].metadata.running?.should == false
183
- @queue['task01'].metadata.waiting?.should == false
184
- @queue['task01'].metadata.cancel_requested?.should == true
181
+ queue['task01'].metadata.finished?.should == false
182
+ queue['task01'].metadata.running?.should == false
183
+ queue['task01'].metadata.waiting?.should == false
184
+ queue['task01'].metadata.cancel_requested?.should == true
185
185
 
186
186
  task01.finish!
187
187
 
188
- @queue['task01'].metadata.finished?.should == true
189
- @queue['task01'].metadata.running?.should == false
190
- @queue['task01'].metadata.waiting?.should == false
191
- @queue['task01'].metadata.cancel_requested?.should == false
188
+ queue['task01'].metadata.finished?.should == true
189
+ queue['task01'].metadata.running?.should == false
190
+ queue['task01'].metadata.waiting?.should == false
191
+ queue['task01'].metadata.cancel_requested?.should == false
192
192
  end
193
193
 
194
194
  it 'fail canceling finished task' do
195
195
  now = Time.now.to_i
196
- @queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
196
+ queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
197
197
 
198
- task01 = @queue.poll(:now=>now+10, :alive_time=>10)
198
+ task01 = queue.poll(:now=>now+10, :alive_time=>10)
199
199
  task01.key.should == 'task01'
200
200
 
201
201
  task01.finish!
202
202
 
203
203
  lambda {
204
- @queue['task01'].cancel_request!
204
+ queue['task01'].cancel_request!
205
205
  }.should raise_error AlreadyFinishedError
206
206
  end
207
207
 
208
208
  it 'retention_time' do
209
209
  now = Time.now.to_i
210
- @queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
210
+ queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
211
211
 
212
- @queue['task01'].metadata.finished?.should == false
212
+ queue['task01'].metadata.finished?.should == false
213
213
 
214
- task01 = @queue.poll(:now=>now+10, :alive_time=>10)
214
+ task01 = queue.poll(:now=>now+10, :alive_time=>10)
215
215
  task01.key.should == 'task01'
216
216
 
217
217
  task01.finish!(:now=>now+11, :retention_time=>10)
218
218
 
219
- @queue.poll(:now=>now+12)
219
+ queue.poll(:now=>now+12)
220
220
 
221
- @queue['task01'].exists?.should == true
221
+ queue['task01'].exists?.should == true
222
222
 
223
- @queue.poll(:now=>now+22)
223
+ queue.poll(:now=>now+22)
224
224
 
225
- @queue['task01'].exists?.should == false
225
+ queue['task01'].exists?.should == false
226
226
  end
227
227
 
228
228
  it 'get_task_metadata failed with NotFoundError' do
229
229
  lambda {
230
- @queue['task99'].metadata
230
+ queue['task99'].metadata
231
231
  }.should raise_error NotFoundError
232
232
  end
233
233
  end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+ require 'perfectqueue/backend/rdb_compat'
3
+
4
+ describe Backend::RDBCompatBackend do
5
+ let :queue do
6
+ FileUtils.rm_f 'spec/test.db'
7
+ queue = PerfectQueue.open({:type=>'rdb_compat', :url=>'sqlite://spec/test.db', :table=>'test_tasks', :processor_type=>'thread'})
8
+ queue.client.init_database
9
+ queue
10
+ end
11
+
12
+ let :client do
13
+ queue.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_tasks (id, timeout, data, created_at, resource) VALUES (?, ?, ?, ?, ?)", "merge_type.1339801200", 1339801201, {'url'=>nil}.to_json, 1339801201, "1"].insert
22
+ ts = backend.acquire(60, 1, {:now=>1339801203})
23
+ ts.should_not == nil
24
+ t = ts[0]
25
+ t.data.should == {'url'=>nil}
26
+ t.type.should == 'merge_type'
27
+ t.key.should == 'merge_type.1339801200'
28
+ end
29
+
30
+ it 'backward compatibility 2' do
31
+ backend.db["INSERT INTO test_tasks (id, timeout, data, created_at, resource) VALUES (?, ?, ?, ?, ?)", "query.379474", 1339801201, {'query_id'=>32}.to_json, 1339801201, nil].insert
32
+ ts = backend.acquire(60, 1, {:now=>1339801203})
33
+ ts.should_not == nil
34
+ t = ts[0]
35
+ t.data.should == {'query_id'=>32}
36
+ t.type.should == 'query'
37
+ t.key.should == 'query.379474'
38
+ end
39
+ end
40
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: perfectqueue
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: sequel
16
- requirement: &70295369852020 !ruby/object:Gem::Requirement
16
+ requirement: &70128708982980 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 3.26.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70295369852020
24
+ version_requirements: *70128708982980
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rake
27
- requirement: &70295369850460 !ruby/object:Gem::Requirement
27
+ requirement: &70128708981280 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 0.9.2
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70295369850460
35
+ version_requirements: *70128708981280
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rspec
38
- requirement: &70295369849140 !ruby/object:Gem::Requirement
38
+ requirement: &70128708976640 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 2.10.0
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70295369849140
46
+ version_requirements: *70128708976640
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: simplecov
49
- requirement: &70295369848300 !ruby/object:Gem::Requirement
49
+ requirement: &70128708973780 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ~>
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: 0.5.4
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70295369848300
57
+ version_requirements: *70128708973780
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: sqlite3
60
- requirement: &70295369847400 !ruby/object:Gem::Requirement
60
+ requirement: &70128708967780 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ~>
@@ -65,7 +65,7 @@ dependencies:
65
65
  version: 1.3.3
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *70295369847400
68
+ version_requirements: *70128708967780
69
69
  description: Highly available distributed cron built on RDBMS
70
70
  email: frsyuki@gmail.com
71
71
  executables:
@@ -109,6 +109,7 @@ files:
109
109
  - lib/perfectqueue/worker.rb
110
110
  - perfectqueue.gemspec
111
111
  - spec/queue_spec.rb
112
+ - spec/rdb_compat_backend_spec.rb
112
113
  - spec/spec_helper.rb
113
114
  - spec/worker_spec.rb
114
115
  homepage: https://github.com/treasure-data/perfectqueue
@@ -137,6 +138,7 @@ specification_version: 3
137
138
  summary: Highly available distributed cron built on RDBMS
138
139
  test_files:
139
140
  - spec/queue_spec.rb
141
+ - spec/rdb_compat_backend_spec.rb
140
142
  - spec/spec_helper.rb
141
143
  - spec/worker_spec.rb
142
144
  has_rdoc: false