perfectqueue 0.7.32 → 0.8.0

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.
Files changed (52) hide show
  1. data/.gitignore +6 -0
  2. data/ChangeLog +0 -62
  3. data/Gemfile +3 -0
  4. data/README.md +239 -0
  5. data/Rakefile +19 -0
  6. data/lib/perfectqueue.rb +68 -4
  7. data/lib/perfectqueue/application.rb +30 -0
  8. data/lib/perfectqueue/application/base.rb +27 -0
  9. data/lib/perfectqueue/application/dispatch.rb +73 -0
  10. data/lib/perfectqueue/application/router.rb +69 -0
  11. data/lib/perfectqueue/backend.rb +44 -47
  12. data/lib/perfectqueue/backend/rdb_compat.rb +298 -0
  13. data/lib/perfectqueue/blocking_flag.rb +84 -0
  14. data/lib/perfectqueue/client.rb +117 -0
  15. data/lib/perfectqueue/command/perfectqueue.rb +108 -323
  16. data/lib/perfectqueue/daemons_logger.rb +80 -0
  17. data/lib/perfectqueue/engine.rb +85 -123
  18. data/lib/perfectqueue/error.rb +53 -0
  19. data/lib/perfectqueue/model.rb +37 -0
  20. data/lib/perfectqueue/multiprocess.rb +31 -0
  21. data/lib/perfectqueue/multiprocess/child_process.rb +108 -0
  22. data/lib/perfectqueue/multiprocess/child_process_monitor.rb +109 -0
  23. data/lib/perfectqueue/multiprocess/fork_processor.rb +164 -0
  24. data/lib/perfectqueue/multiprocess/thread_processor.rb +123 -0
  25. data/lib/perfectqueue/queue.rb +58 -0
  26. data/lib/perfectqueue/runner.rb +39 -0
  27. data/lib/perfectqueue/signal_queue.rb +112 -0
  28. data/lib/perfectqueue/task.rb +103 -0
  29. data/lib/perfectqueue/task_metadata.rb +98 -0
  30. data/lib/perfectqueue/task_monitor.rb +189 -0
  31. data/lib/perfectqueue/task_status.rb +27 -0
  32. data/lib/perfectqueue/version.rb +1 -3
  33. data/lib/perfectqueue/worker.rb +114 -196
  34. data/perfectqueue.gemspec +24 -0
  35. data/spec/queue_spec.rb +234 -0
  36. data/spec/spec_helper.rb +44 -0
  37. data/spec/worker_spec.rb +81 -0
  38. metadata +93 -40
  39. checksums.yaml +0 -7
  40. data/README.rdoc +0 -224
  41. data/lib/perfectqueue/backend/null.rb +0 -33
  42. data/lib/perfectqueue/backend/rdb.rb +0 -181
  43. data/lib/perfectqueue/backend/simpledb.rb +0 -139
  44. data/test/backend_test.rb +0 -259
  45. data/test/cat.sh +0 -2
  46. data/test/echo.sh +0 -4
  47. data/test/exec_test.rb +0 -61
  48. data/test/fail.sh +0 -2
  49. data/test/huge.sh +0 -2
  50. data/test/stress.rb +0 -99
  51. data/test/success.sh +0 -2
  52. data/test/test_helper.rb +0 -19
