disc 0.0.27 → 0.0.28
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/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
|