job_reactor 0.5.0.beta3 → 0.5.0.beta4
Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown
CHANGED
@@ -5,9 +5,9 @@ Now we are in beta (need to complete documentation and fix some bugs)
|
|
5
5
|
|
6
6
|
JobReactor is a library for creating, scheduling and processing background jobs.
|
7
7
|
It is asynchronous client-server distributed system based on [EventMachine][0].
|
8
|
-
Inspired by Resque, Stalker, DelayedJob, and etc.
|
8
|
+
Inspired by [Resque][1], [Stalker][2], [DelayedJob][3], and etc.
|
9
9
|
|
10
|
-
JobReactor
|
10
|
+
JobReactor has not 'rails' integration for the time being.
|
11
11
|
But it is very close. We need test the system with different servers (clusters) and automatize initialization and restart processes.
|
12
12
|
Collaborators, you are welcome!
|
13
13
|
|
@@ -17,6 +17,9 @@ Quick start
|
|
17
17
|
===========
|
18
18
|
Use `gem install job_reactor --pre` to try it.
|
19
19
|
|
20
|
+
You need to install [Redis][6] if you want to persist your jobs.
|
21
|
+
``$ sudo apt-get install redis-server ``
|
22
|
+
|
20
23
|
In you main application:
|
21
24
|
`application.rb`
|
22
25
|
``` ruby
|
@@ -51,7 +54,7 @@ end
|
|
51
54
|
```
|
52
55
|
Run 'application.rb' in one terminal window and 'worker.rb' in another.
|
53
56
|
Node connects to distributor, receives the job and works.
|
54
|
-
Cool! But it was the simplest example. See 'examples' directory and read
|
57
|
+
Cool! But it was the simplest example. See 'examples' directory and read 'advanced usage'(coming soon).
|
55
58
|
|
56
59
|
Features
|
57
60
|
=============
|
@@ -63,9 +66,9 @@ If you don't have many jobs you can leave only one node which will be connected
|
|
63
66
|
2. High scalability
|
64
67
|
-------------------
|
65
68
|
Nodes and distributors are connected via TCP. So, you can run them on any machine you can connect to.
|
66
|
-
Nodes may use different storage or the same one.
|
69
|
+
Nodes may use different storage or the same one. You can store vitally important jobs in database and
|
67
70
|
simple insignificant jobs in memory.
|
68
|
-
And more: your nodes may create jobs for others nodes and communicate with each other. See page [
|
71
|
+
And more: your nodes may create jobs for others nodes and communicate with each other. See page [advanced usage].
|
69
72
|
3. Full job control
|
70
73
|
-------------------
|
71
74
|
You can add callback and errbacks to the job which will be called on the node.
|
@@ -83,7 +86,7 @@ If node is stopped or crashed it will retry stored jobs after start.
|
|
83
86
|
5. EventMachine available
|
84
87
|
-------------------------
|
85
88
|
Remember, your jobs will be run inside EventMachine reactor! You can easily use the power of async nature of EventMachine.
|
86
|
-
Use asynchronous [http
|
89
|
+
Use asynchronous [em-http-request][4], [em-websocket][5], [etc.], [etc.], and [etc]. See page [advance usage].
|
87
90
|
6. Deferred and periodic jobs
|
88
91
|
-----------------------------
|
89
92
|
You can use deferred jobs which will run 'after' some time or 'run_at' given time.
|
@@ -102,7 +105,7 @@ If no nodes are specified distributor will try to send the job to the first free
|
|
102
105
|
10. Node based priorities
|
103
106
|
-----------------------
|
104
107
|
There are no priorities like in Delayed::Job or Stalker. Bud there are flexible node-based priorities.
|
105
|
-
You can specify the node which should execute the job. You can reserve several nodes for high priority jobs.
|
108
|
+
You can specify the node which should execute the job and the node is forbidden for given job. You can reserve several nodes for high priority jobs.
|
106
109
|
|
107
110
|
|
108
111
|
|
@@ -125,9 +128,17 @@ How it works
|
|
125
128
|
#TODO
|
126
129
|
|
127
130
|
|
131
|
+
License
|
132
|
+
---------
|
133
|
+
The MIT License - Copyright (c) 2012 Anton Mishchuk
|
134
|
+
|
128
135
|
|
129
136
|
|
130
137
|
|
131
|
-
|
132
|
-
|
133
|
-
[
|
138
|
+
[0]: http://rubyeventmachine.com
|
139
|
+
[1]: https://github.com/defunkt/resque
|
140
|
+
[2]: https://github.com/han/stalker
|
141
|
+
[3]: https://github.com/tobi/delayed_job
|
142
|
+
[4]: https://github.com/igrigorik/em-http-request
|
143
|
+
[5]: https://github.com/igrigorik/em-websocket
|
144
|
+
[6]: http://redis.io
|
@@ -17,6 +17,7 @@ JR.config[:log_job_processing] = true
|
|
17
17
|
JR.config[:always_use_specified_node] = false #will send job to another node if specified node is not available
|
18
18
|
JR.config[:remove_done_jobs] = true
|
19
19
|
JR.config[:remove_cancelled_jobs] = true
|
20
|
+
JR.config[:remove_failed_jobs] = false
|
20
21
|
|
21
22
|
JR.config[:redis_host] = 'localhost'
|
22
23
|
JR.config[:redis_port] = 6379
|
data/lib/job_reactor/node.rb
CHANGED
@@ -54,11 +54,7 @@ module JobReactor
|
|
54
54
|
def schedule(hash)
|
55
55
|
EM::Timer.new(hash['make_after']) do #Of course, we can start job immediately (unless it is 'after' job), but we let EM take care about it. Maybe there is another job is ready to start
|
56
56
|
self.storage.load(hash) do |hash|
|
57
|
-
|
58
|
-
do_job(job)
|
59
|
-
else
|
60
|
-
#TODO Do nothing or raise exception ????
|
61
|
-
end
|
57
|
+
do_job(JR.make(hash))
|
62
58
|
end
|
63
59
|
end
|
64
60
|
end
|
@@ -75,6 +71,7 @@ module JobReactor
|
|
75
71
|
def do_job(job)
|
76
72
|
job['run_at'] = Time.now
|
77
73
|
job['status'] = 'in progress'
|
74
|
+
job['attempt'] += 1
|
78
75
|
storage.save(job) do |job|
|
79
76
|
begin
|
80
77
|
args = job['args'].merge(JR.config[:merge_job_itself_to_args] ? {:job_itself => job.dup} : {})
|
@@ -136,10 +133,16 @@ module JobReactor
|
|
136
133
|
#Tryes again or report error
|
137
134
|
#
|
138
135
|
def complete_rescue(job)
|
139
|
-
if job['attempt'].to_i < JobReactor.config[:max_attempt]
|
136
|
+
if job['attempt'].to_i < JobReactor.config[:max_attempt]
|
140
137
|
try_again(job)
|
141
138
|
else
|
139
|
+
job['status'] = 'failed'
|
142
140
|
report_error(job) if job['on_error']
|
141
|
+
if JR.config[:remove_failed_jobs]
|
142
|
+
storage.destroy(job)
|
143
|
+
else
|
144
|
+
storage.save(job)
|
145
|
+
end
|
143
146
|
end
|
144
147
|
end
|
145
148
|
|
@@ -159,7 +162,6 @@ module JobReactor
|
|
159
162
|
# They will be rescheduled after period time.
|
160
163
|
#
|
161
164
|
def try_again(job)
|
162
|
-
job['attempt'] += 1
|
163
165
|
if job['period'] && job['period'] > 0
|
164
166
|
job['make_after'] = job['period']
|
165
167
|
else
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'redis'
|
2
|
+
module JobReactor
|
3
|
+
module RedisMonitor
|
4
|
+
|
5
|
+
ATTRS = %w(id name args last_error run_at failed_at attempt period make_after status distributor on_success on_error)
|
6
|
+
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def storage
|
10
|
+
@@storage ||= Redis.new(host: JR.config[:redis_host], port: JR.config[:redis_port])
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns all job for given node.
|
14
|
+
#
|
15
|
+
def jobs_for(name, to_be_retried = false)
|
16
|
+
pattern = "*#{name}_*"
|
17
|
+
keys = storage.keys(pattern)
|
18
|
+
result = {}
|
19
|
+
keys.each do |key|
|
20
|
+
hash = self.load(key)
|
21
|
+
if to_be_retried
|
22
|
+
result.merge!(key => hash) if hash['status'] != 'complete' && hash['status'] != 'cancelled' && hash['status'] != 'failed'
|
23
|
+
else
|
24
|
+
result.merge!(key => hash)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
result
|
28
|
+
end
|
29
|
+
|
30
|
+
# Load job from storage.
|
31
|
+
#
|
32
|
+
def load(key)
|
33
|
+
hash = {}
|
34
|
+
record = storage.hmget(key, *ATTRS)
|
35
|
+
ATTRS.each_with_index do |attr, i|
|
36
|
+
hash[attr] = record[i]
|
37
|
+
end
|
38
|
+
['attempt', 'period', 'make_after'].each do |attr|
|
39
|
+
hash[attr] = hash[attr].to_i
|
40
|
+
end
|
41
|
+
hash['args'] = Marshal.load(hash['args'])
|
42
|
+
|
43
|
+
hash
|
44
|
+
end
|
45
|
+
|
46
|
+
def destroy(key)
|
47
|
+
storage.del(key)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Destroys all job for given node.
|
51
|
+
#
|
52
|
+
def destroy_all_jobs_for(name)
|
53
|
+
pattern = "*#{name}_*"
|
54
|
+
storage.del(*storage.keys(pattern))
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -60,7 +60,8 @@ module JobReactor
|
|
60
60
|
hash['id'] = id
|
61
61
|
hash['node'] = name
|
62
62
|
self.load(hash) do |hash|
|
63
|
-
if hash['status'] != 'complete' && hash['status'] != 'cancelled' && hash['
|
63
|
+
if hash['status'] != 'complete' && hash['status'] != 'cancelled' && hash['status'] != 'failed'
|
64
|
+
else
|
64
65
|
block.call(hash)
|
65
66
|
end
|
66
67
|
end
|
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.0.
|
4
|
+
version: 0.5.0.beta4
|
5
5
|
prerelease: 6
|
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-
|
13
|
+
date: 2012-06-05 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: eventmachine
|
@@ -28,6 +28,22 @@ dependencies:
|
|
28
28
|
- - ! '>='
|
29
29
|
- !ruby/object:Gem::Version
|
30
30
|
version: '0'
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: redis
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ! '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
type: :runtime
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
31
47
|
- !ruby/object:Gem::Dependency
|
32
48
|
name: em-redis
|
33
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,6 +74,7 @@ files:
|
|
58
74
|
- lib/job_reactor/distributor.rb
|
59
75
|
- lib/job_reactor/logger.rb
|
60
76
|
- lib/job_reactor/storages/memory_storage.rb
|
77
|
+
- lib/job_reactor/storages/redis_monitor.rb
|
61
78
|
- lib/job_reactor/storages/redis_storage.rb
|
62
79
|
- lib/job_reactor/job_reactor/storages.rb
|
63
80
|
- lib/job_reactor/job_reactor/config.rb
|