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 +14 -0
- data/lib/perfectqueue/backend/rdb_compat.rb +16 -8
- data/lib/perfectqueue/error.rb +16 -0
- data/lib/perfectqueue/version.rb +1 -1
- data/lib/perfectqueue.rb +1 -0
- data/spec/queue_spec.rb +70 -70
- data/spec/rdb_compat_backend_spec.rb +40 -0
- metadata +14 -12
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
|
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
|
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
|
-
|
276
|
-
|
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
|
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,
|
data/lib/perfectqueue/error.rb
CHANGED
@@ -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
|
|
data/lib/perfectqueue/version.rb
CHANGED
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
|
-
|
5
|
-
|
4
|
+
let :queue do
|
5
|
+
create_test_queue
|
6
6
|
end
|
7
7
|
|
8
8
|
after do
|
9
|
-
|
9
|
+
queue.client.close
|
10
10
|
end
|
11
11
|
|
12
12
|
it 'is a Queue' do
|
13
|
-
|
13
|
+
queue.class.should == PerfectQueue::Queue
|
14
14
|
end
|
15
15
|
|
16
16
|
it 'succeess submit' do
|
17
|
-
|
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
|
-
|
22
|
+
queue.submit('task01', 'type1', {}, :now=>now)
|
23
23
|
|
24
24
|
lambda {
|
25
|
-
|
25
|
+
queue.submit('task01', 'type1', {}, :now=>now+1)
|
26
26
|
}.should raise_error AlreadyExistsError
|
27
27
|
|
28
|
-
|
28
|
+
queue['task01'].cancel_request!(:now=>now+2)
|
29
29
|
|
30
30
|
lambda {
|
31
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
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
|
-
|
68
|
-
|
69
|
-
|
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 =
|
71
|
+
task01 = queue.poll(:now=>now+10)
|
72
72
|
task01.key.should == 'task01'
|
73
73
|
|
74
|
-
t2 =
|
74
|
+
t2 = queue.poll(:now=>now+10)
|
75
75
|
t2.key.should == 'task02'
|
76
76
|
|
77
|
-
t3 =
|
77
|
+
t3 = queue.poll(:now=>now+10)
|
78
78
|
t3.key.should == 'task03'
|
79
79
|
|
80
|
-
t4 =
|
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
|
-
|
86
|
+
queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
|
87
87
|
|
88
|
-
task01 =
|
88
|
+
task01 = queue.poll(:now=>now+10)
|
89
89
|
task01.key.should == 'task01'
|
90
90
|
|
91
|
-
t2 =
|
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 =
|
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
|
-
|
102
|
+
queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
|
103
103
|
|
104
|
-
task01 =
|
104
|
+
task01 = queue.poll(:now=>now+10, :alive_time=>10)
|
105
105
|
task01.key.should == 'task01'
|
106
106
|
|
107
|
-
t2 =
|
107
|
+
t2 = queue.poll(:now=>now+15)
|
108
108
|
t2.should == nil
|
109
109
|
|
110
|
-
t3 =
|
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
|
-
|
116
|
+
queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
|
117
117
|
|
118
|
-
task01 =
|
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 =
|
123
|
+
t2 = queue.poll(:now=>now+20)
|
124
124
|
t2.should == nil
|
125
125
|
|
126
|
-
t3 =
|
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
|
-
|
132
|
+
queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
|
133
133
|
|
134
|
-
task01 =
|
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 =
|
139
|
+
t2 = queue.poll(:now=>now+20)
|
140
140
|
t2.should == nil
|
141
141
|
|
142
|
-
t3 =
|
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
|
-
|
148
|
+
queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
|
149
149
|
|
150
|
-
task01 =
|
150
|
+
task01 = queue.poll(:now=>now+10)
|
151
151
|
task01.key.should == 'task01'
|
152
152
|
|
153
|
-
|
153
|
+
queue['task01'].metadata.running?.should == true
|
154
154
|
|
155
|
-
|
155
|
+
queue['task01'].force_finish!(:now=>now+11)
|
156
156
|
|
157
|
-
|
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
|
-
|
162
|
+
queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
|
163
163
|
|
164
164
|
# rdb_backend backend can't distinguish running with waiting
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
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 =
|
170
|
+
task01 = queue.poll(:now=>now+10, :alive_time=>10)
|
171
171
|
task01.key.should == 'task01'
|
172
172
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
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
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
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
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
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
|
-
|
196
|
+
queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
|
197
197
|
|
198
|
-
task01 =
|
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
|
-
|
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
|
-
|
210
|
+
queue.submit('task01', 'type1', {"a"=>1}, :now=>now+0)
|
211
211
|
|
212
|
-
|
212
|
+
queue['task01'].metadata.finished?.should == false
|
213
213
|
|
214
|
-
task01 =
|
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
|
-
|
219
|
+
queue.poll(:now=>now+12)
|
220
220
|
|
221
|
-
|
221
|
+
queue['task01'].exists?.should == true
|
222
222
|
|
223
|
-
|
223
|
+
queue.poll(:now=>now+22)
|
224
224
|
|
225
|
-
|
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
|
-
|
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.
|
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-
|
12
|
+
date: 2012-06-25 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sequel
|
16
|
-
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: *
|
24
|
+
version_requirements: *70128708982980
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rake
|
27
|
-
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: *
|
35
|
+
version_requirements: *70128708981280
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rspec
|
38
|
-
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: *
|
46
|
+
version_requirements: *70128708976640
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: simplecov
|
49
|
-
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: *
|
57
|
+
version_requirements: *70128708973780
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: sqlite3
|
60
|
-
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: *
|
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
|