perfectqueue 0.8.44.1 → 0.8.45

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: ff48a49e577eab839217d96f1efbe4c3bbb74174
4
- data.tar.gz: d7ddae1782d03ffccacfab5143d7dc08a2f33c1c
3
+ metadata.gz: ca67d530351a8a7e0ff2f950d10248bc23eb5e2e
4
+ data.tar.gz: 5b66bc3219182f721af358e0bce7836ffdca356d
5
5
  SHA512:
6
- metadata.gz: 59b93afc56e199edaee68619425bbba22bc63674f0f4ae4fe2b7dd6c1bde59e2c08cee771a0e60f1020c3d10a139b39e8fa7328c2714bc978d6220dd516d97de
7
- data.tar.gz: 58ba8303acadf21474055d62c9cab9dc4fdc734987f709e7063ba448e7240309eb7b83e63bae0c2ec10767a18561acb14e093443a028788f1eafe30e5ee16063
6
+ metadata.gz: 282b4e05105a5b6fe92cfb3c406b415221784350cb1449e87fe42b481fa7adc0b5d5318c67cc5922e9deafbd244939b2481e76c10ced8b15e981605eacddde35
7
+ data.tar.gz: 7f4a15e96b072fa04144d04f6f92396a730b76de07c0cb45c6f63d9c30d84bbfff88d7cab526a7a895d6265ed74cc897f9d0b8293d3a7c8271d67cba65c4acee
data/ChangeLog CHANGED
@@ -1,6 +1,9 @@
1
- == 2016-06-14 version 0.8.44.1
1
+ == 2015-12-02 version 0.8.45
2
2
 
3
- * Don't delete v0.8.46's finished tasks for migration
3
+ * remove sqlite3 support
4
+ * fix #3
5
+ * add specs
6
+ * fix other trivial bugs
4
7
 
5
8
  == 2015-10-07 version 0.8.44
6
9
 
@@ -19,7 +19,7 @@
19
19
  module PerfectQueue
20
20
  module Application
21
21
 
22
- class UndefinedDecisionError
22
+ class UndefinedDecisionError < StandardError
23
23
  end
24
24
 
25
25
  class Decider
@@ -39,7 +39,7 @@ module PerfectQueue
39
39
  begin
40
40
  m = method(type)
41
41
  rescue NameError
42
- raise UndefinedDecisionError, "Undefined decision #{type} options=#{opt.inspect}"
42
+ raise UndefinedDecisionError, "Undefined decision #{type} options=#{opts.inspect}"
43
43
  end
44
44
  m.call(opts)
45
45
  end
@@ -58,7 +58,7 @@ module PerfectQueue
58
58
  when String, Symbol
59
59
  pattern = /\A#{Regexp.escape(pattern)}\z/
60
60
  else
61
- raise ArguementError, "pattern should be String or Regexp but got #{pattern.class}: #{pattern.inspect}"
61
+ raise ArgumentError, "pattern should be String or Regexp but got #{pattern.class}: #{pattern.inspect}"
62
62
  end
63
63
 
64
64
  @patterns << [pattern, sym]
@@ -21,9 +21,6 @@ module PerfectQueue
21
21
  class RDBCompatBackend
22
22
  include BackendHelper
23
23
 
24
- # backport from v0.8.46
25
- EVENT_HORIZON = 13_0000_0000 # 2011-03-13 07:06:40 UTC
26
-
27
24
  class Token < Struct.new(:key)
28
25
  end
29
26
 
@@ -40,10 +37,7 @@ module PerfectQueue
40
37
  #password = config[:password]
41
38
  #user = config[:user]
42
39
 
43
- case url.split('//',2)[0].to_s
44
- when /sqlite/i
45
- @db = Sequel.connect(url, :max_connections=>1)
46
- when /mysql/i
40
+ if /\Amysql/i =~ url
47
41
  require 'uri'
48
42
 
49
43
  uri = URI.parse(url)
@@ -63,8 +57,20 @@ module PerfectQueue
63
57
  else
64
58
  @use_connection_pooling = !!config[:sslca]
65
59
  end
