disc 0.0.27 → 0.0.28
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +75 -13
- data/bin/disc +1 -0
- data/examples/echoer.rb +1 -1
- data/examples/failer.rb +1 -1
- data/examples/greeter.rb +1 -1
- data/examples/returner.rb +7 -0
- data/lib/disc/errors.rb +6 -0
- data/lib/disc/job.rb +2 -33
- data/lib/disc/testing.rb +51 -4
- data/lib/disc/version.rb +1 -1
- data/lib/disc/worker.rb +9 -5
- data/lib/disc.rb +40 -2
- data/test/disc_test.rb +42 -181
- data/test/job_test.rb +122 -0
- data/test/process_test.rb +59 -0
- data/test/testing_test.rb +45 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5afabb650aa27d3b020674c300b2c2f198da7562
|
4
|
+
data.tar.gz: 309002ddceb18dbbae790371d6761330044591ef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 73c4688d1d3363c68f4927bfc9096efc1ac353371d1a4f0a67bcf22c48e5bd1f7e9d57f2771d2dbb831340f939531757d160797d0915753a41ef1e11ed9ab2cb
|
7
|
+
data.tar.gz: c4fa2f21bb5d56b00c658aa72c3e66f307b5c86a77af9f9e9ef917794fd63faa14a2edb16d60a7bd792a78ecdc8cfc29d450a58dea8040ac9c5debd1aade71be
|
data/README.md
CHANGED
@@ -23,7 +23,7 @@ Disc fills the gap between your Ruby service objects and [antirez](http://antire
|
|
23
23
|
include Disc::Job
|
24
24
|
disc queue: 'urgent'
|
25
25
|
|
26
|
-
def perform(type)
|
26
|
+
def self.perform(type)
|
27
27
|
# perform rather lengthy operations here.
|
28
28
|
end
|
29
29
|
end
|
@@ -40,7 +40,8 @@ Disc fills the gap between your Ruby service objects and [antirez](http://antire
|
|
40
40
|
|
41
41
|
```ruby
|
42
42
|
# disc_init.rb
|
43
|
-
|
43
|
+
# Require here anything that your application needs to run,
|
44
|
+
# like ORMs and your models, database configuration, etc.
|
44
45
|
Dir['./jobs/**/*.rb'].each { |job| require job }
|
45
46
|
```
|
46
47
|
|
@@ -79,7 +80,7 @@ Signature documentation follows:
|
|
79
80
|
## Parameters:
|
80
81
|
#
|
81
82
|
## `arguments` - an optional array of arguments with which to execute
|
82
|
-
# the job's
|
83
|
+
# the job's `self.perform` method.
|
83
84
|
#
|
84
85
|
# `at` - an optional named parameter specifying a moment in the
|
85
86
|
# future in which to run the job, must respond to
|
@@ -117,7 +118,7 @@ Signature documentation follows:
|
|
117
118
|
### CAVEATS
|
118
119
|
#
|
119
120
|
## For convenience, any object can be passed as the `arguments` parameter,
|
120
|
-
# `Array
|
121
|
+
# `Array()` will be used internally to preserve the array structure.
|
121
122
|
#
|
122
123
|
## The `arguments` parameter is serialized for storage using `Disc.serialize`
|
123
124
|
# and Disc workers picking it up use `Disc.deserialize` on it, both methods
|
@@ -127,7 +128,7 @@ Signature documentation follows:
|
|
127
128
|
|
128
129
|
You can see [Disque's ADDJOB documentation](https://github.com/antirez/disque#addjob-queue_name-job-ms-timeout-replicate-count-delay-sec-retry-sec-ttl-sec-maxlen-count-async) for more details
|
129
130
|
|
130
|
-
When a Disc worker process is assigned a job, it will create a new intance of the job's class and execute the
|
131
|
+
When a Disc worker process is assigned a job, it will create a new intance of the job's class and execute the `self.perform` method with whatever arguments were previously passed to `#enqueue`.
|
131
132
|
|
132
133
|
Example:
|
133
134
|
|
@@ -136,7 +137,7 @@ class ComplexJob
|
|
136
137
|
include Disc::Job
|
137
138
|
disc queue: 'urgent'
|
138
139
|
|
139
|
-
def perform(first_parameter, second_parameter)
|
140
|
+
def self.perform(first_parameter, second_parameter)
|
140
141
|
# do things...
|
141
142
|
end
|
142
143
|
end
|
@@ -145,6 +146,18 @@ end
|
|
145
146
|
ComplexJob.enqueue(['first argument', { second: 'argument' }])
|
146
147
|
```
|
147
148
|
|
149
|
+
### Job Serialization
|
150
|
+
|
151
|
+
Job information (their arguments, and class) need to be serialized in order to be stored
|
152
|
+
in Disque, to this end Disc uses the `Disc.serialize` and `Disc.deserialize` methods.
|
153
|
+
|
154
|
+
By default, these methods use by default the Ruby standard library json implementation in order to serialize and deserialize job data, this has a few implications:
|
155
|
+
|
156
|
+
1. Arguments passed to a job's `#enqueue` method need to be serializable by `Disc.serialize` and parsed back by `Disc.deserialize`, so by default you can't pass complex Ruby objects like a `user` model, instead, pass `user.id`, and use that from your job code.
|
157
|
+
|
158
|
+
2. You can override `Disc.serialize` and `Disc.deserialize` to use a different JSON implementation, or MessagePack, or whatever else you wish.
|
159
|
+
|
160
|
+
|
148
161
|
## Settings
|
149
162
|
|
150
163
|
Disc takes its configuration from environment variables.
|
@@ -173,7 +186,7 @@ end
|
|
173
186
|
Dir["./jobs/**/*.rb"].each { |job| require job }
|
174
187
|
```
|
175
188
|
|
176
|
-
|
189
|
+
### Job Definition
|
177
190
|
|
178
191
|
The error handler function gets the data of the current job as a Hash, that has the following schema.
|
179
192
|
|
@@ -182,7 +195,60 @@ The error handler function gets the data of the current job as a Hash, that has
|
|
182
195
|
| `'class'` | (String) The Job class. |
|
183
196
|
| `'arguments'` | (Array) The arguments passed to perform. |
|
184
197
|
| `'queue'` | (String) The queue from which this job was picked up. |
|
185
|
-
| `'
|
198
|
+
| `'disque_id'` | (String) Disque's job ID. |
|
199
|
+
|
200
|
+
|
201
|
+
## Testing modes
|
202
|
+
|
203
|
+
Disc includes a testing mode, so you can run your test suite without a need to run a Disque server.
|
204
|
+
|
205
|
+
### Enqueue mode
|
206
|
+
|
207
|
+
By default, Disc places your jobs in an in-memory hash, with each queue being a key in the hash and values being an array.
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
require 'disc'
|
211
|
+
require 'disc/testing'
|
212
|
+
|
213
|
+
require_relative 'examples/returner'
|
214
|
+
Disc.enqueue! #=> This is the default mode for disc/testing so you don't need to specify it,
|
215
|
+
# you can use this method to go back to the enqueue mode if you switch it.
|
216
|
+
|
217
|
+
|
218
|
+
Returner.enqueue('test argument')
|
219
|
+
|
220
|
+
Disc.queues
|
221
|
+
#=> {"default"=>[{:arguments=>["test argument"], :class=>"Returner", :options=>{}}]}
|
222
|
+
|
223
|
+
Returner.enqueue('another test')
|
224
|
+
#=> => {"default"=>[{:arguments=>["test argument"], :class=>"Returner", :options=>{}}, {:arguments=>["another test"], :class=>"Returner", :options=>{}}]}
|
225
|
+
|
226
|
+
|
227
|
+
```
|
228
|
+
|
229
|
+
You can still flush the queues just as you would running on regular mode.
|
230
|
+
|
231
|
+
```ruby
|
232
|
+
Disc.flush
|
233
|
+
|
234
|
+
Disc.queues
|
235
|
+
#=> {}
|
236
|
+
```
|
237
|
+
|
238
|
+
### Inline mode
|
239
|
+
|
240
|
+
You also have the option for Disc to execute jobs immediately when `#enqueue` is called.
|
241
|
+
|
242
|
+
```ruby
|
243
|
+
require 'disc'
|
244
|
+
require 'disc/testing'
|
245
|
+
|
246
|
+
require_relative 'examples/returner'
|
247
|
+
Disc.inline!
|
248
|
+
|
249
|
+
Returner.enqueue('test argument')
|
250
|
+
#=> 'test argument'
|
251
|
+
```
|
186
252
|
|
187
253
|
## [Optional] Celluloid integration
|
188
254
|
|
@@ -220,7 +286,7 @@ end
|
|
220
286
|
class CluJob < ActiveJob::Base
|
221
287
|
queue_as :urgent
|
222
288
|
|
223
|
-
def perform(*args)
|
289
|
+
def self.perform(*args)
|
224
290
|
# Try to take over The Grid here...
|
225
291
|
end
|
226
292
|
end
|
@@ -238,10 +304,6 @@ Disc is run in the exact same way, for this example it'd be:
|
|
238
304
|
$ QUEUES=urgent disc -r ./disc_init.rb
|
239
305
|
```
|
240
306
|
|
241
|
-
## A note on stability
|
242
|
-
|
243
|
-
The version of Disque at the time of this writing is `0.0.1`. It is a wonderful project, I know of people running it in production and I expect it will only get better and better with time, but please do not expect Disc to be more production ready than Disque is.
|
244
|
-
|
245
307
|
## Similar Projects
|
246
308
|
|
247
309
|
If you want to use Disque but Disc isn't cutting it for you then you should take a look at [Havanna](https://github.com/djanowski/havanna), a project by my friend [@djanowski](https://twitter.com/djanowski).
|
data/bin/disc
CHANGED
data/examples/echoer.rb
CHANGED
data/examples/failer.rb
CHANGED
data/examples/greeter.rb
CHANGED
data/lib/disc/errors.rb
ADDED
data/lib/disc/job.rb
CHANGED
@@ -1,35 +1,10 @@
|
|
1
1
|
module Disc::Job
|
2
|
-
attr_accessor :disque_id,
|
3
|
-
:arguments
|
4
2
|
|
5
3
|
def self.included(base)
|
6
4
|
base.extend(ClassMethods)
|
7
5
|
end
|
8
6
|
|
9
|
-
def info
|
10
|
-
return nil if disque_id.nil?
|
11
|
-
|
12
|
-
Hash[*self.class.disque.call("SHOW", disque_id)]
|
13
|
-
end
|
14
|
-
|
15
|
-
def state
|
16
|
-
info.fetch('state')
|
17
|
-
end
|
18
|
-
|
19
7
|
module ClassMethods
|
20
|
-
def [](disque_id)
|
21
|
-
job_data = disque.call("SHOW", disque_id)
|
22
|
-
return nil if job_data.nil?
|
23
|
-
|
24
|
-
job = self.new
|
25
|
-
job_data = Hash[*job_data]
|
26
|
-
|
27
|
-
job.disque_id = disque_id
|
28
|
-
job.arguments = Disc.deserialize(job_data.fetch('body')).fetch('arguments')
|
29
|
-
|
30
|
-
return job
|
31
|
-
end
|
32
|
-
|
33
8
|
def disque
|
34
9
|
defined?(@disque) ? @disque : Disc.disque
|
35
10
|
end
|
@@ -51,10 +26,6 @@ module Disc::Job
|
|
51
26
|
@queue || Disc.default_queue
|
52
27
|
end
|
53
28
|
|
54
|
-
def perform(arguments)
|
55
|
-
self.new.perform(*arguments)
|
56
|
-
end
|
57
|
-
|
58
29
|
## Disc's `#enqueue` is the main user-facing method of a Disc job, it
|
59
30
|
# enqueues a job with a given set of arguments in Disque, so it can be
|
60
31
|
# picked up by a Disc worker process.
|
@@ -100,7 +71,7 @@ module Disc::Job
|
|
100
71
|
### CAVEATS
|
101
72
|
#
|
102
73
|
## For convenience, any object can be passed as the `arguments` parameter,
|
103
|
-
# `Array
|
74
|
+
# `Array()` will be used internally to preserve the array structure.
|
104
75
|
#
|
105
76
|
## The `arguments` parameter is serialized for storage using `Disc.serialize`
|
106
77
|
# and Disc workers picking it up use `Disc.deserialize` on it, both methods
|
@@ -111,7 +82,7 @@ module Disc::Job
|
|
111
82
|
opt[:delay] = at.to_time.to_i - DateTime.now.to_time.to_i unless at.nil?
|
112
83
|
end
|
113
84
|
|
114
|
-
|
85
|
+
disque.push(
|
115
86
|
queue || self.queue,
|
116
87
|
Disc.serialize({
|
117
88
|
class: self.name,
|
@@ -120,8 +91,6 @@ module Disc::Job
|
|
120
91
|
Disc.disque_timeout,
|
121
92
|
options
|
122
93
|
)
|
123
|
-
|
124
|
-
return self[disque_id]
|
125
94
|
end
|
126
95
|
end
|
127
96
|
end
|
data/lib/disc/testing.rb
CHANGED
@@ -1,9 +1,56 @@
|
|
1
|
-
|
2
|
-
def
|
3
|
-
@
|
1
|
+
class Disc
|
2
|
+
def self.queues
|
3
|
+
@queues ||= {}
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.testing_mode
|
7
|
+
@testing_mode ||= 'enqueue'
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.enqueue!
|
11
|
+
@testing_mode = 'enqueue'
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.inline!
|
15
|
+
@testing_mode = 'inline'
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.flush
|
19
|
+
@queues = {}
|
4
20
|
end
|
5
21
|
|
22
|
+
def self.qlen(queue)
|
23
|
+
return 0 if Disc.queues[queue].nil?
|
24
|
+
|
25
|
+
Disc.queues[queue].length
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.enqueue(klass, arguments, at: nil, queue: nil, **options)
|
29
|
+
job_attrs = { arguments: arguments, class: klass, options: options }
|
30
|
+
if queues[queue].nil?
|
31
|
+
queues[queue] = [job_attrs]
|
32
|
+
else
|
33
|
+
queues[queue] << job_attrs
|
34
|
+
end
|
35
|
+
|
36
|
+
job_attrs
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module Disc::Job::ClassMethods
|
6
41
|
def enqueue(args = [], at: nil, queue: nil, **options)
|
7
|
-
|
42
|
+
case Disc.testing_mode
|
43
|
+
when 'enqueue'
|
44
|
+
Disc.enqueue(
|
45
|
+
self.name,
|
46
|
+
Array(args),
|
47
|
+
queue: queue || self.queue,
|
48
|
+
at: at,
|
49
|
+
**options)
|
50
|
+
when 'inline'
|
51
|
+
self.perform(*args)
|
52
|
+
else
|
53
|
+
raise "Unknown Disc testing mode, this shouldn't happen"
|
54
|
+
end
|
8
55
|
end
|
9
56
|
end
|
data/lib/disc/version.rb
CHANGED
data/lib/disc/worker.rb
CHANGED
@@ -52,13 +52,17 @@ class Disc::Worker
|
|
52
52
|
jobs = disque.fetch(from: queues, timeout: timeout, count: count)
|
53
53
|
Array(jobs).each do |queue, msgid, serialized_job|
|
54
54
|
begin
|
55
|
-
|
56
|
-
|
57
|
-
instance.perform(*job['arguments'])
|
55
|
+
job_class, arguments = Disc.load_job(serialized_job)
|
56
|
+
job_class.perform(*arguments)
|
58
57
|
disque.call('ACKJOB', msgid)
|
59
|
-
$stdout.puts("Completed #{
|
58
|
+
$stdout.puts("Completed #{ job_class.name } id #{ msgid }")
|
60
59
|
rescue => err
|
61
|
-
Disc.on_error(err,
|
60
|
+
Disc.on_error(err, {
|
61
|
+
disque_id: msgid,
|
62
|
+
queue: queue,
|
63
|
+
class: defined?(job_class) ? job_class.name : '',
|
64
|
+
arguments: defined?(arguments) ? arguments : []
|
65
|
+
})
|
62
66
|
end
|
63
67
|
end
|
64
68
|
|
data/lib/disc.rb
CHANGED
@@ -16,7 +16,7 @@ class Disc
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def self.disque_timeout
|
19
|
-
@disque_timeout ||=
|
19
|
+
@disque_timeout ||= 100
|
20
20
|
end
|
21
21
|
|
22
22
|
def self.disque_timeout=(timeout)
|
@@ -50,8 +50,46 @@ class Disc
|
|
50
50
|
def self.deserialize(data)
|
51
51
|
JSON.parse(data)
|
52
52
|
end
|
53
|
+
|
54
|
+
def self.[](disque_id)
|
55
|
+
job_data = disque.call("SHOW", disque_id)
|
56
|
+
return nil if job_data.nil?
|
57
|
+
|
58
|
+
job_data = Hash[*job_data]
|
59
|
+
job_data['arguments'] = Disc.deserialize(job_data['body'])['arguments']
|
60
|
+
job_data['class'] = Disc.deserialize(job_data['body'])['class']
|
61
|
+
|
62
|
+
job_data
|
63
|
+
end
|
64
|
+
|
65
|
+
## Receives:
|
66
|
+
#
|
67
|
+
# A string containing data serialized by `Disc.serialize`
|
68
|
+
#
|
69
|
+
## Returns:
|
70
|
+
#
|
71
|
+
# An array containing:
|
72
|
+
#
|
73
|
+
# * An instance of the given job class
|
74
|
+
# * An array of arguments to pass to the job's `#perorm` class.
|
75
|
+
#
|
76
|
+
def self.load_job(serialized_job)
|
77
|
+
begin
|
78
|
+
job_data = Disc.deserialize(serialized_job)
|
79
|
+
rescue => err
|
80
|
+
raise Disc::NonParsableJobError.new(err)
|
81
|
+
end
|
82
|
+
|
83
|
+
begin
|
84
|
+
job_class = Object.const_get(job_data['class'])
|
85
|
+
rescue => err
|
86
|
+
raise Disc::UnknownJobClassError.new(err)
|
87
|
+
end
|
88
|
+
|
89
|
+
return [job_class, job_data['arguments']]
|
90
|
+
end
|
53
91
|
end
|
54
92
|
|
93
|
+
require_relative 'disc/errors'
|
55
94
|
require_relative 'disc/job'
|
56
95
|
require_relative 'disc/version'
|
57
|
-
|
data/test/disc_test.rb
CHANGED
@@ -1,33 +1,7 @@
|
|
1
1
|
require 'cutest'
|
2
2
|
require 'disc'
|
3
|
-
require 'pty'
|
4
|
-
require 'timeout'
|
5
3
|
|
6
4
|
require_relative '../examples/echoer'
|
7
|
-
# class Echoer
|
8
|
-
# include Disc::Job
|
9
|
-
# disc queue: 'test'
|
10
|
-
#
|
11
|
-
# def perform(first, second, third)
|
12
|
-
# puts "First: #{ first }, Second: #{ second }, Third: #{ third }"
|
13
|
-
# end
|
14
|
-
# end
|
15
|
-
|
16
|
-
require_relative '../examples/failer'
|
17
|
-
# def Disc.on_error(exception, job)
|
18
|
-
# $stdout.puts('<insert error reporting here>')
|
19
|
-
# $stdout.puts(exception.message)
|
20
|
-
# $stdout.puts(job)
|
21
|
-
# end
|
22
|
-
#
|
23
|
-
# class Failer
|
24
|
-
# include Disc::Job
|
25
|
-
# disc queue: 'test'
|
26
|
-
#
|
27
|
-
# def perform(string)
|
28
|
-
# raise string
|
29
|
-
# end
|
30
|
-
# end
|
31
5
|
|
32
6
|
prepare do
|
33
7
|
Disc.disque_timeout = 1 # 1ms so we don't wait at all.
|
@@ -35,180 +9,67 @@ prepare do
|
|
35
9
|
end
|
36
10
|
|
37
11
|
scope do
|
38
|
-
|
39
|
-
|
40
|
-
def run(command)
|
41
|
-
out, _, pid = PTY.spawn(command)
|
42
|
-
yield out, pid
|
43
|
-
ensure
|
44
|
-
Process.kill("KILL", pid)
|
45
|
-
sleep 0.1 # Make sure we give it time to finish.
|
46
|
-
end
|
47
|
-
|
48
|
-
# Checks whether a process is running.
|
49
|
-
def is_running?(pid)
|
50
|
-
Process.getpgid(pid)
|
51
|
-
true
|
52
|
-
rescue Errno::ESRCH
|
53
|
-
false
|
54
|
-
end
|
55
|
-
|
56
|
-
test 'jobs are enqueued to the correct Disque queue with appropriate parameters and class' do
|
57
|
-
job_instance = Echoer.enqueue(['one argument', { random: 'data' }, 3])
|
58
|
-
|
59
|
-
jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
|
60
|
-
assert jobs.any?
|
61
|
-
assert_equal 1, jobs.count
|
12
|
+
test 'Disc should be able to communicate with Disque' do
|
13
|
+
assert !Disc.disque.nil?
|
62
14
|
|
63
|
-
|
64
|
-
job = Disc.deserialize(serialized_job)
|
65
|
-
|
66
|
-
assert job.has_key?('class')
|
67
|
-
assert job.has_key?('arguments')
|
68
|
-
|
69
|
-
assert_equal 'Echoer', job['class']
|
70
|
-
assert_equal job_instance.disque_id, id
|
71
|
-
|
72
|
-
args = job['arguments']
|
73
|
-
assert_equal 3, args.size
|
74
|
-
assert_equal 'one argument', args[0]
|
75
|
-
assert_equal({ 'random' => 'data' }, args[1])
|
76
|
-
assert_equal(3, args[2])
|
77
|
-
end
|
15
|
+
assert_equal 'PONG', Disc.disque.call('PING')
|
78
16
|
end
|
79
17
|
|
80
|
-
test 'we get easy access to the job via the job id' do
|
81
|
-
|
82
|
-
|
83
|
-
assert job_instance.is_a?(Echoer)
|
84
|
-
assert !job_instance.disque_id.nil?
|
85
|
-
assert !job_instance.info.nil?
|
18
|
+
test 'we get easy access to the job via the job id with Disc[job_id]' do
|
19
|
+
job_id = Echoer.enqueue(['one argument', { random: 'data' }, 3])
|
86
20
|
|
87
|
-
|
21
|
+
job_data = Disc[job_id]
|
88
22
|
|
89
|
-
|
90
|
-
assert_equal 'queued',
|
91
|
-
assert_equal 3,
|
92
|
-
assert_equal 'one argument',
|
23
|
+
assert_equal 'Echoer', job_data['class']
|
24
|
+
assert_equal 'queued', job_data['state']
|
25
|
+
assert_equal 3, job_data['arguments'].count
|
26
|
+
assert_equal 'one argument', job_data['arguments'].first
|
93
27
|
end
|
94
28
|
|
95
|
-
test 'we can query the
|
96
|
-
|
29
|
+
test 'we can query the length of a given queue with Disc.qlen' do
|
30
|
+
Echoer.enqueue(['one argument', { random: 'data' }, 3])
|
97
31
|
|
98
32
|
assert_equal 1, Disc.qlen(Echoer.queue)
|
99
33
|
end
|
100
34
|
|
101
|
-
test '
|
102
|
-
job_instance = Echoer.enqueue(['one argument', { random: 'data' }, 3], at: Time.now + 1)
|
103
|
-
|
104
|
-
jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
|
105
|
-
assert jobs.empty?
|
106
|
-
|
107
|
-
sleep 0.5
|
108
|
-
jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
|
109
|
-
assert jobs.empty?
|
110
|
-
|
111
|
-
sleep 0.5
|
112
|
-
jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
|
113
|
-
assert jobs.any?
|
114
|
-
assert_equal 1, jobs.size
|
115
|
-
|
116
|
-
jobs.first.tap do |queue, id, serialized_job|
|
117
|
-
assert_equal 'test', queue
|
118
|
-
assert_equal job_instance.disque_id, id
|
119
|
-
job = Disc.deserialize(serialized_job)
|
120
|
-
assert job.has_key?('class')
|
121
|
-
assert job.has_key?('arguments')
|
122
|
-
assert_equal 'Echoer', job['class']
|
123
|
-
assert_equal 3, job['arguments'].size
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
test 'jobs are executed' do
|
35
|
+
test 'Disc.flush deletes everything in the queue' do
|
128
36
|
Echoer.enqueue(['one argument', { random: 'data' }, 3])
|
37
|
+
Disc.flush
|
129
38
|
|
130
|
-
|
131
|
-
output = Timeout.timeout(1) { cout.take(3) }
|
132
|
-
jobs = Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1)
|
133
|
-
assert jobs.nil?
|
134
|
-
assert output.grep(/First: one argument, Second: {"random"=>"data"}, Third: 3/).any?
|
135
|
-
end
|
39
|
+
assert_equal 0, Disc.qlen(Echoer.queue)
|
136
40
|
end
|
137
41
|
|
138
|
-
test 'Disc.
|
139
|
-
|
42
|
+
test 'Disc.load_job returns a job instance and arguments' do
|
43
|
+
serialized_job = Disc.serialize(
|
44
|
+
{ class: 'Echoer', arguments: ['one argument', { random: 'data' }, 3] }
|
45
|
+
)
|
140
46
|
|
141
|
-
|
142
|
-
output = Timeout.timeout(1) { cout.take(5) }
|
143
|
-
jobs = Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1)
|
144
|
-
assert jobs.nil?
|
47
|
+
job_class, arguments = Disc.load_job(serialized_job)
|
145
48
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
assert is_running?(pid)
|
151
|
-
end
|
49
|
+
assert_equal Echoer, job_class
|
50
|
+
assert arguments.is_a?(Array)
|
51
|
+
assert_equal 3, arguments.count
|
52
|
+
assert_equal 'one argument', arguments.first
|
152
53
|
end
|
153
54
|
|
154
|
-
test '
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
job_instance = Echoer.enqueue(['one argument', { random: 'data' }, 3], delay: 2)
|
163
|
-
|
164
|
-
jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
|
165
|
-
assert jobs.empty?
|
166
|
-
|
167
|
-
sleep 1
|
168
|
-
jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
|
169
|
-
assert jobs.empty?
|
170
|
-
|
171
|
-
sleep 2
|
172
|
-
jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
|
173
|
-
assert jobs.any?
|
174
|
-
assert_equal 1, jobs.size
|
175
|
-
end
|
176
|
-
|
177
|
-
test 'enqueue supports retry' do
|
178
|
-
job_instance = Echoer.enqueue(['one argument', { random: 'data' }, 3], retry: 1)
|
179
|
-
|
180
|
-
jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
|
181
|
-
assert jobs.any?
|
182
|
-
assert_equal 1, jobs.size
|
183
|
-
|
184
|
-
sleep 1.5
|
185
|
-
jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
|
186
|
-
assert jobs.any?
|
187
|
-
assert_equal 1, jobs.size
|
188
|
-
end
|
189
|
-
|
190
|
-
test 'enqueue supports ttl' do
|
191
|
-
job_instance = Echoer.enqueue(['one argument', { random: 'data' }, 3], ttl: 1)
|
192
|
-
|
193
|
-
sleep 1.5
|
194
|
-
jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
|
195
|
-
assert jobs.empty?
|
196
|
-
end
|
197
|
-
|
198
|
-
test 'enqueue supports maxlen' do
|
199
|
-
Echoer.enqueue(['one argument', { random: 'data' }, 3], maxlen: 1)
|
200
|
-
error = Echoer.enqueue(['one argument', { random: 'data' }, 3], maxlen: 1) rescue $!
|
201
|
-
|
202
|
-
assert_equal RuntimeError, error.class
|
203
|
-
assert_equal "MAXLEN Queue is already longer than the specified MAXLEN count", error.message
|
204
|
-
end
|
205
|
-
|
206
|
-
test 'enqueue supports async' do
|
207
|
-
job_instance = Echoer.enqueue(['one argument', { random: 'data' }, 3], async: true)
|
55
|
+
test 'Disc.load_job raises appropriate errors ' do
|
56
|
+
begin
|
57
|
+
job_instance, arguments = Disc.load_job('gibberish')
|
58
|
+
assert_equal 'Should not reach this point', false
|
59
|
+
rescue => err
|
60
|
+
assert err.is_a?(Disc::Error)
|
61
|
+
assert err.is_a?(Disc::NonParsableJobError)
|
62
|
+
end
|
208
63
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
64
|
+
serialized_job = Disc.serialize(
|
65
|
+
{ class: 'NonExistantDiscJobClass', arguments: [] }
|
66
|
+
)
|
67
|
+
begin
|
68
|
+
job_instance, arguments = Disc.load_job(serialized_job)
|
69
|
+
assert_equal 'Should not reach this point', false
|
70
|
+
rescue => err
|
71
|
+
assert err.is_a?(Disc::Error)
|
72
|
+
assert err.is_a?(Disc::UnknownJobClassError)
|
73
|
+
end
|
213
74
|
end
|
214
75
|
end
|
data/test/job_test.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'cutest'
|
2
|
+
require 'disc'
|
3
|
+
|
4
|
+
require_relative '../examples/echoer'
|
5
|
+
|
6
|
+
prepare do
|
7
|
+
Disc.disque_timeout = 1 # 1ms so we don't wait at all.
|
8
|
+
Disc.flush
|
9
|
+
end
|
10
|
+
|
11
|
+
scope do
|
12
|
+
test 'jobs are enqueued to the correct Disque queue with appropriate parameters and class' do
|
13
|
+
job_id = Echoer.enqueue(['one argument', { random: 'data' }, 3])
|
14
|
+
|
15
|
+
jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
|
16
|
+
assert jobs.any?
|
17
|
+
assert_equal 1, jobs.count
|
18
|
+
|
19
|
+
jobs.first.tap do |queue, id, serialized_job|
|
20
|
+
job = Disc.deserialize(serialized_job)
|
21
|
+
|
22
|
+
assert job.has_key?('class')
|
23
|
+
assert job.has_key?('arguments')
|
24
|
+
|
25
|
+
assert_equal 'Echoer', job['class']
|
26
|
+
assert_equal job_id, id
|
27
|
+
|
28
|
+
args = job['arguments']
|
29
|
+
assert_equal 3, args.size
|
30
|
+
assert_equal 'one argument', args[0]
|
31
|
+
assert_equal({ 'random' => 'data' }, args[1])
|
32
|
+
assert_equal(3, args[2])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
test 'enqueue at timestamp behaves properly' do
|
37
|
+
job_id = Echoer.enqueue(['one argument', { random: 'data' }, 3], at: Time.now + 1)
|
38
|
+
|
39
|
+
jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
|
40
|
+
assert jobs.empty?
|
41
|
+
|
42
|
+
sleep 0.5
|
43
|
+
jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
|
44
|
+
assert jobs.empty?
|
45
|
+
|
46
|
+
sleep 0.5
|
47
|
+
jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
|
48
|
+
assert jobs.any?
|
49
|
+
assert_equal 1, jobs.size
|
50
|
+
|
51
|
+
jobs.first.tap do |queue, id, serialized_job|
|
52
|
+
assert_equal 'test', queue
|
53
|
+
assert_equal job_id, id
|
54
|
+
job = Disc.deserialize(serialized_job)
|
55
|
+
assert job.has_key?('class')
|
56
|
+
assert job.has_key?('arguments')
|
57
|
+
assert_equal 'Echoer', job['class']
|
58
|
+
assert_equal 3, job['arguments'].size
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
test 'enqueue supports replicate' do
|
63
|
+
error = Echoer.enqueue(['one argument', { random: 'data' }, 3], replicate: 100) rescue $!
|
64
|
+
|
65
|
+
assert_equal RuntimeError, error.class
|
66
|
+
assert_equal "NOREPL Not enough reachable nodes for the requested replication level", error.message
|
67
|
+
end
|
68
|
+
|
69
|
+
test 'enqueue supports delay' do
|
70
|
+
job_instance = Echoer.enqueue(['one argument', { random: 'data' }, 3], delay: 2)
|
71
|
+
|
72
|
+
jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
|
73
|
+
assert jobs.empty?
|
74
|
+
|
75
|
+
sleep 1
|
76
|
+
jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
|
77
|
+
assert jobs.empty?
|
78
|
+
|
79
|
+
sleep 2
|
80
|
+
jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
|
81
|
+
assert jobs.any?
|
82
|
+
assert_equal 1, jobs.size
|
83
|
+
end
|
84
|
+
|
85
|
+
test 'enqueue supports retry' do
|
86
|
+
job_instance = Echoer.enqueue(['one argument', { random: 'data' }, 3], retry: 1)
|
87
|
+
|
88
|
+
jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
|
89
|
+
assert jobs.any?
|
90
|
+
assert_equal 1, jobs.size
|
91
|
+
|
92
|
+
sleep 1.5
|
93
|
+
jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
|
94
|
+
assert jobs.any?
|
95
|
+
assert_equal 1, jobs.size
|
96
|
+
end
|
97
|
+
|
98
|
+
test 'enqueue supports ttl' do
|
99
|
+
job_instance = Echoer.enqueue(['one argument', { random: 'data' }, 3], ttl: 1)
|
100
|
+
|
101
|
+
sleep 1.5
|
102
|
+
jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
|
103
|
+
assert jobs.empty?
|
104
|
+
end
|
105
|
+
|
106
|
+
test 'enqueue supports maxlen' do
|
107
|
+
Echoer.enqueue(['one argument', { random: 'data' }, 3], maxlen: 1)
|
108
|
+
error = Echoer.enqueue(['one argument', { random: 'data' }, 3], maxlen: 1) rescue $!
|
109
|
+
|
110
|
+
assert_equal RuntimeError, error.class
|
111
|
+
assert_equal "MAXLEN Queue is already longer than the specified MAXLEN count", error.message
|
112
|
+
end
|
113
|
+
|
114
|
+
test 'enqueue supports async' do
|
115
|
+
job_instance = Echoer.enqueue(['one argument', { random: 'data' }, 3], async: true)
|
116
|
+
|
117
|
+
sleep 1 # async is too fast to reliably assert an empty queue, let's wait instead
|
118
|
+
jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
|
119
|
+
assert jobs.any?
|
120
|
+
assert_equal 1, jobs.size
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'cutest'
|
2
|
+
require 'disc'
|
3
|
+
require 'pty'
|
4
|
+
require 'timeout'
|
5
|
+
|
6
|
+
require_relative '../examples/echoer'
|
7
|
+
require_relative '../examples/failer'
|
8
|
+
|
9
|
+
prepare do
|
10
|
+
Disc.disque_timeout = 1 # 1ms so we don't wait at all.
|
11
|
+
Disc.flush
|
12
|
+
end
|
13
|
+
|
14
|
+
scope do
|
15
|
+
# Runs a given command, yielding the stdout (as an IO) and the PID (a String).
|
16
|
+
# Makes sure the process finishes after the block runs.
|
17
|
+
def run(command)
|
18
|
+
out, _, pid = PTY.spawn(command)
|
19
|
+
yield out, pid
|
20
|
+
ensure
|
21
|
+
Process.kill("KILL", pid)
|
22
|
+
sleep 0.1 # Make sure we give it time to finish.
|
23
|
+
end
|
24
|
+
|
25
|
+
# Checks whether a process is running.
|
26
|
+
def is_running?(pid)
|
27
|
+
Process.getpgid(pid)
|
28
|
+
true
|
29
|
+
rescue Errno::ESRCH
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
test 'Disc.on_error will catch unhandled exceptions and keep disc alive' do
|
34
|
+
failer = Failer.enqueue('this can only end positively')
|
35
|
+
|
36
|
+
run('QUEUES=test ruby -Ilib bin/disc -r ./examples/failer') do |cout, pid|
|
37
|
+
output = Timeout.timeout(1) { cout.take(5) }
|
38
|
+
jobs = Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1)
|
39
|
+
assert jobs.nil?
|
40
|
+
|
41
|
+
assert output.grep(/<insert error reporting here>/).any?
|
42
|
+
assert output.grep(/this can only end positively/).any?
|
43
|
+
assert output.grep(/Failer/).any?
|
44
|
+
|
45
|
+
assert is_running?(pid)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
test 'jobs are executed' do
|
50
|
+
Echoer.enqueue(['one argument', { random: 'data' }, 3])
|
51
|
+
|
52
|
+
run('QUEUES=test ruby -Ilib bin/disc -r ./examples/echoer') do |cout, pid|
|
53
|
+
output = Timeout.timeout(1) { cout.take(3) }
|
54
|
+
jobs = Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1)
|
55
|
+
assert jobs.nil?
|
56
|
+
assert output.grep(/First: one argument, Second: {"random"=>"data"}, Third: 3/).any?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# Yo dawg I put some testing in your testing so you can test while you test.
|
2
|
+
|
3
|
+
require 'disc'
|
4
|
+
require 'disc/testing'
|
5
|
+
|
6
|
+
require_relative '../examples/echoer'
|
7
|
+
require_relative '../examples/returner'
|
8
|
+
|
9
|
+
prepare do
|
10
|
+
Disc.disque_timeout = 1 # 1ms so we don't wait at all.
|
11
|
+
Disc.enqueue!
|
12
|
+
Disc.flush
|
13
|
+
end
|
14
|
+
|
15
|
+
scope do
|
16
|
+
test "testing mode should not enqueue jobs into Disque" do
|
17
|
+
Echoer.enqueue(['one argument', { random: 'data' }, 3])
|
18
|
+
assert_equal 0, Disc.disque.call('QLEN', 'test')
|
19
|
+
assert_equal 1, Disc.qlen('test')
|
20
|
+
|
21
|
+
# Flush should still work though
|
22
|
+
Disc.flush
|
23
|
+
assert_equal 0, Disc.qlen('test')
|
24
|
+
end
|
25
|
+
|
26
|
+
test "testing mode enqueue jobs into an in-memory list by default" do
|
27
|
+
Echoer.enqueue(['one argument', { random: 'data' }, 3])
|
28
|
+
|
29
|
+
assert_equal 1, Disc.queues['test'].count
|
30
|
+
assert Disc.queues['test'].first.has_key?(:arguments)
|
31
|
+
assert_equal 3, Disc.queues['test'].first[:arguments].count
|
32
|
+
assert_equal 'one argument', Disc.queues['test'].first[:arguments].first
|
33
|
+
assert_equal 'Echoer', Disc.queues['test'].first[:class]
|
34
|
+
end
|
35
|
+
|
36
|
+
test "testing mode enqueue jobs into an in-memory list by default" do
|
37
|
+
Echoer.enqueue(['one argument', { random: 'data' }, 3])
|
38
|
+
assert_equal 'one argument', Disc.queues['test'].first[:arguments].first
|
39
|
+
end
|
40
|
+
|
41
|
+
test "ability to run jobs inline" do
|
42
|
+
Disc.inline!
|
43
|
+
assert_equal 'this is an argument', Returner.enqueue('this is an argument')
|
44
|
+
end
|
45
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: disc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.28
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- pote
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-05-
|
11
|
+
date: 2016-05-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: disque
|
@@ -61,13 +61,18 @@ files:
|
|
61
61
|
- examples/echoer.rb
|
62
62
|
- examples/failer.rb
|
63
63
|
- examples/greeter.rb
|
64
|
+
- examples/returner.rb
|
64
65
|
- lib/active_job/queue_adapters/disc_adapter.rb
|
65
66
|
- lib/disc.rb
|
67
|
+
- lib/disc/errors.rb
|
66
68
|
- lib/disc/job.rb
|
67
69
|
- lib/disc/testing.rb
|
68
70
|
- lib/disc/version.rb
|
69
71
|
- lib/disc/worker.rb
|
70
72
|
- test/disc_test.rb
|
73
|
+
- test/job_test.rb
|
74
|
+
- test/process_test.rb
|
75
|
+
- test/testing_test.rb
|
71
76
|
homepage: https://github.com/pote/disc
|
72
77
|
licenses:
|
73
78
|
- MIT
|