job_reactor 0.5.2 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.markdown +37 -21
- data/lib/job_reactor.rb +17 -7
- data/lib/job_reactor/distributor.rb +8 -8
- data/lib/job_reactor/distributor/client.rb +1 -1
- data/lib/job_reactor/distributor/server.rb +2 -2
- data/lib/job_reactor/{logger.rb → job_logger.rb} +10 -6
- data/lib/job_reactor/job_reactor.rb +16 -13
- data/lib/job_reactor/job_reactor/config.rb +3 -12
- data/lib/job_reactor/job_reactor/exceptions.rb +2 -4
- data/lib/job_reactor/job_reactor/job_parser.rb +3 -2
- data/lib/job_reactor/job_reactor/storages.rb +1 -6
- data/lib/job_reactor/node.rb +8 -2
- data/lib/job_reactor/node/client.rb +2 -2
- data/lib/job_reactor/node/server.rb +2 -2
- data/lib/job_reactor/storages/memory_storage.rb +7 -8
- data/lib/job_reactor/storages/redis_monitor.rb +2 -2
- data/lib/job_reactor/storages/redis_storage.rb +11 -13
- metadata +29 -38
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4fe498b8b0ef18152ba17eaaae1b4907359c13f1
|
4
|
+
data.tar.gz: 4fcaed4b7cbe32e27b0dcbbd43cf00696f6458f9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 75ebee033f02d15af6987a3f12cb69f2fdc8a61b8e3f8d72a30cfcbae0a542b901f0af89a6a105381b71cb164b7068624a65eed28605d6e109e8661aa257f173
|
7
|
+
data.tar.gz: f0b615f30c23efcfd851f28387e62ff650ab5b590bc05165df56cda72869852b869329a81845a3edae7e1373caaab28a30c72f9a5850b83aa75beeab626d8855
|
data/README.markdown
CHANGED
@@ -3,13 +3,10 @@ JobReactor <img src='https://secure.travis-ci.org/antonmi/job_reactor.png'>
|
|
3
3
|
|
4
4
|
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
|
-
Inspired by [Resque][1], [Beanstalkd][2] ([Stalker][3]), [DelayedJob][4], and etc.
|
7
6
|
|
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).
|
10
|
-
|
11
|
-
We need to test the system with different servers (clusters) and automate the initialization and re-start processes.
|
12
|
-
Collaborators, you are welcome!
|
7
|
+
To use JobReactor with [Sinatra][11] or [Ruby on Rails][9] you should start distributor in initializer using `JR.run` method (it launches EventMachine in separate thread).
|
8
|
+
Then add rake task(s) which will run the node(s).
|
9
|
+
If you use server based on EventMachine such [Thin][12] use JR.wait_em_and_run method which will initialize JobReactor when EventMachine started.
|
13
10
|
|
14
11
|
So, read the 'features' section and try JobReactor. You can do a lot with it.
|
15
12
|
|
@@ -76,7 +73,7 @@ end
|
|
76
73
|
```
|
77
74
|
Run 'application.rb' in one terminal window and 'worker.rb' in another.
|
78
75
|
Node connects to distributor, receives the job and works.
|
79
|
-
Cool! But it was the simplest example. See 'examples' directory
|
76
|
+
Cool! But it was the simplest example. See 'examples' directory.
|
80
77
|
|
81
78
|
Features
|
82
79
|
=============
|
@@ -90,7 +87,7 @@ If you don't have many jobs you can leave only one node which will be connected
|
|
90
87
|
Nodes and distributors are connected via TCP. So, you can run them on any machine you can connect to.
|
91
88
|
Nodes may use different storage or the same one. You can store vitally important jobs in database and
|
92
89
|
simple insignificant jobs in memory.
|
93
|
-
And more: your nodes may create jobs for others nodes and communicate with each other.
|
90
|
+
And more: You can run node and distributor inside one EMreactor, so your nodes may create jobs for others nodes and communicate with each other.
|
94
91
|
3. Full job control
|
95
92
|
-------------------
|
96
93
|
You can add 'callback' and 'errbacks' to the job which will be called on the node.
|
@@ -111,9 +108,11 @@ If node is stopped or crashed it will retry stored jobs after start.
|
|
111
108
|
6. EventMachine available
|
112
109
|
-------------------------
|
113
110
|
Remember, your jobs will be run inside EventMachine reactor! You can easily use the power of async nature of EventMachine.
|
114
|
-
Use asynchronous [em-http-request][6], [em-websocket][7],
|
111
|
+
Use asynchronous [em-http-request][6], [em-websocket][7], and etc.
|
115
112
|
7. Thread safe
|
116
|
-
|
113
|
+
--------------
|
114
|
+
Eventmachine reactor loop runs in one thread. So the code in jobs executed in the given node is absolutely threadsafe.
|
115
|
+
The only exception is 'defer' job, when you tell the node to run job in EM.defer block (so job will be executed in separate thread).
|
117
116
|
8. Deferred and periodic jobs
|
118
117
|
-----------------------------
|
119
118
|
You can use deferred jobs which will run 'after' some time or 'run_at' given time.
|
@@ -153,10 +152,10 @@ And now JobReactor is ready to work.
|
|
153
152
|
``` ruby
|
154
153
|
JR.run! do
|
155
154
|
JR.start_node({
|
156
|
-
:
|
157
|
-
:
|
158
|
-
:
|
159
|
-
:
|
155
|
+
storage: 'redis_storage',
|
156
|
+
name: 'redis_node1',
|
157
|
+
server: ['localhost', 5001],
|
158
|
+
distributors: [['localhost', 5000]]
|
160
159
|
})
|
161
160
|
end
|
162
161
|
```
|
@@ -179,6 +178,7 @@ JR.enqueue('my_job',{arg1: 1, arg2: 2}, {after: 20}, success, error)
|
|
179
178
|
|
180
179
|
The first argument is the name of the job, the second is the arguments hash for the job.
|
181
180
|
The third is the options hash. If you don't specify any option job will be instant job and will be sent to any free node. You can use the following options:
|
181
|
+
* `defer: true or false` - node will run the job in 'EM.defer' block. Be careful, the default threadpool size is 20 for EM. You can increase it by setting EM.threadpool_size = 'your value', but it is not recommended;
|
182
182
|
* `after: seconds` - node will try run the job after `seconds` seconds;
|
183
183
|
* `run_at: time` - node will try run the job at given time;
|
184
184
|
* `period: seconds` - node will run job periodically, each `seconds` seconds;
|
@@ -198,8 +198,10 @@ Example:
|
|
198
198
|
```ruby
|
199
199
|
#in your 'job_file'
|
200
200
|
job 'my_job' do |args|
|
201
|
-
#do smth
|
202
|
-
args.merge!(result: 'Yay!')
|
201
|
+
#do smth
|
202
|
+
args.merge!(result: 'Yay!')
|
203
|
+
end
|
204
|
+
|
203
205
|
#in your application
|
204
206
|
#success feedback
|
205
207
|
success = proc {|args| puts args}
|
@@ -293,14 +295,15 @@ __Note__, feedbacks are kept in memory in your application, so they disappear wh
|
|
293
295
|
|
294
296
|
Job Storage
|
295
297
|
==========
|
296
|
-
Now you can store your
|
298
|
+
Now you can store your jobs in [Redis][5] storage (`'redis_storage`') or in memory (`'memory_storage'`).
|
299
|
+
We use [em-hiredis](https://github.com/mloughran/em-hiredis) gem
|
297
300
|
Only the first, of course, 'really' persists the jobs. You can use the last one if you don't want install Redis, don't need retry jobs and need more speed (by the way, the difference in performance is not so great - Redis is very fast).
|
301
|
+
You can easily integrate your own storage. Just make it EventMachine compatible.
|
298
302
|
|
299
|
-
The default
|
303
|
+
The default url for Redis server are:
|
300
304
|
|
301
305
|
```ruby
|
302
|
-
JR.config[:
|
303
|
-
JR.config[:redis_port] = 6379
|
306
|
+
JR.config[:hiredis_url] = "redis://127.0.0.1:6379/0"
|
304
307
|
```
|
305
308
|
|
306
309
|
JobReactor works asynchronously with Redis using [em-redis][8] library to increase the speed.
|
@@ -314,6 +317,7 @@ The informaion about jobs is saved several times during processing. This informa
|
|
314
317
|
* failed_at - the time when job was failed;
|
315
318
|
* last_error - the error occured;
|
316
319
|
* period - period (for periodic jobs);
|
320
|
+
* defer - 'true' or 'false', flag to run job in EM.defer block;
|
317
321
|
* status - job status ('new', 'in progress', 'queued', 'complete', 'error', 'failed', 'cancelled');
|
318
322
|
* attempt - the number of attempt;
|
319
323
|
* make_after - when to start job again (in seconds after last save);
|
@@ -332,6 +336,16 @@ JR.config[:retry_jobs_at_start] = true
|
|
332
336
|
```
|
333
337
|
|
334
338
|
We provide simple `JR::RedisMonitor` module to check the Redis storage from irb console (or from your app).
|
339
|
+
We use synchronous [redis](https://github.com/redis/redis-rb) gem.
|
340
|
+
Connect to Redis by:
|
341
|
+
|
342
|
+
```ruby
|
343
|
+
JR.config[:redis_host] = 'localhost'
|
344
|
+
JR.config[:redis_port] = 6379
|
345
|
+
```
|
346
|
+
|
347
|
+
|
348
|
+
|
335
349
|
See methods:
|
336
350
|
|
337
351
|
```ruby
|
@@ -344,7 +358,7 @@ JR::RedisMonitor.destroy_all_jobs_for(node_name)
|
|
344
358
|
|
345
359
|
License
|
346
360
|
=======
|
347
|
-
The MIT License - Copyright (c) 2012 Anton Mishchuk
|
361
|
+
The MIT License - Copyright (c) 2012-2013 Anton Mishchuk
|
348
362
|
|
349
363
|
[0]: http://rubyeventmachine.com
|
350
364
|
[1]: https://github.com/defunkt/resque
|
@@ -357,3 +371,5 @@ The MIT License - Copyright (c) 2012 Anton Mishchuk
|
|
357
371
|
[8]: https://github.com/madsimian/em-redis
|
358
372
|
[9]: http://rubyonrails.org/
|
359
373
|
[10]: http://code.macournoyer.com/thin/
|
374
|
+
[11]: http://www.sinatrarb.com/
|
375
|
+
[12]: http://code.macournoyer.com/thin/
|
data/lib/job_reactor.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'eventmachine'
|
2
2
|
require 'job_reactor/job_reactor'
|
3
|
-
require 'job_reactor/
|
3
|
+
require 'job_reactor/job_logger'
|
4
4
|
require 'job_reactor/node'
|
5
5
|
require 'job_reactor/distributor'
|
6
6
|
|
@@ -15,27 +15,37 @@ require 'job_reactor/distributor'
|
|
15
15
|
module JobReactor
|
16
16
|
extend self
|
17
17
|
|
18
|
-
def run
|
18
|
+
def run
|
19
19
|
Thread.new do
|
20
20
|
if EM.reactor_running?
|
21
|
-
|
21
|
+
yield if block_given?
|
22
22
|
JR.ready!
|
23
23
|
else
|
24
24
|
EM.run do
|
25
|
-
|
25
|
+
yield if block_given?
|
26
26
|
JR.ready!
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
def run!
|
32
|
+
def run!
|
33
33
|
if EM.reactor_running?
|
34
|
-
|
34
|
+
yield if block_given?
|
35
35
|
JR.ready!
|
36
36
|
else
|
37
37
|
EM.run do
|
38
|
-
|
38
|
+
yield if block_given?
|
39
|
+
JR.ready!
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def wait_em_and_run
|
45
|
+
Thread.new do
|
46
|
+
sleep(0.1) until EM.reactor_running?
|
47
|
+
EM.schedule do
|
48
|
+
yield if block_given?
|
39
49
|
JR.ready!
|
40
50
|
end
|
41
51
|
end
|
@@ -10,27 +10,27 @@ module JobReactor
|
|
10
10
|
# EM::PeriodicTimer.new(10) { JR::Logger.log nodes}.
|
11
11
|
#
|
12
12
|
def nodes
|
13
|
-
|
13
|
+
@nodes ||= []
|
14
14
|
end
|
15
15
|
|
16
16
|
# Contains connections pool - all node connections.
|
17
17
|
#
|
18
18
|
def connections
|
19
|
-
|
19
|
+
@connections ||= []
|
20
20
|
end
|
21
21
|
|
22
22
|
def server
|
23
|
-
|
23
|
+
@connect_to || "#{@host}:#{@port}"
|
24
24
|
end
|
25
25
|
|
26
26
|
# Starts distributor on given hast and port.
|
27
27
|
# See JR.start_distributor documentation.
|
28
28
|
#
|
29
29
|
def start(host, port, opts = {})
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
JR::
|
30
|
+
@connect_to = opts[:connect_to] && opts[:connect_to].join(':')
|
31
|
+
@host = host
|
32
|
+
@port = port
|
33
|
+
JR::JobLogger.log "Distributor listens #{host}:#{port}"
|
34
34
|
EM.start_server(host, port, JobReactor::Distributor::Server)
|
35
35
|
end
|
36
36
|
|
@@ -44,7 +44,7 @@ module JobReactor
|
|
44
44
|
data = Marshal.dump(hash)
|
45
45
|
connection.send_data(data)
|
46
46
|
connection.lock
|
47
|
-
JR::
|
47
|
+
JR::JobLogger.log "Distributor sent job '#{hash['name']}' to '#{connection.name}'"
|
48
48
|
else
|
49
49
|
EM.next_tick do
|
50
50
|
send_data_to_node(hash)
|
@@ -4,14 +4,14 @@ module JobReactor
|
|
4
4
|
class Server < EM::Connection
|
5
5
|
|
6
6
|
def post_init
|
7
|
-
JR::
|
7
|
+
JR::JobLogger.log 'Begin node handshake'
|
8
8
|
end
|
9
9
|
|
10
10
|
def receive_data(data)
|
11
11
|
data = Marshal.load(data)
|
12
12
|
if data[:node_info]
|
13
13
|
node_info = data[:node_info]
|
14
|
-
JR::
|
14
|
+
JR::JobLogger.log "Receive data from node: #{data[:node_info]}"
|
15
15
|
JobReactor::Distributor.nodes << node_info
|
16
16
|
connection = EM.connect(*node_info[:server], Client, node_info[:name])
|
17
17
|
JobReactor::Distributor.connections << connection
|
@@ -1,23 +1,27 @@
|
|
1
1
|
module JobReactor
|
2
|
-
module
|
2
|
+
module JobLogger
|
3
3
|
|
4
4
|
# Sets the output stream
|
5
5
|
#
|
6
|
-
|
6
|
+
def self.logger_method
|
7
|
+
@logger_method ||= JR.config[:logger_method]
|
8
|
+
end
|
7
9
|
|
8
10
|
def self.stdout=(value)
|
9
|
-
|
11
|
+
@stdout = value
|
10
12
|
end
|
11
13
|
|
12
14
|
def self.stdout
|
13
|
-
|
15
|
+
@stdout ||= $stdout
|
14
16
|
end
|
15
17
|
|
16
18
|
# Logs message to output stream
|
17
19
|
#
|
18
20
|
def self.log(msg)
|
19
|
-
|
20
|
-
|
21
|
+
if logger_method
|
22
|
+
stdout.public_send(logger_method, "-----#{Time.now.utc}-----")
|
23
|
+
stdout.public_send(logger_method, msg)
|
24
|
+
end
|
21
25
|
end
|
22
26
|
|
23
27
|
# Builds string for job event and log it
|
@@ -22,18 +22,18 @@ module JobReactor
|
|
22
22
|
# Accessors to jobs.
|
23
23
|
#
|
24
24
|
def jobs
|
25
|
-
|
25
|
+
@jobs ||= { }
|
26
26
|
end
|
27
27
|
|
28
28
|
# Ready flag.
|
29
|
-
#
|
29
|
+
# @ready is true when block is called inside EM reactor.
|
30
30
|
#
|
31
31
|
def ready!
|
32
|
-
|
32
|
+
@ready = true
|
33
33
|
end
|
34
34
|
|
35
35
|
def ready?
|
36
|
-
(
|
36
|
+
(@ready ||= false) && EM.reactor_running?
|
37
37
|
end
|
38
38
|
|
39
39
|
# Parses jobs.
|
@@ -66,11 +66,11 @@ module JobReactor
|
|
66
66
|
end
|
67
67
|
|
68
68
|
def succ_feedbacks
|
69
|
-
|
69
|
+
@succ_feedbacks ||= { }
|
70
70
|
end
|
71
71
|
|
72
72
|
def err_feedbacks
|
73
|
-
|
73
|
+
@err_feedbacks ||= { }
|
74
74
|
end
|
75
75
|
|
76
76
|
# Here is the only method user can call inside the application (excepts start-up methods, of course).
|
@@ -90,6 +90,9 @@ module JobReactor
|
|
90
90
|
# JR.enqueue 'job', {arg1: 'arg1', arg2: 'arg2'}
|
91
91
|
#
|
92
92
|
# You can add the following options:
|
93
|
+
# :defer - run job in EM.defer block (in separate thread). Default is false.
|
94
|
+
# Be careful, the default threadpool size is 20 for EM.
|
95
|
+
# You can increase it by setting EM.threadpool_size = 'your value', but it is not recommended.
|
93
96
|
# :run_at - run at given time;
|
94
97
|
# :after - run after some time (in seconds);
|
95
98
|
# :period - will make periodic job which will be launched every opts[:period] seconds;
|
@@ -97,7 +100,7 @@ module JobReactor
|
|
97
100
|
# :not_node - to do not send job to the node;
|
98
101
|
#
|
99
102
|
# Example:
|
100
|
-
# JR.enqueue 'job', {arg1: 'arg1'}, {period: 100, node: 'my_favorite_node'}
|
103
|
+
# JR.enqueue 'job', {arg1: 'arg1'}, {period: 100, node: 'my_favorite_node', defer: true}
|
101
104
|
# JR.enqueue 'job', {arg1: 'arg1'}, {after: 10, not_node: 'some_node'}
|
102
105
|
#
|
103
106
|
# You can add 'success feedback' and 'error feedback'. We use term 'feedback' to distinguish them from callbacks and errbacks which are executed on the node side.
|
@@ -109,7 +112,7 @@ module JobReactor
|
|
109
112
|
# error = proc { |args| result = args }
|
110
113
|
# JR.enqueue 'job', { arg1: 'arg1'}, {}, success, error
|
111
114
|
#
|
112
|
-
def enqueue(name, args = {
|
115
|
+
def enqueue(name, args = {}, opts = {}, success_proc = nil, error_proc = nil)
|
113
116
|
hash = { 'name' => name, 'args' => args, 'attempt' => 0, 'status' => 'new', 'defer' => 'false' }
|
114
117
|
|
115
118
|
hash.merge!('period' => opts[:period]) if opts[:period]
|
@@ -121,7 +124,7 @@ module JobReactor
|
|
121
124
|
|
122
125
|
hash.merge!('distributor' => JR::Distributor.server)
|
123
126
|
|
124
|
-
hash.merge!('defer' => 'true'
|
127
|
+
hash.merge!('defer' => opts[:defer] ? 'true' : 'false')
|
125
128
|
|
126
129
|
add_succ_feedbacks!(hash, success_proc) if success_proc.is_a? Proc
|
127
130
|
add_err_feedbacks!(hash, error_proc) if error_proc.is_a? Proc
|
@@ -219,7 +222,7 @@ module JobReactor
|
|
219
222
|
#
|
220
223
|
def add_start_callback(job)
|
221
224
|
job.callback do
|
222
|
-
JR::
|
225
|
+
JR::JobLogger.log_event(:start, job)
|
223
226
|
end
|
224
227
|
end
|
225
228
|
|
@@ -227,7 +230,7 @@ module JobReactor
|
|
227
230
|
#
|
228
231
|
def add_last_callback(job)
|
229
232
|
job.callback do
|
230
|
-
JR::
|
233
|
+
JR::JobLogger.log_event(:complete, job)
|
231
234
|
end
|
232
235
|
end
|
233
236
|
|
@@ -235,7 +238,7 @@ module JobReactor
|
|
235
238
|
#
|
236
239
|
def add_start_errback(job)
|
237
240
|
job.errback do
|
238
|
-
JR::
|
241
|
+
JR::JobLogger.log_event(:error, job)
|
239
242
|
end
|
240
243
|
end
|
241
244
|
|
@@ -243,7 +246,7 @@ module JobReactor
|
|
243
246
|
#
|
244
247
|
def add_complete_errback(job)
|
245
248
|
job.errback do
|
246
|
-
JR::
|
249
|
+
JR::JobLogger.log_event(:error_complete, job)
|
247
250
|
end
|
248
251
|
end
|
249
252
|
|
@@ -1,8 +1,6 @@
|
|
1
|
-
# Names are informative
|
2
|
-
# TODO
|
3
1
|
module JobReactor
|
4
2
|
def self.config
|
5
|
-
|
3
|
+
@config ||= {}
|
6
4
|
end
|
7
5
|
end
|
8
6
|
|
@@ -19,15 +17,8 @@ JR.config[:remove_done_jobs] = true
|
|
19
17
|
JR.config[:remove_cancelled_jobs] = true
|
20
18
|
JR.config[:remove_failed_jobs] = false
|
21
19
|
|
20
|
+
JR.config[:hiredis_url] = "redis://localhost:6379"
|
22
21
|
JR.config[:redis_host] = 'localhost'
|
23
22
|
JR.config[:redis_port] = 6379
|
24
23
|
|
25
|
-
JR.config[:logger_method] = :puts
|
26
|
-
|
27
|
-
#TODO next releases with rails support
|
28
|
-
#JR.config[:active_record_adapter] = 'mysql2'
|
29
|
-
#JR.config[:active_record_database] = 'em'
|
30
|
-
#JR.config[:active_record_user] = ''
|
31
|
-
#JR.config[:active_record_password] = ''
|
32
|
-
#JR.config[:active_record_table_name] = 'reactor_jobs'
|
33
|
-
#JR.config[:use_custom_active_record_connection] = true
|
24
|
+
JR.config[:logger_method] = :puts #nil for complete disable any messages
|
@@ -17,10 +17,11 @@
|
|
17
17
|
# errbacks: [[]]
|
18
18
|
# }
|
19
19
|
# }
|
20
|
+
#
|
20
21
|
# Names of callbacks and errbacks are optional and may be used just for description
|
21
22
|
#
|
22
23
|
# Job and job_callbacks are absolutely identical on the node side.
|
23
|
-
# They become
|
24
|
+
# They become callbacks of the Deferrable instance. 'job' is the first callback, 'job_callbacks' are the next
|
24
25
|
#
|
25
26
|
# Use job_callbacks to split your job into small parts.
|
26
27
|
# You can send additional arguments from one callback to another by merging them into 'args'.
|
@@ -35,7 +36,7 @@
|
|
35
36
|
# end
|
36
37
|
#
|
37
38
|
# This is true for errbacks too. Note that you can't access additional arguments added in callbacks in your errbacks
|
38
|
-
# In errbacks you also have :error key in args which point the error message
|
39
|
+
# In errbacks you also have :error key in args which point the error message.
|
39
40
|
#
|
40
41
|
# Note, that callbacks and errbacks are called one after another synchronously in one EM tick.
|
41
42
|
#
|
@@ -3,22 +3,17 @@
|
|
3
3
|
# save(hash).
|
4
4
|
# load(hash).
|
5
5
|
# destroy(hash).
|
6
|
-
# jobs_for(name).
|
6
|
+
# jobs_for(name). The method is called when node starts.
|
7
7
|
# The last one is used when node is restarting to retry saved jobs.
|
8
8
|
# The storage may not be thread safe, because each node manage it own jobs and don't now anything about others.
|
9
9
|
|
10
10
|
# Defines storages for lazy loading
|
11
11
|
|
12
|
-
# TODO
|
13
|
-
# require 'active_record'
|
14
|
-
# class JobReactor::ActiveRecordStorage < ::ActiveRecord::Base; end
|
15
|
-
|
16
12
|
module JobReactor::MemoryStorage; end
|
17
13
|
module JobReactor::RedisStorage; end
|
18
14
|
|
19
15
|
module JobReactor
|
20
16
|
STORAGES = {
|
21
|
-
#'active_record_storage' => JobReactor::ActiveRecordStorage,
|
22
17
|
'memory_storage' => JobReactor::MemoryStorage,
|
23
18
|
'redis_storage' => JobReactor::RedisStorage
|
24
19
|
}
|
data/lib/job_reactor/node.rb
CHANGED
@@ -4,7 +4,13 @@ module JobReactor
|
|
4
4
|
class Node
|
5
5
|
|
6
6
|
def initialize(opts)
|
7
|
-
@config = {
|
7
|
+
@config = {
|
8
|
+
storage: opts[:storage],
|
9
|
+
name: opts[:name],
|
10
|
+
server: opts[:server],
|
11
|
+
connect_to: opts[:connect_to],
|
12
|
+
distributors: opts[:distributors]
|
13
|
+
}
|
8
14
|
end
|
9
15
|
|
10
16
|
def config
|
@@ -45,7 +51,7 @@ module JobReactor
|
|
45
51
|
#
|
46
52
|
def connect_to(distributor)
|
47
53
|
if connections[distributor]
|
48
|
-
JR::
|
54
|
+
JR::JobLogger.log "Searching for distributor #{distributor.join(' ')}"
|
49
55
|
connections[distributor].reconnect(*distributor)
|
50
56
|
else
|
51
57
|
connections.merge!(distributor => EM.connect(*distributor, Client, self, distributor))
|
@@ -8,7 +8,7 @@ module JobReactor
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def post_init
|
11
|
-
JR::
|
11
|
+
JR::JobLogger.log("Searching for distributor: #{@distributor.join(' ')} ...")
|
12
12
|
end
|
13
13
|
|
14
14
|
def lock
|
@@ -34,7 +34,7 @@ module JobReactor
|
|
34
34
|
# Sends node credentials to distributor.
|
35
35
|
#
|
36
36
|
def connection_completed
|
37
|
-
JR::
|
37
|
+
JR::JobLogger.log('Begin distributor handshake')
|
38
38
|
data = {node_info: {name: @node.config[:name], server: @node.server} }
|
39
39
|
data = Marshal.dump(data)
|
40
40
|
send_data(data)
|
@@ -13,7 +13,7 @@ module JobReactor
|
|
13
13
|
#Ok, node is connected and ready to work
|
14
14
|
#
|
15
15
|
def post_init
|
16
|
-
JR::
|
16
|
+
JR::JobLogger.log("#{@node.name} ready to work")
|
17
17
|
end
|
18
18
|
|
19
19
|
# It is the place where job life cycle begins.
|
@@ -25,7 +25,7 @@ module JobReactor
|
|
25
25
|
#
|
26
26
|
def receive_data(data)
|
27
27
|
hash = Marshal.load(data)
|
28
|
-
JR::
|
28
|
+
JR::JobLogger.log("#{@node.name} received job: #{hash}")
|
29
29
|
hash.merge!('node' => @node.name)
|
30
30
|
@storage.save(hash) do |hash|
|
31
31
|
@node.schedule(hash)
|
@@ -2,37 +2,36 @@
|
|
2
2
|
module JobReactor
|
3
3
|
module MemoryStorage
|
4
4
|
|
5
|
-
@@storage = { }
|
6
5
|
|
7
6
|
class << self
|
8
7
|
def storage
|
9
|
-
|
8
|
+
@storage ||= {}
|
10
9
|
end
|
11
10
|
|
12
|
-
def load(hash
|
11
|
+
def load(hash)
|
13
12
|
hash = storage[hash['id']]
|
14
13
|
if hash
|
15
|
-
hash_copy = {
|
14
|
+
hash_copy = {}
|
16
15
|
hash.each { |k, v| hash_copy.merge!(k => v) }
|
17
|
-
|
16
|
+
yield hash_copy if block_given?
|
18
17
|
end
|
19
18
|
end
|
20
19
|
|
21
|
-
def save(hash
|
20
|
+
def save(hash)
|
22
21
|
unless (hash['id'])
|
23
22
|
id = Time.now.to_f.to_s
|
24
23
|
hash.merge!('id' => id)
|
25
24
|
end
|
26
25
|
storage.merge!(hash['id'] => hash)
|
27
26
|
|
28
|
-
|
27
|
+
yield hash if block_given?
|
29
28
|
end
|
30
29
|
|
31
30
|
def destroy(hash)
|
32
31
|
storage.delete(hash['id'])
|
33
32
|
end
|
34
33
|
|
35
|
-
def jobs_for(name, &block) #No
|
34
|
+
def jobs_for(name, &block) #No persistence
|
36
35
|
nil
|
37
36
|
end
|
38
37
|
end
|
@@ -2,12 +2,12 @@ require 'redis'
|
|
2
2
|
module JobReactor
|
3
3
|
module RedisMonitor
|
4
4
|
|
5
|
-
ATTRS = %w(id name args last_error run_at failed_at attempt period make_after status distributor on_success on_error)
|
5
|
+
ATTRS = %w(id name args last_error run_at failed_at attempt period make_after status distributor on_success on_error defer)
|
6
6
|
|
7
7
|
extend self
|
8
8
|
|
9
9
|
def storage
|
10
|
-
|
10
|
+
@storage ||= Redis.new(host: JR.config[:redis_host], port: JR.config[:redis_port])
|
11
11
|
end
|
12
12
|
|
13
13
|
# Returns all job for given node.
|
@@ -1,21 +1,19 @@
|
|
1
1
|
# TODO comment it
|
2
|
-
require 'em-
|
2
|
+
require 'em-hiredis'
|
3
3
|
|
4
4
|
module JobReactor
|
5
5
|
module RedisStorage
|
6
|
-
@@storage = EM::Protocols::Redis.connect(host: JobReactor.config[:redis_host], port: JobReactor.config[:redis_port])
|
7
6
|
ATTRS = %w(id name args last_error run_at failed_at attempt period make_after status distributor on_success on_error defer)
|
8
7
|
|
9
8
|
class << self
|
10
9
|
|
11
10
|
def storage
|
12
|
-
|
11
|
+
@storage ||= EM::Hiredis.connect(JobReactor.config[:hiredis_url])
|
13
12
|
end
|
14
13
|
|
15
|
-
def load(hash
|
14
|
+
def load(hash)
|
16
15
|
key = "#{hash['node']}_#{hash['id']}"
|
17
16
|
hash_copy = {'node' => hash['node']} #need new object, because old one has been 'failed'
|
18
|
-
|
19
17
|
storage.hmget(key, *ATTRS) do |record|
|
20
18
|
unless record.compact.empty?
|
21
19
|
ATTRS.each_with_index do |attr, i|
|
@@ -26,26 +24,26 @@ module JobReactor
|
|
26
24
|
end
|
27
25
|
hash_copy['args'] = Marshal.load(hash_copy['args'])
|
28
26
|
|
29
|
-
|
27
|
+
yield hash_copy if block_given?
|
30
28
|
end
|
31
29
|
end
|
32
30
|
end
|
33
31
|
|
34
|
-
|
35
|
-
def save(hash, &block)
|
32
|
+
def save(hash)
|
36
33
|
hash.merge!('id' => Time.now.to_f.to_s) unless hash['id']
|
37
34
|
key = "#{hash['node']}_#{hash['id']}"
|
38
35
|
args, hash['args'] = hash['args'], Marshal.dump(hash['args'])
|
39
36
|
|
40
37
|
storage.hmset(key, *ATTRS.map{|attr| [attr, hash[attr]]}.flatten) do
|
41
38
|
hash['args'] = args
|
42
|
-
|
43
|
-
block.call(hash) if block_given?
|
39
|
+
yield hash if block_given?
|
44
40
|
end
|
45
41
|
end
|
46
42
|
|
47
43
|
def destroy(hash)
|
48
|
-
storage.del("#{hash['node']}_#{hash['id']}")
|
44
|
+
storage.del("#{hash['node']}_#{hash['id']}") do
|
45
|
+
yield hash if block_given?
|
46
|
+
end
|
49
47
|
end
|
50
48
|
|
51
49
|
def destroy_all_jobs_for(name)
|
@@ -53,7 +51,7 @@ module JobReactor
|
|
53
51
|
storage.del(*storage.keys(pattern))
|
54
52
|
end
|
55
53
|
|
56
|
-
def jobs_for(name
|
54
|
+
def jobs_for(name)
|
57
55
|
pattern = "*#{name}_*"
|
58
56
|
storage.keys(pattern) do |keys|
|
59
57
|
keys.each do |key|
|
@@ -64,7 +62,7 @@ module JobReactor
|
|
64
62
|
self.load(hash) do |hash|
|
65
63
|
if hash['status'] != 'complete' && hash['status'] != 'cancelled' && hash['status'] != 'failed'
|
66
64
|
else
|
67
|
-
|
65
|
+
yield hash if block_given?
|
68
66
|
end
|
69
67
|
end
|
70
68
|
end
|
metadata
CHANGED
@@ -1,113 +1,104 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: job_reactor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.6.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Anton Mishchuk
|
9
|
-
- Andrey Rozhkovskiy
|
10
8
|
autorequire:
|
11
9
|
bindir: bin
|
12
10
|
cert_chain: []
|
13
|
-
date:
|
11
|
+
date: 2013-06-21 00:00:00.000000000 Z
|
14
12
|
dependencies:
|
15
13
|
- !ruby/object:Gem::Dependency
|
16
14
|
name: eventmachine
|
17
15
|
requirement: !ruby/object:Gem::Requirement
|
18
|
-
none: false
|
19
16
|
requirements:
|
20
|
-
- -
|
17
|
+
- - '>='
|
21
18
|
- !ruby/object:Gem::Version
|
22
19
|
version: '0'
|
23
20
|
type: :runtime
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
26
|
-
none: false
|
27
23
|
requirements:
|
28
|
-
- -
|
24
|
+
- - '>='
|
29
25
|
- !ruby/object:Gem::Version
|
30
26
|
version: '0'
|
31
27
|
- !ruby/object:Gem::Dependency
|
32
28
|
name: redis
|
33
29
|
requirement: !ruby/object:Gem::Requirement
|
34
|
-
none: false
|
35
30
|
requirements:
|
36
|
-
- -
|
31
|
+
- - '>='
|
37
32
|
- !ruby/object:Gem::Version
|
38
33
|
version: '0'
|
39
34
|
type: :runtime
|
40
35
|
prerelease: false
|
41
36
|
version_requirements: !ruby/object:Gem::Requirement
|
42
|
-
none: false
|
43
37
|
requirements:
|
44
|
-
- -
|
38
|
+
- - '>='
|
45
39
|
- !ruby/object:Gem::Version
|
46
40
|
version: '0'
|
47
41
|
- !ruby/object:Gem::Dependency
|
48
|
-
name: em-
|
42
|
+
name: em-hiredis
|
49
43
|
requirement: !ruby/object:Gem::Requirement
|
50
|
-
none: false
|
51
44
|
requirements:
|
52
|
-
- -
|
45
|
+
- - '>='
|
53
46
|
- !ruby/object:Gem::Version
|
54
47
|
version: '0'
|
55
48
|
type: :runtime
|
56
49
|
prerelease: false
|
57
50
|
version_requirements: !ruby/object:Gem::Requirement
|
58
|
-
none: false
|
59
51
|
requirements:
|
60
|
-
- -
|
52
|
+
- - '>='
|
61
53
|
- !ruby/object:Gem::Version
|
62
54
|
version: '0'
|
63
|
-
description:
|
64
|
-
|
65
|
-
|
55
|
+
description: |2
|
56
|
+
JobReactor is a library for creating, scheduling and processing background jobs.
|
57
|
+
It is asynchronous client-server distributed system based on EventMachine.
|
66
58
|
email: anton.mishchuk@gmial.com
|
67
59
|
executables: []
|
68
60
|
extensions: []
|
69
61
|
extra_rdoc_files: []
|
70
62
|
files:
|
71
|
-
- lib/job_reactor.rb
|
72
|
-
- lib/job_reactor/
|
63
|
+
- lib/job_reactor/job_reactor/job_parser.rb
|
64
|
+
- lib/job_reactor/job_reactor/config.rb
|
65
|
+
- lib/job_reactor/job_reactor/exceptions.rb
|
66
|
+
- lib/job_reactor/job_reactor/storages.rb
|
67
|
+
- lib/job_reactor/storages/redis_storage.rb
|
68
|
+
- lib/job_reactor/storages/redis_monitor.rb
|
69
|
+
- lib/job_reactor/storages/memory_storage.rb
|
70
|
+
- lib/job_reactor/distributor/client.rb
|
71
|
+
- lib/job_reactor/distributor/server.rb
|
72
|
+
- lib/job_reactor/job_logger.rb
|
73
73
|
- lib/job_reactor/node/client.rb
|
74
|
+
- lib/job_reactor/node/server.rb
|
74
75
|
- lib/job_reactor/node.rb
|
75
76
|
- lib/job_reactor/job_reactor.rb
|
76
|
-
- lib/job_reactor/distributor/server.rb
|
77
|
-
- lib/job_reactor/distributor/client.rb
|
78
|
-
- lib/job_reactor/storages/redis_monitor.rb
|
79
|
-
- lib/job_reactor/storages/memory_storage.rb
|
80
|
-
- lib/job_reactor/storages/redis_storage.rb
|
81
77
|
- lib/job_reactor/distributor.rb
|
82
|
-
- lib/job_reactor
|
83
|
-
- lib/job_reactor/job_reactor/storages.rb
|
84
|
-
- lib/job_reactor/job_reactor/exceptions.rb
|
85
|
-
- lib/job_reactor/job_reactor/job_parser.rb
|
86
|
-
- lib/job_reactor/job_reactor/config.rb
|
78
|
+
- lib/job_reactor.rb
|
87
79
|
- README.markdown
|
88
80
|
homepage: http://github.com/antonmi/job_reactor
|
89
81
|
licenses: []
|
82
|
+
metadata: {}
|
90
83
|
post_install_message:
|
91
84
|
rdoc_options: []
|
92
85
|
require_paths:
|
93
86
|
- lib
|
94
87
|
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
-
none: false
|
96
88
|
requirements:
|
97
|
-
- -
|
89
|
+
- - '>='
|
98
90
|
- !ruby/object:Gem::Version
|
99
91
|
version: '0'
|
100
92
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
-
none: false
|
102
93
|
requirements:
|
103
|
-
- -
|
94
|
+
- - '>='
|
104
95
|
- !ruby/object:Gem::Version
|
105
96
|
version: '0'
|
106
97
|
requirements: []
|
107
98
|
rubyforge_project:
|
108
|
-
rubygems_version:
|
99
|
+
rubygems_version: 2.0.3
|
109
100
|
signing_key:
|
110
|
-
specification_version:
|
101
|
+
specification_version: 4
|
111
102
|
summary: Simple, powerful and high scalable job queueing and background workers system
|
112
103
|
based on EventMachine
|
113
104
|
test_files: []
|