60
+ @table_lock = lambda {
61
+ locked = nil
62
+ loop do
63
+ @db.fetch("SELECT GET_LOCK('#{@table}', #{LOCK_WAIT_TIMEOUT}) locked") do |row|
64
+ locked = true if row[:locked] == 1
65
+ end
66
+ break if locked
67
+ end
68
+ }
69
+ @table_unlock = lambda {
70
+ @db.run("SELECT RELEASE_LOCK('#{@table}')")
71
+ }
66
72
  else
67
- raise ConfigError, "'sqlite' and 'mysql' are supported"
73
+ raise ConfigError, "only 'mysql' is supported"
68
74
  end
69
75
 
70
76
  @last_time = Time.now.to_i
@@ -79,7 +85,6 @@ module PerfectQueue
79
85
  SELECT id, timeout, data, created_at, resource
80
86
  FROM `#{@table}`
81
87
  WHERE timeout <= ? AND timeout <= ? AND created_at IS NOT NULL
82
- AND #{EVENT_HORIZON} < timeout
83
88
  ORDER BY timeout ASC
84
89
  LIMIT ?
85
90
  SQL
@@ -94,37 +99,11 @@ LEFT JOIN (
94
99
  GROUP BY resource
95
100
  ) AS R ON resource = res
96
101
  WHERE timeout <= ? AND created_at IS NOT NULL AND (max_running-running IS NULL OR max_running-running > 0)
97
- AND #{EVENT_HORIZON} < timeout
98
102
  ORDER BY weight DESC, timeout ASC
99
103
  LIMIT ?
100
104
  SQL
101
105
  end
102
106
 
103
- case url.split('//',2)[0].to_s
104
- when /sqlite/i
105
- # sqlite always locks tables on BEGIN
106
- @table_lock = nil
107
- @table_unlock = nil
108
- when /mysql/i
109
- @table_lock = lambda {
110
- locked = nil
111
- loop do
112
- @db.fetch("SELECT GET_LOCK('#{@table}', #{LOCK_WAIT_TIMEOUT}) locked") do |row|
113
- locked = true if row[:locked] == 1
114
- end
115
- break if locked
116
- end
117
- }
118
- @table_unlock = lambda {
119
- @db.run("SELECT RELEASE_LOCK('#{@table}')")
120
- }
121
- else
122
- @table_lock = lambda {
123
- @db.run("LOCK TABLE `#{@table}`")
124
- }
125
- @table_unlock = nil
126
- end
127
-
128
107
  @prefetch_break_types = config[:prefetch_break_types] || []
129
108
 
130
109
  @cleanup_interval = config[:cleanup_interval] || DEFAULT_DELETE_INTERVAL
@@ -190,6 +169,24 @@ SQL
190
169
  }
191
170
  end
192
171
 
172
+ def compress_data(data, compression)
173
+ if compression == 'gzip'
174
+ require 'zlib'
175
+ require 'stringio'
176
+ io = StringIO.new
177
+ io.set_encoding(Encoding::ASCII_8BIT)
178
+ gz = Zlib::GzipWriter.new(io)
179
+ begin
180
+ gz.write(data)
181
+ ensure
182
+ gz.close
183
+ end
184
+ data = io.string
185
+ data = Sequel::SQL::Blob.new(data)
186
+ end
187
+ data
188
+ end
189
+
193
190
  # => Task
194
191
  def submit(key, type, data, options)
195
192
  now = (options[:now] || Time.now).to_i
@@ -200,23 +197,7 @@ SQL
200
197
  max_running = options[:max_running]
201
198
  data = data ? data.dup : {}
202
199
  data['type'] = type
203
-
204
- d = data.to_json
205
-
206
- if options[:compression] == 'gzip'
207
- require 'zlib'
208
- require 'stringio'
209
- io = StringIO.new
210
- gz = Zlib::GzipWriter.new(io)
211
- begin
212
- gz.write(d)
213
- ensure
214
- gz.close
215
- end
216
- d = io.string
217
- d.force_encoding('ASCII-8BIT') if d.respond_to?(:force_encoding)
218
- d = Sequel::SQL::Blob.new(d)
219
- end
200
+ d = compress_data(data.to_json, options[:compression])
220
201
 
