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 has not 'rails' integration for the time being.
9
- But it is very close. We need to test the system with different servers (clusters) and automate the initialization and re-start processes.
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 documentation
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 documentation
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
- The last two arguments are optional too. 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.
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'. Note that error feedback will be launched after all attempts failed on the node side.
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 delete all completed and cancelled jobs, but you can configure it:
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/
@@ -9,7 +9,7 @@
9
9
 
10
10
  # Defines storages for lazy loading
11
11
 
12
- # TODO 'NEXT RELEASE'
12
+ # TODO
13
13
  # require 'active_record'
14
14
  # class JobReactor::ActiveRecordStorage < ::ActiveRecord::Base; end
15
15
 
@@ -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
 
@@ -56,14 +56,22 @@ module JobReactor
56
56
  # It makes a job and run do_job.
57
57
  #
58
58
  def schedule(hash)
59
- if hash['make_after'].to_i > 0
60
- EM::Timer.new(hash['make_after']) do
61
- self.storage.load(hash) { |hash| do_job(JR.make(hash)) }
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
- EM.next_tick do
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
 
@@ -10,7 +10,7 @@ module JobReactor
10
10
  end
11
11
 
12
12
  def load(hash, &block)
13
- hash = storage[hash['id']]
13
+ hash = storage[hash['id']]
14
14
  if hash
15
15
  hash_copy = { }
16
16
  hash.each { |k, v| hash_copy.merge!(k => v) }
@@ -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.1
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-06-18 00:00:00.000000000 Z
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/node.rb
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: []