perfectqueue 0.8.45 → 0.8.46
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ChangeLog +7 -0
- data/lib/perfectqueue/backend/rdb_compat.rb +32 -34
- data/lib/perfectqueue/multiprocess/thread_processor.rb +1 -1
- data/lib/perfectqueue/version.rb +1 -1
- data/spec/rdb_compat_backend_spec.rb +25 -5
- data/spec/rdb_stress.rb +79 -0
- data/spec/task_monitor_spec.rb +30 -0
- data/spec/task_spec.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e091df346ddc80d849184494788572dbbe07919e
|
4
|
+
data.tar.gz: 40533e8a9adf12acc69d85273675ba9043743c8b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 253f1165eebc079a3ad181add6e6a516bb3457000de54172470a62a8ac26335208a1bbb135df77b1a8ad28ad82103426d8321f70954f357a5b94c410b6e7e15c
|
7
|
+
data.tar.gz: d37142ec6e8927b4708bb8f223d61ac3a5618ebccfae7b70239612883e0a1739c69c4910c2cc4390de12756268cad248ea34cdb2300a213e6bf348b87cf3a33c
|
data/ChangeLog
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
== 2016-03-23 version 0.8.46
|
2
|
+
|
3
|
+
* Introduce new timeout model
|
4
|
+
NOTE: this change requires migration process like actual DELETE query
|
5
|
+
see WHERE timeout <= now AND created_at IS NULL.
|
6
|
+
80946079a91dd89c8e371e9b3fdd7f4805c7e4f9
|
7
|
+
|
1
8
|
== 2015-12-02 version 0.8.45
|
2
9
|
|
3
10
|
* remove sqlite3 support
|
@@ -21,6 +21,18 @@ module PerfectQueue
|
|
21
21
|
class RDBCompatBackend
|
22
22
|
include BackendHelper
|
23
23
|
|
24
|
+
#
|
25
|
+
# == timeout model
|
26
|
+
#
|
27
|
+
# 0 ---- now-1Bs ---- retention ---|----- now -- alive ------- FUTURE
|
28
|
+
# ~~~~~~~^ to be deleted ^ |~~~^~~~ ^ running or in-queue
|
29
|
+
# DELETE 13_0000_0000->| to be acquired
|
30
|
+
#
|
31
|
+
# NOTE: this architecture introduces Year 2042 problem.
|
32
|
+
#
|
33
|
+
DELETE_OFFSET = 10_0000_0000
|
34
|
+
EVENT_HORIZON = 13_0000_0000 # 2011-03-13 07:06:40 UTC
|
35
|
+
|
24
36
|
class Token < Struct.new(:key)
|
25
37
|
end
|
26
38
|
|
@@ -34,24 +46,8 @@ module PerfectQueue
|
|
34
46
|
raise ConfigError, ":table option is required"
|
35
47
|
end
|
36
48
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
if /\Amysql/i =~ url
|
41
|
-
require 'uri'
|
42
|
-
|
43
|
-
uri = URI.parse(url)
|
44
|
-
options = {
|
45
|
-
user: uri.user,
|
46
|
-
password: uri.password,
|
47
|
-
host: uri.host,
|
48
|
-
port: uri.port ? uri.port.to_i : 3306,
|
49
|
-
max_connections: 1
|
50
|
-
}
|
51
|
-
options[:sslca] = config[:sslca] if config[:sslca]
|
52
|
-
|
53
|
-
db_name = uri.path.split('/')[1]
|
54
|
-
@db = Sequel.mysql2(db_name, options)
|
49
|
+
if /\Amysql2:/i =~ url
|
50
|
+
@db = Sequel.connect(url, {max_connections: 1, sslca: config[:sslca]})
|
55
51
|
if config.fetch(:use_connection_pooling, nil) != nil
|
56
52
|
@use_connection_pooling = !!config[:use_connection_pooling]
|
57
53
|
else
|
@@ -67,7 +63,7 @@ module PerfectQueue
|
|
67
63
|
end
|
68
64
|
}
|
69
65
|
@table_unlock = lambda {
|
70
|
-
@db.run("
|
66
|
+
@db.run("DO RELEASE_LOCK('#{@table}')")
|
71
67
|
}
|
72
68
|
else
|
73
69
|
raise ConfigError, "only 'mysql' is supported"
|
@@ -84,7 +80,8 @@ module PerfectQueue
|
|
84
80
|
@sql = <<SQL
|
85
81
|
SELECT id, timeout, data, created_at, resource
|
86
82
|
FROM `#{@table}`
|
87
|
-
WHERE
|
83
|
+
WHERE #{EVENT_HORIZON} < timeout AND timeout <= ? AND timeout <= ?
|
84
|
+
AND created_at IS NOT NULL
|
88
85
|
ORDER BY timeout ASC
|
89
86
|
LIMIT ?
|
90
87
|
SQL
|
@@ -98,7 +95,9 @@ LEFT JOIN (
|
|
98
95
|
WHERE timeout > ? AND created_at IS NOT NULL AND resource IS NOT NULL
|
99
96
|
GROUP BY resource
|
100
97
|
) AS R ON resource = res
|
101
|
-
WHERE
|
98
|
+
WHERE #{EVENT_HORIZON} < timeout AND timeout <= ?
|
99
|
+
AND created_at IS NOT NULL
|
100
|
+
AND (max_running-running IS NULL OR max_running-running > 0)
|
102
101
|
ORDER BY weight DESC, timeout ASC
|
103
102
|
LIMIT ?
|
104
103
|
SQL
|
@@ -220,8 +219,9 @@ SQL
|
|
220
219
|
tasks = []
|
221
220
|
|
222
221
|
if @cleanup_interval_count <= 0
|
223
|
-
|
224
|
-
|
222
|
+
delete_timeout = now - DELETE_OFFSET
|
223
|
+
connect { # TODO: HERE should be still connect_locked ?
|
224
|
+
@db["DELETE FROM `#{@table}` WHERE timeout <= ? AND created_at IS NULL", delete_timeout].delete
|
225
225
|
@cleanup_interval_count = @cleanup_interval
|
226
226
|
}
|
227
227
|
end
|
@@ -243,15 +243,16 @@ SQL
|
|
243
243
|
return nil
|
244
244
|
end
|
245
245
|
|
246
|
-
sql = "UPDATE `#{@table}` SET timeout=? WHERE id IN ("
|
247
|
-
params = [sql, next_timeout]
|
246
|
+
sql = "UPDATE `#{@table}` SET timeout=? WHERE timeout <= ? AND id IN ("
|
247
|
+
params = [sql, next_timeout, now]
|
248
248
|
tasks.each {|t| params << t.key }
|
249
249
|
sql << (1..tasks.size).map { '?' }.join(',')
|
250
250
|
sql << ") AND created_at IS NOT NULL"
|
251
251
|
|
252
252
|
n = @db[*params].update
|
253
253
|
if n != tasks.size
|
254
|
-
#
|
254
|
+
# NOTE table lock doesn't work. error!
|
255
|
+
return nil
|
255
256
|
end
|
256
257
|
|
257
258
|
@cleanup_interval_count -= 1
|
@@ -279,7 +280,7 @@ SQL
|
|
279
280
|
# => nil
|
280
281
|
def finish(task_token, retention_time, options)
|
281
282
|
now = (options[:now] || Time.now).to_i
|
282
|
-
delete_timeout = now + retention_time
|
283
|
+
delete_timeout = now - DELETE_OFFSET + retention_time
|
283
284
|
key = task_token.key
|
284
285
|
|
285
286
|
connect {
|
@@ -333,15 +334,12 @@ SQL
|
|
333
334
|
locked = false
|
334
335
|
|
335
336
|
begin
|
336
|
-
@
|
337
|
-
|
338
|
-
|
339
|
-
locked = true
|
340
|
-
end
|
341
|
-
|
342
|
-
return block.call
|
337
|
+
if @table_lock
|
338
|
+
@table_lock.call
|
339
|
+
locked = true
|
343
340
|
end
|
344
341
|
|
342
|
+
return block.call
|
345
343
|
ensure
|
346
344
|
if @use_connection_pooling && locked
|
347
345
|
@table_unlock.call
|
@@ -121,7 +121,7 @@ module PerfectQueue
|
|
121
121
|
end
|
122
122
|
|
123
123
|
def process(task)
|
124
|
-
@log.info "acquired task task=#{task.key} id=#{@processor_id}"
|
124
|
+
@log.info "acquired task task=#{task.key} id=#{@processor_id}: #{task.inspect}"
|
125
125
|
begin
|
126
126
|
r = @runner.new(task)
|
127
127
|
@tm.set_task(task, r)
|
data/lib/perfectqueue/version.rb
CHANGED
@@ -76,7 +76,7 @@ describe Backend::RDBCompatBackend do
|
|
76
76
|
let (:now){ Time.now.to_i }
|
77
77
|
let (:client){ double('client') }
|
78
78
|
let (:table){ 'test_queues' }
|
79
|
-
let (:config){ {url: '
|
79
|
+
let (:config){ {url: 'mysql2://root:@localhost/perfectqueue_test', table: table} }
|
80
80
|
let (:db) do
|
81
81
|
d = Backend::RDBCompatBackend.new(client, config)
|
82
82
|
s = d.db
|
@@ -103,12 +103,12 @@ describe Backend::RDBCompatBackend do
|
|
103
103
|
expect{Backend::RDBCompatBackend.new(client, config)}.to raise_error(ConfigError)
|
104
104
|
end
|
105
105
|
it 'with use_connection_pooling' do
|
106
|
-
config = {url: '
|
106
|
+
config = {url: 'mysql2://root:@localhost/perfectqueue_test', table: table, use_connection_pooling: true}
|
107
107
|
db = Backend::RDBCompatBackend.new(client, config)
|
108
108
|
expect(db.instance_variable_get(:@use_connection_pooling)).to eq true
|
109
109
|
end
|
110
110
|
it 'disable_resource_limit' do
|
111
|
-
config = {url: '
|
111
|
+
config = {url: 'mysql2://root:@localhost/perfectqueue_test', table: table, disable_resource_limit: true}
|
112
112
|
db = Backend::RDBCompatBackend.new(client, config)
|
113
113
|
expect(db.instance_variable_get(:@sql)).not_to include('max_running')
|
114
114
|
end
|
@@ -228,6 +228,26 @@ describe Backend::RDBCompatBackend do
|
|
228
228
|
expect(ary[1]).to be_an_instance_of(AcquiredTask)
|
229
229
|
end
|
230
230
|
end
|
231
|
+
context 'stole a task' do
|
232
|
+
let :t0 do now - 100 end
|
233
|
+
before do
|
234
|
+
db.submit('key1', 'test1', nil, {now: t0})
|
235
|
+
db.submit('key2', 'test2', nil, {now: t0})
|
236
|
+
db.submit('key3', 'test3', nil, {now: t0})
|
237
|
+
end
|
238
|
+
it 'returns nil' do
|
239
|
+
# hook and steal a task
|
240
|
+
mock = double('prefetch_break_types')
|
241
|
+
db.instance_variable_set(:@prefetch_break_types, mock)
|
242
|
+
allow(mock).to receive(:include?) do
|
243
|
+
db.db['UPDATE `test_queues` SET timeout=? WHERE id=?', now+300, 'key2'].update
|
244
|
+
false
|
245
|
+
end
|
246
|
+
|
247
|
+
ary = db.acquire(alive_time, max_acquire, {})
|
248
|
+
expect(ary).to be_nil
|
249
|
+
end
|
250
|
+
end
|
231
251
|
end
|
232
252
|
|
233
253
|
context '#cancel_request' do
|
@@ -275,7 +295,7 @@ describe Backend::RDBCompatBackend do
|
|
275
295
|
let (:key){ 'key' }
|
276
296
|
let (:task_token){ Backend::RDBCompatBackend::Token.new(key) }
|
277
297
|
let (:retention_time) { 42 }
|
278
|
-
let (:delete_timeout){ now + retention_time }
|
298
|
+
let (:delete_timeout){ now - Backend::RDBCompatBackend::DELETE_OFFSET + retention_time }
|
279
299
|
let (:options){ {now: now} }
|
280
300
|
context 'have the task' do
|
281
301
|
before do
|
@@ -442,7 +462,7 @@ describe Backend::RDBCompatBackend do
|
|
442
462
|
end
|
443
463
|
it 'ensures to unlock on error with use_connection_pooling' do
|
444
464
|
#expect(STDERR).to receive(:puts)
|
445
|
-
config = {url: '
|
465
|
+
config = {url: 'mysql2://root:@localhost/perfectqueue_test', table: table, use_connection_pooling: true}
|
446
466
|
db1 = Backend::RDBCompatBackend.new(client, config)
|
447
467
|
#expect{ db.__send__(:connect_locked){ raise } }.to raise_error(RuntimeError)
|
448
468
|
db1.__send__(:connect_locked){ ret }
|
data/spec/rdb_stress.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'perfectqueue/backend/rdb_compat'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
# run this with `bundle exec rake spec SPEC_OPTS="-fd" SPEC=spec/rdb_stress.rb`
|
6
|
+
|
7
|
+
describe Backend::RDBCompatBackend do
|
8
|
+
let (:now){ Time.now.to_i }
|
9
|
+
let (:client){ double('client') }
|
10
|
+
let (:table){ 'test_queues' }
|
11
|
+
let (:config){ {
|
12
|
+
url: 'mysql2://root:@localhost/perfectqueue_test',
|
13
|
+
table: table,
|
14
|
+
disable_resource_limit: true,
|
15
|
+
} }
|
16
|
+
let (:db) do
|
17
|
+
d = Backend::RDBCompatBackend.new(client, config)
|
18
|
+
s = d.db
|
19
|
+
s.tables.each{|t| s.drop_table(t) }
|
20
|
+
d.init_database({})
|
21
|
+
d
|
22
|
+
end
|
23
|
+
|
24
|
+
context '#acquire' do
|
25
|
+
let (:task_token){ Backend::RDBCompatBackend::Token.new(key) }
|
26
|
+
let (:alive_time){ 42 }
|
27
|
+
let (:max_acquire){ 10 }
|
28
|
+
|
29
|
+
context 'some tasks' do
|
30
|
+
before do
|
31
|
+
sql = nil
|
32
|
+
bucket_size = 200000
|
33
|
+
600_000.times do |i|
|
34
|
+
if i % bucket_size == 0
|
35
|
+
sql = 'INSERT `test_queues` (id, timeout, data, created_at, resource) VALUES'
|
36
|
+
end
|
37
|
+
t = now - 600 + i/1000
|
38
|
+
sql << "(UUID(),#{t},TO_BASE64(RANDOM_BYTES(540)),#{t},NULL),"
|
39
|
+
if i % bucket_size == bucket_size - 1
|
40
|
+
db.db.run sql.chop!
|
41
|
+
end
|
42
|
+
end
|
43
|
+
db.db.loggers << Logger.new($stderr)
|
44
|
+
db.db.sql_log_level = :debug
|
45
|
+
end
|
46
|
+
it 'returns a task' do
|
47
|
+
#db.instance_variable_set(:@cleanup_interval_count, 0)
|
48
|
+
#expect(db.db.instance_variable_get(:@default_dataset)).to receive(:delete).and_call_original
|
49
|
+
ary = db.acquire(alive_time, max_acquire, {})
|
50
|
+
expect(ary).to be_an_instance_of(Array)
|
51
|
+
expect(ary.size).to eq(10)
|
52
|
+
expect(ary[0]).to be_an_instance_of(AcquiredTask)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'very large jobs' do
|
57
|
+
before do
|
58
|
+
sql = nil
|
59
|
+
sql = 'INSERT `test_queues` (id, timeout, data, created_at, resource) VALUES'
|
60
|
+
data = %<UNCOMPRESS(UNCOMPRESS(FROM_BASE64('6B8AAHic7c6xCYNQFEDRh8HGAawjmAmEOE7WsPziGoJ1xnAJLbJCVgiJbpBOkHOqW96IFN34nvvYpOvyuZXPIgAAAICTS6/hku1RfR9tffQNAAAA8Icxb+7r9AO74A1h')))>
|
61
|
+
200.times do |i|
|
62
|
+
t = now - 600 + i/1000
|
63
|
+
sql << "(UUID(),#{t},#{data},#{t},NULL),"
|
64
|
+
end
|
65
|
+
db.db.run sql.chop!
|
66
|
+
db.db.loggers << Logger.new($stderr)
|
67
|
+
db.db.sql_log_level = :debug
|
68
|
+
end
|
69
|
+
it 'returns a task' do
|
70
|
+
#db.instance_variable_set(:@cleanup_interval_count, 0)
|
71
|
+
#expect(db.db.instance_variable_get(:@default_dataset)).to receive(:delete).and_call_original
|
72
|
+
ary = db.acquire(alive_time, max_acquire, {})
|
73
|
+
expect(ary).to be_an_instance_of(Array)
|
74
|
+
expect(ary.size).to eq(10)
|
75
|
+
expect(ary[0]).to be_an_instance_of(AcquiredTask)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/spec/task_monitor_spec.rb
CHANGED
@@ -40,3 +40,33 @@ describe PerfectQueue::TaskMonitor do
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
end
|
43
|
+
|
44
|
+
describe PerfectQueue::TaskMonitorHook do
|
45
|
+
let (:task_monitor) do
|
46
|
+
tm = PerfectQueue::TaskMonitor.new(logger: double('logger').as_null_object)
|
47
|
+
end
|
48
|
+
let (:task) do
|
49
|
+
obj = double('task', key: 'foo', finish!: 1, release!: 1, retry!: 1, cancel_request!: 1, update_data!: 1)
|
50
|
+
obj.extend(TaskMonitorHook)
|
51
|
+
obj.instance_variable_set(:@log, double('log', info: nil))
|
52
|
+
obj.instance_variable_set(:@task_monitor, task_monitor)
|
53
|
+
obj
|
54
|
+
end
|
55
|
+
before do
|
56
|
+
end
|
57
|
+
describe 'finish!' do
|
58
|
+
it { task.finish! }
|
59
|
+
end
|
60
|
+
describe 'release!' do
|
61
|
+
it { task.release! }
|
62
|
+
end
|
63
|
+
describe 'retry!' do
|
64
|
+
it { task.retry! }
|
65
|
+
end
|
66
|
+
describe 'cancel_request!' do
|
67
|
+
it { task.cancel_request! }
|
68
|
+
end
|
69
|
+
describe 'update_data!' do
|
70
|
+
it { task.update_data!(double) }
|
71
|
+
end
|
72
|
+
end
|
data/spec/task_spec.rb
CHANGED
@@ -42,7 +42,7 @@ describe PerfectQueue::Task do
|
|
42
42
|
|
43
43
|
describe '#update_data!' do
|
44
44
|
context 'PLT-4238' do
|
45
|
-
let (:config){ {type: 'rdb_compat', url: '
|
45
|
+
let (:config){ {type: 'rdb_compat', url: 'mysql2://root:@localhost/perfectqueue_test', table: 'test_queues'} }
|
46
46
|
let (:client){ Client.new(config) }
|
47
47
|
before do
|
48
48
|
client.backend.db.tap{|s| s.tables.each{|t| s.drop_table(t) } }
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
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.46
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sadayuki Furuhashi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-03-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -144,6 +144,7 @@ files:
|
|
144
144
|
- spec/multiprocess/thread_processor_spec.rb
|
145
145
|
- spec/queue_spec.rb
|
146
146
|
- spec/rdb_compat_backend_spec.rb
|
147
|
+
- spec/rdb_stress.rb
|
147
148
|
- spec/runner_spec.rb
|
148
149
|
- spec/signal_thread_spec.rb
|
149
150
|
- spec/spec_helper.rb
|
@@ -192,6 +193,7 @@ test_files:
|
|
192
193
|
- spec/multiprocess/thread_processor_spec.rb
|
193
194
|
- spec/queue_spec.rb
|
194
195
|
- spec/rdb_compat_backend_spec.rb
|
196
|
+
- spec/rdb_stress.rb
|
195
197
|
- spec/runner_spec.rb
|
196
198
|
- spec/signal_thread_spec.rb
|
197
199
|
- spec/spec_helper.rb
|