221
202
  connect {
222
203
  begin
@@ -240,7 +221,7 @@ SQL
240
221
 
241
222
  if @cleanup_interval_count <= 0
242
223
  connect_locked {
243
- @db["DELETE FROM `#{@table}` WHERE #{EVENT_HORIZON} < timeout && timeout <= ? AND created_at IS NULL", now].delete
224
+ @db["DELETE FROM `#{@table}` WHERE timeout <= ? AND created_at IS NULL", now].delete
244
225
  @cleanup_interval_count = @cleanup_interval
245
226
  }
246
227
  end
@@ -281,8 +262,6 @@ SQL
281
262
 
282
263
  # => nil
283
264
  def cancel_request(key, options)
284
- now = (options[:now] || Time.now).to_i
285
-
286
265
  # created_at=0 means cancel_requested
287
266
  connect {
288
267
  n = @db["UPDATE `#{@table}` SET created_at=0 WHERE id=? AND created_at IS NOT NULL", key].update
@@ -323,7 +302,7 @@ SQL
323
302
  params = [sql, next_timeout]
324
303
  if data
325
304
  sql << ", data=?"
326
- params << data.to_json
305
+ params << compress_data(data.to_json, options[:compression])
327
306
  end
328
307
  sql << " WHERE id=? AND created_at IS NOT NULL"
329
308
  params << key
@@ -336,8 +315,6 @@ SQL
336
315
  raise PreemptedError, "task key=#{key} does not exist or preempted."
337
316
  elsif row[:created_at] == nil
338
317
  raise PreemptedError, "task key=#{key} preempted."
339
- elsif row[:created_at] <= 0
340
- raise CancelRequestedError, "task key=#{key} is cancel requested."
341
318
  else # row[:timeout] == next_timeout
342
319
  # ok
343
320
  end
@@ -395,6 +372,8 @@ SQL
395
372
  else
396
373
  STDERR.puts err + "\n abort."
397
374
  end
375
+ else
376
+ err = $!
398
377
  end
399
378
 
400
379
  STDERR.puts "disconnects current connection: #{err}"
@@ -411,6 +390,7 @@ SQL
411
390
  GZIP_MAGIC_BYTES = [0x1f, 0x8b].pack('CC')
412
391
 
413
392
  def create_attributes(now, row)
393
+ compression = nil
414
394
  if row[:created_at] === nil
415
395
  created_at = nil # unknown creation time
416
396
  status = TaskStatus::FINISHED
@@ -434,6 +414,7 @@ SQL
434
414
  if d[0, 2] == GZIP_MAGIC_BYTES
435
415
  require 'zlib'
436
416
  require 'stringio'
417
+ compression = 'gzip'
437
418
  gz = Zlib::GzipReader.new(StringIO.new(d))
438
419
  begin
439
420
  d = gz.read
@@ -464,6 +445,7 @@ SQL
464
445
  :max_running => row[:max_running],
465
446
  :message => nil, # not supported
466
447
  :node => nil, # not supported
448
+ :compression => compression,
467
449
  }
468
450
  end
469
451
 
@@ -56,7 +56,7 @@ module PerfectQueue
56
56
  @processors << @processor_class.new(@runner, @processors.size+1, config)
57
57
  end
58
58
  elsif extra < 0
59
- -extra.times do
59
+ (-extra).times do
60
60
  c = @processors.shift
61
61
  c.stop(immediate)
62
62
  c.join
@@ -106,10 +106,7 @@ module PerfectQueue
106
106
  def replace(immediate, command=[$0]+ARGV)
107
107
  return if @replaced_pid
108
108
  stop(immediate)
109
- @replaced_pid = Process.fork do
110
- exec(*command)
111
- exit!(127)
112
- end
109
+ @replaced_pid = Process.spawn(*command)
113
110
  self
114
111
  end
115
112
 
@@ -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}: #{task.inspect}"
124
+ @log.info "acquired task task=#{task.key} id=#{@processor_id}"
125
125
  begin
126
126
  r = @runner.new(task)
127
127
  @tm.set_task(task, r)