@@ -1,33 +0,0 @@
1
-
2
- module PerfectQueue
3
-
4
-
5
- class NullBackend < Backend
6
- def list(&block)
7
- nil
8
- end
9
-
10
- def acquire(timeout, now=Time.now.to_i)
11
- nil
12
- end
13
-
14
- def finish(token, delete_timeout=3600, now=Time.now.to_i)
15
- true
16
- end
17
-
18
- def update(token, timeout)
19
- nil
20
- end
21
-
22
- def cancel(id, delete_timeout=3600, now=Time.now.to_i)
23
- true
24
- end
25
-
26
- def submit(id, data, time=Time.now.to_i, resource=nil, max_running=nil)
27
- true
28
- end
29
- end
30
-
31
-
32
- end
33
-
@@ -1,181 +0,0 @@
1
-
2
- module PerfectQueue
3
-
4
-
5
- class RDBBackend < Backend
6
- DELETE_OFFSET = 10_0000_0000
7
-
8
- def initialize(uri, table, config={})
9
- require 'sequel'
10
- require 'uri'
11
-
12
- @uri = uri
13
- @table = table
14
-
15
- u = URI.parse(@uri)
16
- options = {
17
- max_connections: 1,
18
- user: u.user,
19
- password: u.password,
20
- host: u.host,
21
- port: u.port ? u.port.to_i : 3306
22
- }
23
- options[:sslca] = config[:sslca] if config[:sslca]
24
- db_name = u.path.split('/')[1]
25
- @db = Sequel.mysql2(db_name, options)
26
-
27
- #@last_time = Time.now.to_i
28
- @mutex = Mutex.new
29
- #init_db(@uri.split('//',2)[0])
30
- connect {
31
- # connection test
32
- }
33
- @sql = <<SQL
34
- SELECT id, timeout, data, created_at, resource, max_running/running AS weight
35
- FROM `#{@table}`
36
- LEFT JOIN (
37
- SELECT resource AS res, COUNT(1) AS running
38
- FROM `#{@table}` AS T
39
- WHERE timeout > ? AND created_at IS NOT NULL AND resource IS NOT NULL
40
- GROUP BY resource
41
- ) AS R ON resource = res
42
- WHERE timeout <= ? AND (max_running-running IS NULL OR max_running-running > 0)
43
- ORDER BY weight IS NOT NULL, weight DESC, timeout ASC
44
- LIMIT #{MAX_SELECT_ROW}
45
- SQL
46
- # sqlite doesn't support SELECT ... FOR UPDATE but
47
- # sqlite doesn't need it because the db is not shared
48
- unless @uri.split('//',2)[0].to_s.include?('sqlite')
49
- @sql << 'FOR UPDATE'
50
- end
51
- end
52
-
53
- def create_tables
54
- sql = ''
55
- sql << "CREATE TABLE IF NOT EXISTS `#{@table}` ("
56
- sql << " id VARCHAR(256) NOT NULL,"
57
- sql << " timeout INT NOT NULL,"
58
- sql << " data BLOB NOT NULL,"
59
- sql << " created_at INT,"
60
- sql << " resource VARCHAR(256),"
61
- sql << " max_running INT,"
62
- sql << " PRIMARY KEY (id)"
63
- sql << ");"
64
- # TODO index
65
- connect {
66
- @db.run sql
67
- }
68
- end
69
-
70
- private
71
- def connect(&block)
72
- #now = Time.now.to_i
73
- @mutex.synchronize do
74
- #if now - @last_time > KEEPALIVE
75
- # @db.disconnect
76
- #end
77
- #@last_time = now
78
- retry_count = 0
79
- begin
80
- block.call
81
- rescue
82
- # workaround for "Mysql2::Error: Deadlock found when trying to get lock; try restarting transaction" error
83
- if $!.to_s.include?('try restarting transaction')
84
- err = ([$!] + $!.backtrace.map {|bt| " #{bt}" }).join("\n")
85
- retry_count += 1
86
- if retry_count < MAX_RETRY
87
- STDERR.puts err + "\n retrying."
88
- sleep 0.5
89
- retry
90
- else
91
- STDERR.puts err + "\n abort."
92
- end
93
- end
94
- raise
95
- ensure
96
- @db.disconnect
97
- end
98
- end
99
- end
100
-
101
- public
102
- def list(&block)
103
- @db.fetch("SELECT id, timeout, data, created_at, resource FROM `#{@table}` WHERE created_at IS NOT NULL ORDER BY timeout ASC;") {|row|
104
- yield row[:id], row[:created_at], row[:data], row[:timeout], row[:resource]
105
- }
106
- end
107
-
108
- MAX_SELECT_ROW = 8
109
- #KEEPALIVE = 10
110
- MAX_RETRY = 10
111
-
112
- def acquire(timeout, now=Time.now.to_i)
113
- connect {
114
- while true
115
- rows = 0
116
- @db.transaction do
117
- @db.fetch(@sql, now, now) {|row|
118
- unless row[:created_at]
119
- # finished/canceled task
120
- @db["DELETE FROM `#{@table}` WHERE id=?;", row[:id]].delete
121
-
122
- else
123
- ## optimistic lock is not needed because the row is locked for update
124
- #n = @db["UPDATE `#{@table}` SET timeout=? WHERE id=? AND timeout=?", timeout, row[:id], row[:timeout]].update
125
- n = @db["UPDATE `#{@table}` SET timeout=? WHERE id=?", timeout, row[:id]].update
126
- if n > 0
127
- return row[:id], Task.new(row[:id], row[:created_at], row[:data], row[:resource])
128
- end
129
- end
130
-
131
- rows += 1
132
- }
133
- end
134
- break nil if rows < MAX_SELECT_ROW
135
- end
136
- }
137
- end
138
-
139
- def finish(id, delete_timeout=3600, now=Time.now.to_i)
140
- connect {
141
- 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
142
- return n > 0
143
- }
144
- end
145
-
146
- def update(id, timeout)
147
- connect {
148
- n = @db["UPDATE `#{@table}` SET timeout=? WHERE id=? AND created_at IS NOT NULL;", timeout, id].update
149
- if n <= 0
150
- raise CanceledError, "Task id=#{id} is canceled."
151
- end
152
- return nil
153
- }
154
- end
155
-
156
- def cancel(id, delete_timeout=3600, now=Time.now.to_i)
157
- finish(id, delete_timeout, now)
158
- end
159
-
160
- def submit(id, data, time=Time.now.to_i, resource=nil, max_running=nil)
161
- connect {
162
- begin
163
- data = Sequel::SQL::Blob.new(data)
164
- n = @db["INSERT INTO `#{@table}` (id, timeout, data, created_at, resource, max_running) VALUES (?, ?, ?, ?, ?, ?);", id, time, data, time, resource, max_running].insert
165
- return true
166
- rescue Sequel::DatabaseError => e
167
- # Sequel doesn't provide error classes to distinguish duplicate-entry from other
168
- # errors like connectivity error. This code assumes the driver is mysql2 and
169
- # the error message is "Mysql::ServerError::DupEntry: Duplicate entry"
170
- if /: Duplicate entry/ =~ e.to_s
171
- return nil
172
- end
173
- raise e
174
- end
175
- }
176
- end
177
- end
178
-
179
-
180
- end
181
-
@@ -1,139 +0,0 @@
1
-
2
- module PerfectQueue
3
-
4
-
5
- class SimpleDBBackend < Backend
6
- def initialize(key_id, secret_key, domain)
7
- require 'aws-sdk'
8
- @consistent_read = false
9
-
10
- @db = AWS::SimpleDB.new(
11
- :access_key_id => key_id,
12
- :secret_access_key => secret_key)
13
-
14
- @domain_name = domain
15
- @domain = @db.domains[@domain_name]
16
- unless @domain.exists?
17
- @domain = @db.domains.create(@domain_name)
18
- end
19
- end
20
-
21
- attr_accessor :consistent_read
22
-
23
- def use_consistent_read(b=true)
24
- @consistent_read = b
25
- self
26
- end
27
-
28
- def list(&block)
29
- # TODO support resource limit
30
- @domain.items.select('timeout', 'data', 'created_at',
31
- :where => "created_at != '' AND timeout > '#{int_encode(0)}'",
32
- :order => [:timeout, :asc],
33
- :consistent_read => @consistent_read) {|itemdata|
34
- id = itemdata.name
35
- attrs = itemdata.attributes
36
- next unless attrs['created_at'].first
37
- created_at = int_decode(attrs['created_at'].first)
38
- data = attrs['data'].first
39
- timeout = int_decode(attrs['timeout'].first)
40
- yield id, created_at, data, timeout, nil
41
- }
42
- end
43
-
44
- MAX_SELECT_ROW = 4
45
-
46
- def acquire(timeout, now=Time.now.to_i)
47
- while true
48
- # TODO support resource limit
49
- rows = 0
50
- @domain.items.select('timeout', 'data', 'created_at',
51
- :where => "timeout <= '#{int_encode(now)}'",
52
- :order => [:timeout, :asc],
53
- :consistent_read => @consistent_read,
54
- :limit => MAX_SELECT_ROW) {|itemdata|
55
- begin
56
- id = itemdata.name
57
- attrs = itemdata.attributes
58
- salt = attrs['created_at'].first
59
-
60
- if !salt || salt.empty?
61
- # finished/canceled task
62
- @domain.items[id].delete(:if=>{'created_at'=>''})
63
-
64
- else
65
- created_at = int_decode(salt)
66
- @domain.items[id].attributes.replace('timeout'=>int_encode(timeout),
67
- :if=>{'timeout'=>attrs['timeout'].first})
68
-
69
- data = attrs['data'].first
70
-
71
- return [id,salt], Task.new(id, created_at, data, nil)
72
- end
73
-
74
- rescue AWS::SimpleDB::Errors::ConditionalCheckFailed, AWS::SimpleDB::Errors::AttributeDoesNotExist
75
- end
76
-
77
- rows += 1
78
- }
79
- if rows < MAX_SELECT_ROW
80
- return nil
81
- end
82
- end
83
- end
84
-
85
- def finish(token, delete_timeout=3600, now=Time.now.to_i)
86
- begin
87
- id, salt = *token
88
- @domain.items[id].attributes.replace('timeout'=>int_encode(now+delete_timeout), 'created_at'=>'',
89
- :if=>{'created_at'=>salt})
90
- return true
91
- rescue AWS::SimpleDB::Errors::ConditionalCheckFailed, AWS::SimpleDB::Errors::AttributeDoesNotExist
92
- return false
93
- end
94
- end
95
-
96
- def update(token, timeout)
97
- begin
98
- id, salt = *token
99
- @domain.items[id].attributes.replace('timeout'=>int_encode(timeout),
100
- :if=>{'created_at'=>salt})
101
- rescue AWS::SimpleDB::Errors::ConditionalCheckFailed, AWS::SimpleDB::Errors::AttributeDoesNotExist
102
- raise CanceledError, "Task id=#{id} is canceled."
103
- end
104
- nil
105
- end
106
-
107
- def cancel(id, delete_timeout=3600, now=Time.now.to_i)
108
- salt = @domain.items[id].attributes['created_at'].first
109
- unless salt
110
- return false
111
- end
112
- token = [id,salt]
113
- finish(token, delete_timeout, now)
114
- end
115
-
116
- def submit(id, data, time=Time.now.to_i, resource=nil)
117
- # TODO support resource limit
118
- begin
119
- @domain.items[id].attributes.replace('timeout'=>int_encode(time), 'created_at'=>int_encode(time), 'data'=>data,
120
- :unless=>'timeout')
121
- return true
122
- rescue AWS::SimpleDB::Errors::ConditionalCheckFailed, AWS::SimpleDB::Errors::ExistsAndExpectedValue
123
- return nil
124
- end
125
- end
126
-
127
- private
128
- def int_encode(num)
129
- "%08x" % num
130
- end
131
-
132
- def int_decode(str)
133
- str.to_i(16)
134
- end
135
- end
136
-
137
-
138
- end
139
-
data/test/backend_test.rb DELETED
@@ -1,259 +0,0 @@
1
- require File.dirname(__FILE__)+'/test_helper'
2
-
3
- class BackendTest < Test::Unit::TestCase
4
- TIMEOUT = 10
5
- DB_PATH = File.dirname(__FILE__)+'/test.db'
6
- DB_URI = "sqlite://#{DB_PATH}"
7
-
8
- def clean_backend
9
- @key_prefix = "test-#{"%08x"%rand(2**32)}-"
10
- db = open_backend
11
- db.list {|id,created_at,data,timeout|
12
- db.cancel(id)
13
- }
14
- FileUtils.rm_f DB_PATH
15
- end
16
-
17
- def open_backend
18
- #PerfectQueue::SimpleDBBackend.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY'], 'perfectqueue-test-1').use_consistent_read
19
- db = PerfectQueue::RDBBackend.new(DB_URI, "perfectdb_test")
20
- db.create_tables
21
- db
22
- end
23
-
24
- it 'acquire' do
25
- clean_backend
26
-
27
- db1 = open_backend
28
- db2 = open_backend
29
- db3 = open_backend
30
-
31
- time = Time.now.to_i
32
-
33
- ok = db1.submit(@key_prefix+'test1', 'data1', time)
34
- assert_equal true, ok
35
-
36
- token, task = db2.acquire(time+TIMEOUT)
37
- assert_not_equal nil, task
38
- assert_equal @key_prefix+'test1', task.id
39
- assert_equal time, task.created_at
40
- assert_equal 'data1', task.data
41
-
42
- token_, task_ = db3.acquire(time+TIMEOUT)
43
- assert_equal nil, token_
44
- end
45
-
46
- it 'finish' do
47
- clean_backend
48
-
49
- db1 = open_backend
50
- db2 = open_backend
51
-
52
- time = Time.now.to_i
53
-
54
- ok = db1.submit(@key_prefix+'test1', 'data1', time)
55
- assert_equal true, ok
56
-
57
- token, task = db1.acquire(time+TIMEOUT)
58
- assert_not_equal nil, task
59
-
60
- ok = db1.finish(token)
61
- assert_equal true, ok
62
-
63
- token_, task_ = db2.acquire(time+TIMEOUT)
64
- assert_equal nil, token_
65
-
66
- ok = db1.finish(token)
67
- assert_equal false, ok
68
- end
69
-
70
- it 'canceled' do
71
- clean_backend
72
-
73
- db1 = open_backend
74
- db2 = open_backend
75
-
76
- time = Time.now.to_i
77
-
78
- ok = db1.submit(@key_prefix+'test1', 'data1', time)
79
- assert_equal true, ok
80
-
81
- token, task = db1.acquire(time+TIMEOUT)
82
- assert_not_equal nil, task
83
-
84
- ok = db1.cancel(task.id)
85
- assert_equal true, ok
86
-
87
- token_, task_ = db2.acquire(time+TIMEOUT)
88
- assert_equal nil, token_
89
-
90
- assert_raise(PerfectQueue::CanceledError) do
91
- db1.update(token, time+TIMEOUT)
92
- end
93
-
94
- ok = db1.cancel(task.id)
95
- assert_equal false, ok
96
- end
97
-
98
- it 'order' do
99
- clean_backend
100
-
101
- db1 = open_backend
102
- db2 = open_backend
103
- db3 = open_backend
104
-
105
- time = Time.now.to_i
106
-
107
- ok = db1.submit(@key_prefix+'test1', 'data1', time)
108
- assert_equal true, ok
109
-
110
- ok = db1.submit(@key_prefix+'test2', 'data2', time-1)
111
- assert_equal true, ok
112
-
113
- ok = db1.submit(@key_prefix+'test3', 'data3', time+1)
114
- assert_equal true, ok
115
-
116
- token, task = db2.acquire(time+TIMEOUT, time+1)
117
- assert_not_equal nil, task
118
- assert_equal @key_prefix+'test2', task.id
119
- assert_equal time-1, task.created_at
120
- assert_equal 'data2', task.data
121
-
122
- token, task = db2.acquire(time+TIMEOUT, time+1)
123
- assert_not_equal nil, task
124
- assert_equal @key_prefix+'test1', task.id
125
- assert_equal time, task.created_at
126
- assert_equal 'data1', task.data
127
-
128
- token, task = db2.acquire(time+TIMEOUT, time+1)
129
- assert_not_equal nil, task
130
- assert_equal @key_prefix+'test3', task.id
131
- assert_equal time+1, task.created_at
132
- assert_equal 'data3', task.data
133
- end
134
-
135
- it 'timeout' do
136
- clean_backend
137
-
138
- db1 = open_backend
139
- db2 = open_backend
140
-
141
- time = Time.now.to_i
142
-
143
- ok = db1.submit(@key_prefix+'test1', 'data1', time)
144
- assert_equal true, ok
145
-
146
- token, task = db1.acquire(time+TIMEOUT)
147
- assert_not_equal nil, task
148
- assert_equal @key_prefix+'test1', task.id
149
- assert_equal time, task.created_at
150
- assert_equal 'data1', task.data
151
-
152
- token, task = db2.acquire(time+TIMEOUT*2, time+TIMEOUT)
153
- assert_not_equal nil, task
154
- assert_equal @key_prefix+'test1', task.id
155
- assert_equal time, task.created_at
156
- assert_equal 'data1', task.data
157
- end
158
-
159
- it 'extend' do
160
- clean_backend
161
-
162
- db1 = open_backend
163
- db2 = open_backend
164
-
165
- time = Time.now.to_i
166
-
167
- ok = db1.submit(@key_prefix+'test1', 'data1', time)
168
- assert_equal true, ok
169
-
170
- token, task = db1.acquire(time+TIMEOUT)
171
- assert_not_equal nil, task
172
-
173
- assert_nothing_raised do
174
- db1.update(token, time+TIMEOUT+1)
175
- end
176
-
177
- token_, task_ = db2.acquire(time+TIMEOUT, time)
178
- assert_equal nil, token_
179
-
180
- token, task = db2.acquire(time+TIMEOUT*2, time+TIMEOUT+1)
181
- assert_not_equal nil, task
182
- assert_equal @key_prefix+'test1', task.id
183
- assert_equal time, task.created_at
184
- assert_equal 'data1', task.data
185
- end
186
-
187
- it 'release' do
188
- clean_backend
189
-
190
- db1 = open_backend
191
- db2 = open_backend
192
-
193
- time = Time.now.to_i
194
-
195
- ok = db1.submit(@key_prefix+'test1', 'data1', time)
196
- assert_equal true, ok
197
-
198
- token, task = db1.acquire(time+TIMEOUT)
199
- assert_not_equal nil, task
200
-
201
- assert_nothing_raised do
202
- db1.update(token, time+TIMEOUT+1)
203
- end
204
-
205
- token_, task_ = db2.acquire(time+TIMEOUT, time)
206
- assert_equal nil, token_
207
-
208
- assert_nothing_raised do
209
- db1.update(token, time)
210
- end
211
-
212
- token, task = db2.acquire(time+TIMEOUT, time)
213
- assert_not_equal nil, task
214
- assert_equal @key_prefix+'test1', task.id
215
- assert_equal time, task.created_at
216
- assert_equal 'data1', task.data
217
- end
218
-
219
- it 'resource limit' do
220
- clean_backend
221
-
222
- db1 = open_backend
223
-
224
- time = Time.now.to_i
225
-
226
- 3.times do |i|
227
- ok = db1.submit(@key_prefix+'test'+i.to_s, 'data1', time-(i+1), 'user1', 2)
228
- assert_equal true, ok
229
- end
230
- ok = db1.submit(@key_prefix+'test5', 'data2', time, 'user2', 2)
231
- assert_equal true, ok
232
-
233
- token_1 = nil
234
- task_1 = nil
235
-
236
- token_1, task_1 = db1.acquire(time+TIMEOUT, time)
237
- assert_not_equal nil, task_1
238
- assert_equal "user1", task_1.resource
239
-
240
- token, task = db1.acquire(time+TIMEOUT, time)
241
- assert_not_equal nil, task
242
- assert_equal "user2", task.resource
243
-
244
- token, task = db1.acquire(time+TIMEOUT, time)
245
- assert_not_equal nil, task
246
- assert_equal "user1", task.resource
247
-
248
- token, task = db1.acquire(time+TIMEOUT, time)
249
- assert_equal nil, task
250
-
251
- ok = db1.finish(token_1)
252
- assert_equal true, ok
253
-
254
- token, task = db1.acquire(time+TIMEOUT, time)
255
- assert_not_equal nil, task
256
- assert_equal "user1", task.resource
257
- end
258
- end
259
-