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 +4 -4
- data/ChangeLog +7 -0
- data/README.md +1 -1
- data/lib/perfectqueue/backend/null.rb +27 -0
- data/lib/perfectqueue/backend/rdb.rb +82 -0
- data/lib/perfectqueue/backend/rdb_compat.rb +3 -5
- data/lib/perfectqueue/version.rb +1 -1
- data/spec/null_backend_spec.rb +30 -0
- data/spec/rdb_backend_spec.rb +69 -0
- data/spec/rdb_compat_backend_spec.rb +1 -1
- metadata +10 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 61295f3241d66b1d9f27aac68294e32344b09611
|
4
|
+
data.tar.gz: 36371652be7e64c3d5c1b79396c37ff8f5e5344c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2461e220ce8b2f45308db2f377b0816718c4222bc70bcdefec5b6c6fe2df0b3938b7e5d5ff87ad2888b7f20133c91d132a333aa9c0578928a231cb9e8f72b312
|
7
|
+
data.tar.gz: ba6542077acbf7e914c22a90afbcd54833d6e06e42781e8d62300b8d74cda1dfdf29eb5f7bfcb90f3788547828bc79c9cf61e74676127f29b879b2b00f8878d9
|
data/ChangeLog
CHANGED
data/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
[](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
|
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::
|
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
|
-
|
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
|
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
|
}
|
data/lib/perfectqueue/version.rb
CHANGED
@@ -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.
|
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-
|
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.
|
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
|