perfectqueue 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 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