job_reactor 0.5.1 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown
CHANGED
@@ -5,8 +5,10 @@ JobReactor is a library for creating, scheduling and processing background jobs.
|
|
5
5
|
It is asynchronous client-server distributed system based on [EventMachine][0].
|
6
6
|
Inspired by [Resque][1], [Beanstalkd][2] ([Stalker][3]), [DelayedJob][4], and etc.
|
7
7
|
|
8
|
-
JobReactor
|
9
|
-
|
8
|
+
To use JobReactor with [Ruby on Rails][9] you should start distributor in initializer using `JR.run` method (it launches EventMachine in separate thread).
|
9
|
+
Then add rake task(s) which will run the node(s). If you use [Thin][10] server the solution is more complicated because 'Thin' use EventMachine too.
|
10
|
+
So, 'rails' integration is not complete for the time being.
|
11
|
+
We need to test the system with different servers (clusters) and automate the initialization and re-start processes.
|
10
12
|
Collaborators, you are welcome!
|
11
13
|
|
12
14
|
So, read the 'features' section and try JobReactor. You can do a lot with it.
|
@@ -34,9 +36,11 @@ In your main application:
|
|
34
36
|
`application.rb`
|
35
37
|
``` ruby
|
36
38
|
require 'job_reactor'
|
39
|
+
|
37
40
|
JR.run do
|
38
|
-
JR.start_distributor('localhost', 5000) #see
|
41
|
+
JR.start_distributor('localhost', 5000) #see lib/job_reactor/job_reactor.rb
|
39
42
|
end
|
43
|
+
|
40
44
|
sleep(1) until(JR.ready?)
|
41
45
|
|
42
46
|
# The application
|
@@ -49,6 +53,7 @@ Define the 'my_job' in separate directory (files with job's definitions **must**
|
|
49
53
|
`reactor_jobs/my_jobs.rb`
|
50
54
|
``` ruby
|
51
55
|
include JobReactor
|
56
|
+
|
52
57
|
job 'my_job' do |args|
|
53
58
|
puts args[:arg1]
|
54
59
|
end
|
@@ -57,14 +62,16 @@ And the last file - 'the worker code':
|
|
57
62
|
`worker.rb`
|
58
63
|
``` ruby
|
59
64
|
require 'job_reactor'
|
65
|
+
|
60
66
|
JR.config[:job_directory] = 'reactor_jobs' #this default config, so you can omit this line
|
67
|
+
|
61
68
|
JR.run! do
|
62
69
|
JR.start_node({
|
63
70
|
:storage => 'memory_storage',
|
64
71
|
:name => 'worker_1',
|
65
72
|
:server => ['localhost', 5001],
|
66
73
|
:distributors => [['localhost', 5000]]
|
67
|
-
}) #see
|
74
|
+
}) #see lib/job_reactor/job_reactor.rb
|
68
75
|
end
|
69
76
|
```
|
70
77
|
Run 'application.rb' in one terminal window and 'worker.rb' in another.
|
@@ -183,7 +190,9 @@ JR.enqueue('my_job', {arg1: 1}, {period: 100, node: 'my_favourite_node', not_nod
|
|
183
190
|
|
184
191
|
The rule to use specified node is not strict if `JR.config[:always_use_specified_node]` is false (default).
|
185
192
|
This means that distributor will try to send the job to the given node at first. But if the node is `locked` (maybe you have just sent another job to it and it is very busy) distributor will look for other node.
|
186
|
-
|
193
|
+
|
194
|
+
The last two arguments are optional. The first is 'success feedback' and the last is 'error feedback'. We use term 'feedback' to distinguish from 'callbacks' and 'errbacks'. 'feedback' is executed on the main application side while 'callbacks' on the node side. 'feedbacks' are the procs which will be called when node sent message that job is completed (successfully or not). The argunments for the 'feedback' are the arguments of the initial job plus all added on the node side.
|
195
|
+
|
187
196
|
Example:
|
188
197
|
|
189
198
|
```ruby
|
@@ -199,7 +208,7 @@ JR.enqueue('my_job', {arg1: 1}, {}, success)
|
|
199
208
|
```
|
200
209
|
|
201
210
|
The 'success' proc args will be {arg1: 1, result: 'Yay!'}.
|
202
|
-
The same story is with 'error feedback'.
|
211
|
+
The same story is with 'error feedback'. __Note__, that error feedback will be launched after all attempts failed on the node side.
|
203
212
|
See config: `JR.config[:max_attempt] = 10` and `JR.config[:retry_multiplier]`
|
204
213
|
|
205
214
|
4. You disconnect node (stop it manually or node fails itself)
|
@@ -212,6 +221,76 @@ See config: `JR.config[:max_attempt] = 10` and `JR.config[:retry_multiplier]`
|
|
212
221
|
---------------------------------
|
213
222
|
* Nodes will continue to work, but you won't be able to receive the results from node when you start the application again because all feedbacks are stored in memory.
|
214
223
|
|
224
|
+
Callbacks and feedbacks
|
225
|
+
============================
|
226
|
+
'callbacks', 'errbacks', 'success feedback', and 'error feedback' helps you divide the __job__ into small relatively independent parts.
|
227
|
+
|
228
|
+
To define `'job'` you use `JobReactor.job` method (see 'Quick start' section). The only arguments are 'job_name' and the block which is the job itself.
|
229
|
+
|
230
|
+
You can define any number of callbacks and errbacks for the given job. Just use `JobReactor.job_callback` and `JobRector.job_errback` methods. The are three arguments for calbacks and errbacks. The name of the job, the name of callback/errback (optional) and the block.
|
231
|
+
|
232
|
+
```ruby
|
233
|
+
include JobReactor
|
234
|
+
|
235
|
+
job 'test_job' do |args|
|
236
|
+
puts "job with args #{args}"
|
237
|
+
end
|
238
|
+
|
239
|
+
job_callback 'test_job', 'first_callback' do |args|
|
240
|
+
puts "first callback with args #{args}"
|
241
|
+
end
|
242
|
+
|
243
|
+
job_callback 'test_job', 'second_callback' do |args|
|
244
|
+
puts "second callback with args #{args}"
|
245
|
+
end
|
246
|
+
|
247
|
+
job_errback 'test_job', 'first_errback' do |args|
|
248
|
+
puts "first errback with error #{args[:error]}"
|
249
|
+
end
|
250
|
+
|
251
|
+
job_errback 'test_job', 'second_errback' do |args|
|
252
|
+
puts 'another errback'
|
253
|
+
end
|
254
|
+
```
|
255
|
+
|
256
|
+
Callbacks and errbacks acts as ordinary EventMachine::Deferrable callbacks and errbacks. The `'job'` is the first callack, first `'job_callback'` becomes second callback and so on. See `lib/job_reactor/job_reactor/job_parser.rb` for more information. When Node start job it calls `succeed` method on the 'job object' with given argument (args). This runs all callbacks sequentially. If error occurs in any callback Node calls `fail` method on the 'deferrable' object with the same args (plus merged `:error => 'Error message`).
|
257
|
+
|
258
|
+
__Note__, you define jobs, callbacks and errbacks in top-level scope, so the `self` is `main` object.
|
259
|
+
|
260
|
+
You can `merge!` additional key-value pairs to 'args' in the job to exchange information between job and it's callbacks.
|
261
|
+
|
262
|
+
```ruby
|
263
|
+
include JobReactor
|
264
|
+
|
265
|
+
job 'test_job' do |args|
|
266
|
+
args.merge!(result: 'Hello')
|
267
|
+
end
|
268
|
+
|
269
|
+
job_callback 'test_job', 'first_callback' do |args|
|
270
|
+
puts args[:result]
|
271
|
+
args.merge!(another_result: 'world')
|
272
|
+
end
|
273
|
+
|
274
|
+
job_callback 'test_job', 'second_callback' do |args|
|
275
|
+
puts "#{args[:result]} #{args[:another_result]}"
|
276
|
+
end
|
277
|
+
```
|
278
|
+
__Note__, if error occurs you can't see additional arguments in job errbacks.
|
279
|
+
|
280
|
+
Another trick is `JR.config[:merge_job_itself_to_args]` option which is `false` by default. If you set this option to `true` you can see `:job_itself` key in `args`. The value contains many usefull information about job ('name', 'attempt', 'status', 'make_after', 'node', etc).
|
281
|
+
|
282
|
+
Feedbacks are defined as a Proc object and attached to the 'job' when it is enqueued on the application side.
|
283
|
+
|
284
|
+
```ruby
|
285
|
+
success = Proc.new { |args| puts 'Success' }
|
286
|
+
error = Proc.new { |args| puts 'Error' }
|
287
|
+
JR.enqueue('my_job', {arg1: 1, arg2: 2}, {after: 100}, success, error)
|
288
|
+
```
|
289
|
+
|
290
|
+
This procs will be called when Node informs about success or error. The 'args' for the corresponding proc will be the same 'args' which is in the job (and it's callbacks) on the node side. So you can, for example, return any result by merging it to 'args' in the job (or it's callbacks).
|
291
|
+
|
292
|
+
__Note__, feedbacks are kept in memory in your application, so they disappear when you restart the application.
|
293
|
+
|
215
294
|
Job Storage
|
216
295
|
==========
|
217
296
|
Now you can store your job in [Redis][5] storage (`'redis_storage`') or in memory (`'memory_storage'`).
|
@@ -242,7 +321,7 @@ The informaion about jobs is saved several times during processing. This informa
|
|
242
321
|
* on_success - the unique id of success feedback on the distributor side;
|
243
322
|
* on_error - the unique id of error feedback on the distributor side;
|
244
323
|
|
245
|
-
By default JobReactor
|
324
|
+
By default JobReactor deletes all completed and cancelled jobs, but you can configure it:
|
246
325
|
The default options are:
|
247
326
|
|
248
327
|
```ruby
|
@@ -276,3 +355,5 @@ The MIT License - Copyright (c) 2012 Anton Mishchuk
|
|
276
355
|
[6]: https://github.com/igrigorik/em-http-request
|
277
356
|
[7]: https://github.com/igrigorik/em-websocket
|
278
357
|
[8]: https://github.com/madsimian/em-redis
|
358
|
+
[9]: http://rubyonrails.org/
|
359
|
+
[10]: http://code.macournoyer.com/thin/
|
@@ -110,7 +110,7 @@ module JobReactor
|
|
110
110
|
# JR.enqueue 'job', { arg1: 'arg1'}, {}, success, error
|
111
111
|
#
|
112
112
|
def enqueue(name, args = { }, opts = { }, success_proc = nil, error_proc = nil)
|
113
|
-
hash = { 'name' => name, 'args' => args, 'attempt' => 0, 'status' => 'new' }
|
113
|
+
hash = { 'name' => name, 'args' => args, 'attempt' => 0, 'status' => 'new', 'defer' => 'false' }
|
114
114
|
|
115
115
|
hash.merge!('period' => opts[:period]) if opts[:period]
|
116
116
|
opts[:after] = (opts[:run_at] - Time.now) if opts[:run_at]
|
@@ -121,6 +121,8 @@ module JobReactor
|
|
121
121
|
|
122
122
|
hash.merge!('distributor' => JR::Distributor.server)
|
123
123
|
|
124
|
+
hash.merge!('defer' => 'true') if opts[:defer]
|
125
|
+
|
124
126
|
add_succ_feedbacks!(hash, success_proc) if success_proc.is_a? Proc
|
125
127
|
add_err_feedbacks!(hash, error_proc) if error_proc.is_a? Proc
|
126
128
|
|
data/lib/job_reactor/node.rb
CHANGED
@@ -56,14 +56,22 @@ module JobReactor
|
|
56
56
|
# It makes a job and run do_job.
|
57
57
|
#
|
58
58
|
def schedule(hash)
|
59
|
-
|
60
|
-
|
61
|
-
|
59
|
+
run_job = Proc.new do
|
60
|
+
if hash['make_after'].to_i > 0
|
61
|
+
EM::Timer.new(hash['make_after']) do
|
62
|
+
self.storage.load(hash) { |hash| do_job(JR.make(hash)) }
|
63
|
+
end
|
64
|
+
else
|
65
|
+
EM.next_tick do
|
66
|
+
self.storage.load(hash) { |hash| do_job(JR.make(hash)) }
|
67
|
+
end
|
62
68
|
end
|
69
|
+
end
|
70
|
+
|
71
|
+
if hash['defer'] == 'true'
|
72
|
+
EM.defer { run_job.call }
|
63
73
|
else
|
64
|
-
|
65
|
-
self.storage.load(hash) { |hash| do_job(JR.make(hash)) }
|
66
|
-
end
|
74
|
+
run_job.call
|
67
75
|
end
|
68
76
|
end
|
69
77
|
|
@@ -4,7 +4,7 @@ require 'em-redis'
|
|
4
4
|
module JobReactor
|
5
5
|
module RedisStorage
|
6
6
|
@@storage = EM::Protocols::Redis.connect(host: JobReactor.config[:redis_host], port: JobReactor.config[:redis_port])
|
7
|
-
ATTRS = %w(id name args last_error run_at failed_at attempt period make_after status distributor on_success on_error)
|
7
|
+
ATTRS = %w(id name args last_error run_at failed_at attempt period make_after status distributor on_success on_error defer)
|
8
8
|
|
9
9
|
class << self
|
10
10
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: job_reactor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-
|
13
|
+
date: 2012-09-18 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: eventmachine
|
@@ -68,22 +68,22 @@ executables: []
|
|
68
68
|
extensions: []
|
69
69
|
extra_rdoc_files: []
|
70
70
|
files:
|
71
|
+
- lib/job_reactor.rb
|
71
72
|
- lib/job_reactor/node/server.rb
|
72
73
|
- lib/job_reactor/node/client.rb
|
74
|
+
- lib/job_reactor/node.rb
|
75
|
+
- lib/job_reactor/job_reactor.rb
|
73
76
|
- lib/job_reactor/distributor/server.rb
|
74
77
|
- lib/job_reactor/distributor/client.rb
|
75
|
-
- lib/job_reactor/distributor.rb
|
76
|
-
- lib/job_reactor/logger.rb
|
77
|
-
- lib/job_reactor/storages/memory_storage.rb
|
78
78
|
- lib/job_reactor/storages/redis_monitor.rb
|
79
|
+
- lib/job_reactor/storages/memory_storage.rb
|
79
80
|
- lib/job_reactor/storages/redis_storage.rb
|
81
|
+
- lib/job_reactor/distributor.rb
|
82
|
+
- lib/job_reactor/logger.rb
|
80
83
|
- lib/job_reactor/job_reactor/storages.rb
|
81
|
-
- lib/job_reactor/job_reactor/config.rb
|
82
|
-
- lib/job_reactor/job_reactor/job_parser.rb
|
83
84
|
- lib/job_reactor/job_reactor/exceptions.rb
|
84
|
-
- lib/job_reactor/job_reactor.rb
|
85
|
-
- lib/job_reactor/
|
86
|
-
- lib/job_reactor.rb
|
85
|
+
- lib/job_reactor/job_reactor/job_parser.rb
|
86
|
+
- lib/job_reactor/job_reactor/config.rb
|
87
87
|
- README.markdown
|
88
88
|
homepage: http://github.com/antonmi/job_reactor
|
89
89
|
licenses: []
|