@@ -58,6 +58,7 @@ module PerfectQueue
58
58
  class TaskWithMetadata < Task
59
59
  def initialize(client, key, attributes)
60
60
  super(client, key)
61
+ @compression = attributes.delete(:compression)
61
62
  @attributes = attributes
62
63
  end
63
64
 
@@ -93,7 +94,7 @@ module PerfectQueue
93
94
  def update_data!(hash)
94
95
  data = @attributes[:data] || {}
95
96
  merged = data.merge(hash)
96
- heartbeat!(:data => merged)
97
+ heartbeat!(data: merged, compression: compression)
97
98
  @attributes[:data] = merged
98
99
  end
99
100
 
@@ -19,6 +19,7 @@
19
19
  module PerfectQueue
20
20
  module TaskMetadataAccessors
21
21
  attr_reader :attributes
22
+ attr_reader :compression
22
23
 
23
24
  def type
24
25
  @attributes[:type]
@@ -60,10 +61,6 @@ module PerfectQueue
60
61
  status == TaskStatus::FINISHED
61
62
  end
62
63
 
63
- def running?
64
- status == TaskStatus::RUNNING
65
- end
66
-
67
64
  def waiting?
68
65
  status == TaskStatus::WAITING
69
66
  end
@@ -83,6 +80,7 @@ module PerfectQueue
83
80
  def initialize(client, key, attributes)
84
81
  super(client)
85
82
  @key = key
83
+ @compression = attributes.delete(:compression)
86
84
  @attributes = attributes
87
85
  end
88
86
 
@@ -1,3 +1,3 @@
1
1
  module PerfectQueue
2
- VERSION = "0.8.44.1"
2
+ VERSION = "0.8.45"
3
3
  end
@@ -19,8 +19,7 @@ Gem::Specification.new do |gem|
19
19
 
20
20
  gem.add_dependency "sequel", "~> 3.48.0"
21
21
  gem.add_development_dependency "rake", "~> 0.9.2"
22
- gem.add_development_dependency "rspec", "~> 2.10.0"
22
+ gem.add_development_dependency "rspec", "~> 3.3.0"
23
23
  gem.add_development_dependency "simplecov", "~> 0.10.0"
24
- gem.add_development_dependency "sqlite3", "~> 1.3.3"
25
24
  gem.add_development_dependency "mysql2", "~> 0.3.20"
26
25
  end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+
