perfectqueue 0.8.48 → 0.8.49

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