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