perfectqueue 0.8.48 → 0.8.49

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: 56f251e9261948702f9f058179343788c08ea08e
4
- data.tar.gz: 4bd8336b27bfc2097a22581120e872fe621d4c53
3
+ metadata.gz: 61295f3241d66b1d9f27aac68294e32344b09611
4
+ data.tar.gz: 36371652be7e64c3d5c1b79396c37ff8f5e5344c
5
5
  SHA512:
6
- metadata.gz: 0a826da88811b6f511b4e4b0a6bdab9255a0a792848874b761fb9c2b6014fb3ac32f1c2a8231c8bdd54dfec82bb37fbd46b87664d392f829fb9fadfcb4388d3e
7
- data.tar.gz: 4591e5a7f253502dafb1687ce57d572fa8b7742b2482be6938d41ce501d9e42f44cc4a2ca3e9a13a4a7de6f50f3518831b2bc9ba60bbac5ec29930ea67c36602
6
+ metadata.gz: 2461e220ce8b2f45308db2f377b0816718c4222bc70bcdefec5b6c6fe2df0b3938b7e5d5ff87ad2888b7f20133c91d132a333aa9c0578928a231cb9e8f72b312
7
+ data.tar.gz: ba6542077acbf7e914c22a90afbcd54833d6e06e42781e8d62300b8d74cda1dfdf29eb5f7bfcb90f3788547828bc79c9cf61e74676127f29b879b2b00f8878d9
data/ChangeLog CHANGED
@@ -1,3 +1,10 @@
1
+ == 2016-08-02 version 0.8.49
2
+
3
+ * Revert v0.8.44 migration path (#38)
4
+ * v0.8.48's timeout express taks's life (#39)
5
+ * Use Sequel::UniqueConstraintViolation (#40)
6
+ * Merge v07 branch (#45)
7
+
1
8
  == 2016-07-07 version 0.8.48
2
9
 
3
10
  * Drop 2.0.0 support #33
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![Coverage Status](https://coveralls.io/repos/treasure-data/perfectqueue/badge.svg?branch=master&service=github)](https://coveralls.io/github/treasure-data/perfectqueue?branch=master)
5
5
 
6
6
  PerfectQueue is a highly available distributed queue built on top of RDBMS.
7
- PerfectQueue provides similar API to Amazon SQS, while PerfectQueue focues on reliability and flexible scheduling rather than scalability.
7
+ PerfectQueue provides similar API to Amazon SQS, while PerfectQueue focuses on reliability and flexible scheduling rather than scalability.
8
8
 
9
9
  PerfectQueue introduces following concepts:
10
10
 
@@ -0,0 +1,27 @@
1
+ module PerfectQueue::Backend
2
+ class NullBackend
3
+ def list(&block)
4
+ nil
5
+ end
6
+
7
+ def acquire(timeout, now=Time.now.to_i)
8
+ nil
9
+ end
10
+
11
+ def finish(token, delete_timeout=3600, now=Time.now.to_i)
12
+ true
13
+ end
14
+
15
+ def update(token, timeout)
16
+ nil
17
+ end
18
+
19
+ def cancel(id, delete_timeout=3600, now=Time.now.to_i)
20
+ true
21
+ end
22
+
23
+ def submit(id, data, time=Time.now.to_i, resource=nil, max_running=nil)
24
+ true
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,82 @@
1
+ require 'sequel'
2
+ require 'uri'
3
+ require_relative 'rdb_compat'
4
+
5
+ module PerfectQueue::Backend
6
+ class RDBBackend
7
+ MAX_RETRY = ::PerfectQueue::Backend::RDBCompatBackend::MAX_RETRY
8
+ DELETE_OFFSET = ::PerfectQueue::Backend::RDBCompatBackend::DELETE_OFFSET
9
+ class Token < Struct.new(:key)
10
+ end
11
+
12
+ def initialize(uri, table, config={})
13
+ @uri = uri
14
+ @table = table
15
+
16
+ u = URI.parse(@uri)
17
+ options = {
18
+ max_connections: 1,
19
+ user: u.user,
20
+ password: u.password,
21
+ host: u.host,
22
+ port: u.port ? u.port.to_i : 3306
23
+ }
24
+ options[:sslca] = config[:sslca] if config[:sslca]
25
+ db_name = u.path.split('/')[1]
26
+ @db = Sequel.mysql2(db_name, options)
27
+
28
+ @mutex = Mutex.new
29
+ connect {
30
+ # connection test
31
+ }
32
+ end
33
+
34
+ attr_reader :db
35
+
36
+ def submit(id, data, time=Process.clock_gettime(Process::CLOCK_REALTIME, :second), resource=nil, max_running=nil)
37
+ connect {
38
+ begin
39
+ data = Sequel::SQL::Blob.new(data)
40
+ @db.sql_log_level = :debug
41
+ n = @db["INSERT INTO `#{@table}` (id, timeout, data, created_at, resource, max_running) VALUES (?, ?, ?, ?, ?, ?);", id, time, data, time, resource, max_running].insert
42
+ return true
43
+ rescue Sequel::UniqueConstraintViolation => e
44
+ return nil
45
+ end
46
+ }
47
+ end
48
+
49
+ def cancel(id, delete_timeout=3600, now=Process.clock_gettime(Process::CLOCK_REALTIME, :second))
50
+ connect {
51
+ n = @db["UPDATE `#{@table}` SET timeout=?, created_at=NULL, resource=NULL WHERE id=? AND created_at IS NOT NULL;", now+delete_timeout-DELETE_OFFSET, id].update
52
+ return n > 0
53
+ }
54
+ end
55
+
56
+ private
57
+ def connect(&block)
58
+ @mutex.synchronize do
59
+ retry_count = 0
60
+ begin
61
+ block.call
62
+ rescue
63
+ # workaround for "Mysql2::Error: Deadlock found when trying to get lock; try restarting transaction" error
64
+ if $!.to_s.include?('try restarting transaction')
65
+ err = ([$!] + $!.backtrace.map {|bt| " #{bt}" }).join("\n")
66
+ retry_count += 1
67
+ if retry_count < MAX_RETRY
68
+ STDERR.puts err + "\n retrying."
69
+ sleep 0.5
70
+ retry
71
+ else
72
+ STDERR.puts err + "\n abort."
73
+ end
74
+ end
75
+ raise
76
+ ensure
77
+ @db.disconnect
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -205,7 +205,7 @@ SQL
205
205
  key, run_at, d, now, user, max_running
206
206
  ].insert
207
207
  return Task.new(@client, key)
208
- rescue Sequel::DatabaseError
208
+ rescue Sequel::UniqueConstraintViolation
209
209
  raise IdempotentAlreadyExistsError, "task key=#{key} already exists"
210
210
  end
211
211
  }
@@ -219,11 +219,9 @@ SQL
219
219
  t0 = nil
220
220
 
221
221
  if @cleanup_interval_count <= 0
222
- delete_timeout = now - DELETE_OFFSET
223
- connect_locked {
222
+ connect { # TODO: HERE should be still connect_locked ?
224
223
  t0=Process.clock_gettime(Process::CLOCK_MONOTONIC)
225
- @db["DELETE FROM `#{@table}` WHERE #{EVENT_HORIZON} < timeout && timeout <= ? AND created_at IS NULL", now].delete
226
- @db["DELETE FROM `#{@table}` WHERE timeout <= ? AND created_at IS NULL", delete_timeout].delete
224
+ @db["DELETE FROM `#{@table}` WHERE timeout <= ?", now-DELETE_OFFSET].delete
227
225
  @cleanup_interval_count = @cleanup_interval
228
226
  STDERR.puts"PQ:delete from #{@table}:%6f sec" % [Process.clock_gettime(Process::CLOCK_MONOTONIC)-t0]
229
227
  }
@@ -1,3 +1,3 @@
1
1
  module PerfectQueue
2
- VERSION = "0.8.48"
2
+ VERSION = "0.8.49"
3
3
  end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+ require 'perfectqueue/backend/null'
3
+
4
+ describe Backend::NullBackend do
5
+ let (:backend){ Backend::NullBackend.new }
6
+ describe '#list' do
7
+ subject { backend.list{} }
8
+ it { is_expected.to be_nil }
9
+ end
10
+ describe '#acquire' do
11
+ subject { backend.acquire(double('timeout')) }
12
+ it { is_expected.to be_nil }
13
+ end
14
+ describe '#finish' do
15
+ subject { backend.finish(double('token')) }
16
+ it { is_expected.to be true }
17
+ end
18
+ describe '#update' do
19
+ subject { backend.update(double('token'), double('timeout')) }
20
+ it { is_expected.to be_nil }
21
+ end
22
+ describe '#cancel' do
23
+ subject { backend.cancel(double('id')) }
24
+ it { is_expected.to be true }
25
+ end
26
+ describe '#submit' do
27
+ subject { backend.submit(double('id'), double('data')) }
28
+ it { is_expected.to be true }
29
+ end
30
+ end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+ require 'perfectqueue/backend/rdb'
3
+
4
+ describe Backend::RDBBackend do
5
+ let (:now){ Time.now.to_i }
6
+ let (:uri){ 'mysql2://root:@localhost/perfectqueue_test' }
7
+ let (:table){ 'test_queues' }
8
+ let (:db) do
9
+ d = Backend::RDBCompatBackend.new(double, url: uri, table: table)
10
+ s = d.db
11
+ s.tables.each{|t| s.drop_table(t) }
12
+ d.init_database({})
13
+ Backend::RDBBackend.new(uri, table)
14
+ end
15
+
16
+ context '.new' do
17
+ it 'supports mysql' do
18
+ expect(Backend::RDBBackend.new(uri, table)).to be_an_instance_of(Backend::RDBBackend)
19
+ end
20
+ end
21
+
22
+ context '#submit' do
23
+ it 'adds task' do
24
+ db.submit('key', '{"foo":"bar"}')
25
+ row = db.db.fetch("SELECT * FROM `#{table}` WHERE id=? LIMIT 1", 'key').first
26
+ expect(row[:created_at]).not_to be_nil
27
+ expect(row[:data]).to eq('{"foo":"bar"}')
28
+ end
29
+ end
30
+
31
+ context '#cancel' do
32
+ let (:key){ 'key' }
33
+ context 'have the task' do
34
+ before do
35
+ db.submit(key, '{}')
36
+ end
37
+ it 'returns true' do
38
+ expect(db.cancel(key)).to be true
39
+ row = db.db.fetch("SELECT created_at FROM `#{table}` WHERE id=? LIMIT 1", key).first
40
+ expect(row[:created_at]).to be_nil
41
+ end
42
+ end
43
+ context 'already canceled' do
44
+ it 'returns false' do
45
+ expect(db.cancel(key)).to be false
46
+ end
47
+ end
48
+ end
49
+
50
+ context '#connect' do
51
+ context 'normal' do
52
+ it 'returns nil' do
53
+ expect(db.__send__(:connect){ }).to be_nil
54
+ end
55
+ end
56
+ context 'error' do
57
+ it 'returns block result' do
58
+ expect(RuntimeError).to receive(:new).exactly(Backend::RDBBackend::MAX_RETRY).and_call_original
59
+ allow(STDERR).to receive(:puts)
60
+ allow(db).to receive(:sleep)
61
+ expect do
62
+ db.__send__(:connect) do
63
+ raise RuntimeError.new('try restarting transaction')
64
+ end
65
+ end.to raise_error(RuntimeError)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -208,7 +208,7 @@ describe Backend::RDBCompatBackend do
208
208
  db.list({}) do |task|
209
209
  expect(task.timeout).to eq now0.to_time
210
210
  end
211
- ary = db.acquire(alive_time, max_acquire, {})
211
+ ary = db.acquire(alive_time, max_acquire, {now: now})
212
212
  expect(ary).to be_an_instance_of(Array)
213
213
  expect(ary.size).to eq(3)
214
214
  expect(ary[0]).to be_an_instance_of(AcquiredTask)
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.48
4
+ version: 0.8.49
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sadayuki Furuhashi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-07 00:00:00.000000000 Z
11
+ date: 2016-08-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
@@ -106,6 +106,8 @@ files:
106
106
  - lib/perfectqueue/application/dispatch.rb
107
107
  - lib/perfectqueue/application/router.rb
108
108
  - lib/perfectqueue/backend.rb
109
+ - lib/perfectqueue/backend/null.rb
110
+ - lib/perfectqueue/backend/rdb.rb
109
111
  - lib/perfectqueue/backend/rdb_compat.rb
110
112
  - lib/perfectqueue/blocking_flag.rb
111
113
  - lib/perfectqueue/client.rb
@@ -142,7 +144,9 @@ files:
142
144
  - spec/multiprocess/child_process_spec.rb
143
145
  - spec/multiprocess/fork_processor_spec.rb
144
146
  - spec/multiprocess/thread_processor_spec.rb
147
+ - spec/null_backend_spec.rb
145
148
  - spec/queue_spec.rb
149
+ - spec/rdb_backend_spec.rb
146
150
  - spec/rdb_compat_backend_spec.rb
147
151
  - spec/rdb_stress.rb
148
152
  - spec/runner_spec.rb
@@ -173,7 +177,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
173
177
  version: '0'
174
178
  requirements: []
175
179
  rubyforge_project:
176
- rubygems_version: 2.4.5.1
180
+ rubygems_version: 2.5.1
177
181
  signing_key:
178
182
  specification_version: 4
179
183
  summary: Highly available distributed cron built on RDBMS
@@ -191,7 +195,9 @@ test_files:
191
195
  - spec/multiprocess/child_process_spec.rb
192
196
  - spec/multiprocess/fork_processor_spec.rb
193
197
  - spec/multiprocess/thread_processor_spec.rb
198
+ - spec/null_backend_spec.rb
194
199
  - spec/queue_spec.rb
200
+ - spec/rdb_backend_spec.rb
195
201
  - spec/rdb_compat_backend_spec.rb
196
202
  - spec/rdb_stress.rb
197
203
  - spec/runner_spec.rb
@@ -202,3 +208,4 @@ test_files:
202
208
  - spec/task_metadata_spec.rb
203
209
  - spec/task_monitor_spec.rb
204
210
  - spec/task_spec.rb
211
+ has_rdoc: false