3
+ describe PerfectQueue::Application::Base do
4
+ describe '.decider=' do
5
+ it 'defines .decider which returns the decider' do
6
+ decider_klass = double('decider_klass')
7
+ klass = PerfectQueue::Application::Base
8
+ allow(klass).to receive(:decider).and_call_original
9
+ allow(klass).to receive(:decider=).with(decider_klass).and_call_original
10
+ expect(klass.decider = decider_klass).to eq(decider_klass)
11
+ expect(klass.decider).to eq(decider_klass)
12
+ end
13
+ end
14
+
15
+ describe '.decider' do
16
+ it 'returns DefaultDecider' do
17
+ expect(PerfectQueue::Application::Base.decider).to eq(PerfectQueue::Application::DefaultDecider)
18
+ end
19
+ end
20
+
21
+ describe '#new' do
22
+ let (:task){ double('task') }
23
+ let (:base) { PerfectQueue::Application::Base.new(task) }
24
+ it 'calls super and set decider'do
25
+ expect(base).to be_an_instance_of(PerfectQueue::Application::Base)
26
+ expect(base.instance_variable_get(:@task)).to eq(task)
27
+ expect(base.instance_variable_get(:@decider)).to be_an_instance_of(Application::DefaultDecider)
28
+ end
29
+ end
30
+
31
+ describe '#run' do
32
+ let (:base) { PerfectQueue::Application::Base.new(double('task')) }
33
+ it 'returns nil if before_perform returns false' do
34
+ allow(base).to receive(:before_perform).and_return(false)
35
+ expect(base.run).to be_nil
36
+ end
37
+ it 'returns nil' do
38
+ expect(base).to receive(:before_perform).exactly(:once).and_call_original
39
+ expect(base).to receive(:perform).exactly(:once).and_return(nil)
40
+ expect(base).to receive(:after_perform).exactly(:once).and_call_original
41
+ expect(base.run).to be_nil
42
+ end
43
+ it 'calls unexpected_error_raised on error' do
44
+ allow(base).to receive(:before_perform).exactly(:once).and_call_original
45
+ allow(base).to receive(:perform).exactly(:once) { raise }
46
+ allow(base).to receive(:decide!).with(:unexpected_error_raised, error: kind_of(Exception)).exactly(:once)
47
+ expect(base.run).to be_nil
48
+ end
49
+ end
50
+
51
+ describe '#before_perform' do
52
+ let (:base) { PerfectQueue::Application::Base.new(double('task')) }
53
+ it 'returns true' do
54
+ expect(base.before_perform).to be true
55
+ end
56
+ end
57
+
58
+ describe '#after_perform' do
59
+ let (:base) { PerfectQueue::Application::Base.new(double('task')) }
60
+ it 'returns nil' do
61
+ expect(base.after_perform).to be_nil
62
+ end
63
+ end
64
+
65
+ describe '#decide!' do
66
+ let (:base) do
67
+ decider = double('decider')
68
+ expect(decider).to receive(:decide!).with(:type, :option).exactly(:once)
69
+ decider_klass = double('decider_klass')
70
+ allow(decider_klass).to receive(:new).with(kind_of(PerfectQueue::Application::Base)).and_return(decider)
71
+ klass = PerfectQueue::Application::Base
72
+ allow(klass).to receive(:decider).and_call_original
73
+ allow(klass).to receive(:decider=).with(decider_klass).and_call_original
74
+ klass.decider = decider_klass
75
+ klass.new(double('task'))
76
+ end
77
+ it 'calls decider.decide' do
78
+ expect(base.decide!(:type, :option)).to be_nil
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe PerfectQueue::Application::UndefinedDecisionError do
4
+ it { is_expected.to be_an_instance_of(PerfectQueue::Application::UndefinedDecisionError) }
5
+ it { is_expected.to be_a(Exception) }
6
+ end
7
+
8
+ describe PerfectQueue::Application::Decider do
9
+ describe '#new' do
10
+ let (:decider) { PerfectQueue::Application::Decider.new(nil) }
11
+ it do
12
+ expect(decider).to be_an_instance_of(PerfectQueue::Application::Decider)
13
+ end
14
+ end
15
+
16
+ describe '#queue' do
17
+ let (:queue){ double('queue') }
18
+ let (:decider) do
19
+ base = double('base')
20
+ allow(base).to receive(:queue).exactly(:once).and_return(queue)
21
+ PerfectQueue::Application::Decider.new(base)
22
+ end
23
+ it 'calls @base.queue' do
24
+ expect(decider.queue).to eq(queue)
25
+ end
26
+ end
27
+
28
+ describe '#task' do
29
+ let (:task){ double('task') }
30
+ let (:decider) do
31
+ base = double('base')
32
+ allow(base).to receive(:task).exactly(:once).and_return(task)
33
+ PerfectQueue::Application::Decider.new(base)
34
+ end
35
+ it 'calls @base.task' do
36
+ expect(decider.task).to eq(task)
37
+ end
38
+ end
39
+
40
+ describe '#decide!' do
41
+ let (:decider) { PerfectQueue::Application::Decider.new(nil) }
42
+ it 'calls the specified method' do
43
+ allow(decider).to receive(:foo).exactly(:once).with(72).and_return(42)
44
+ expect(decider.decide!(:foo, 72)).to eq(42)
45
+ end
46
+ it 'raises UndefinedDecisionError on unknown method' do
47
+ expect{ decider.decide!(:foo, 72) }.to raise_error(PerfectQueue::Application::UndefinedDecisionError)
48
+ end
49
+ end
50
+ end
51
+
52
+ describe PerfectQueue::Application::DefaultDecider do
53
+ subject { PerfectQueue::Application::DefaultDecider.new(nil) }
54
+ it { is_expected.to be_a(PerfectQueue::Application::Decider) }
55
+ it { is_expected.to be_an_instance_of(PerfectQueue::Application::DefaultDecider) }
56
+ end