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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ca67d530351a8a7e0ff2f950d10248bc23eb5e2e
4
- data.tar.gz: 5b66bc3219182f721af358e0bce7836ffdca356d
3
+ metadata.gz: e091df346ddc80d849184494788572dbbe07919e
4
+ data.tar.gz: 40533e8a9adf12acc69d85273675ba9043743c8b
5
5
  SHA512:
6
- metadata.gz: 282b4e05105a5b6fe92cfb3c406b415221784350cb1449e87fe42b481fa7adc0b5d5318c67cc5922e9deafbd244939b2481e76c10ced8b15e981605eacddde35
7
- data.tar.gz: 7f4a15e96b072fa04144d04f6f92396a730b76de07c0cb45c6f63d9c30d84bbfff88d7cab526a7a895d6265ed74cc897f9d0b8293d3a7c8271d67cba65c4acee
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
- #password = config[:password]
38
- #user = config[:user]
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("SELECT RELEASE_LOCK('#{@table}')")
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 timeout <= ? AND timeout <= ? AND created_at IS NOT NULL
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 timeout <= ? AND created_at IS NOT NULL AND (max_running-running IS NULL OR max_running-running > 0)
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
- connect_locked {
224
- @db["DELETE FROM `#{@table}` WHERE timeout <= ? AND created_at IS NULL", now].delete
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
- # TODO table lock doesn't work. error?
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
- @db.transaction do
337
- if @table_lock
338
- @table_lock.call
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)
@@ -1,3 +1,3 @@
1
1
  module PerfectQueue
2
- VERSION = "0.8.45"
2
+ VERSION = "0.8.46"
3
3
  end
@@ -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: 'mysql://root:@localhost/perfectqueue_test', table: table} }
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: 'mysql://root:@localhost/perfectqueue_test', table: table, use_connection_pooling: true}
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: 'mysql://root:@localhost/perfectqueue_test', table: table, disable_resource_limit: true}
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: 'mysql://root:@localhost/perfectqueue_test', table: table, use_connection_pooling: true}
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 }
@@ -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
@@ -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: 'mysql://root:@localhost/perfectqueue_test', table: 'test_queues', type: 'rdb_compat'} }
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.45
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: 2015-12-02 00:00:00.000000000 Z
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