perfectqueue 0.8.45 → 0.8.46
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.
- 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
|