job_reactor 0.5.0.beta2 → 0.5.0.beta3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +50 -5
- data/lib/job_reactor/distributor.rb +11 -25
- data/lib/job_reactor/job_reactor/config.rb +0 -1
- data/lib/job_reactor/job_reactor/exceptions.rb +0 -13
- data/lib/job_reactor/job_reactor/job_parser.rb +25 -0
- data/lib/job_reactor/job_reactor.rb +32 -6
- data/lib/job_reactor/node.rb +2 -2
- metadata +29 -19
data/README.markdown
CHANGED
@@ -1,14 +1,59 @@
|
|
1
1
|
JobReactor
|
2
2
|
==========
|
3
|
+
Now we are in beta (need to complete documentation and fix some bugs)
|
4
|
+
---------------------------------------------------
|
3
5
|
|
4
|
-
JobReactor is a library for creating and processing background jobs.
|
6
|
+
JobReactor is a library for creating, scheduling and processing background jobs.
|
5
7
|
It is asynchronous client-server distributed system based on [EventMachine][0].
|
8
|
+
Inspired by Resque, Stalker, DelayedJob, and etc.
|
9
|
+
|
10
|
+
JobReactor hasn't 'rails' integration for the time being.
|
11
|
+
But it is very close. We need test the system with different servers (clusters) and automatize initialization and restart processes.
|
12
|
+
Collaborators, you are welcome!
|
13
|
+
|
14
|
+
So, read 'features' part and try JobReactor. You can do a lot with it.
|
6
15
|
|
7
16
|
Quick start
|
8
17
|
===========
|
9
|
-
|
10
|
-
|
11
|
-
|
18
|
+
Use `gem install job_reactor --pre` to try it.
|
19
|
+
|
20
|
+
In you main application:
|
21
|
+
`application.rb`
|
22
|
+
``` ruby
|
23
|
+
require 'job_reactor'
|
24
|
+
JR.run do
|
25
|
+
JR.start_distributor('localhost', 5000)
|
26
|
+
end
|
27
|
+
sleep(1) until(JR.ready?)
|
28
|
+
|
29
|
+
# The application
|
30
|
+
loop do
|
31
|
+
sleep(3) #Your application is working
|
32
|
+
JR.enqueue 'my_job', {arg1: 'Hello'}
|
33
|
+
end
|
34
|
+
```
|
35
|
+
Define the 'my_job' in separate directory (files with job's definitions must be in separate directory):
|
36
|
+
`reactor_jobs/my_jobs.rb`
|
37
|
+
``` ruby
|
38
|
+
include JobReactor
|
39
|
+
job 'my_job' do |args|
|
40
|
+
puts args[:arg1]
|
41
|
+
end
|
42
|
+
```
|
43
|
+
And the last file - 'the worker code':
|
44
|
+
`worker.rb`
|
45
|
+
``` ruby
|
46
|
+
require 'job_reactor'
|
47
|
+
JR.config[:job_directory] = 'reactor_jobs' #this default config so you can omit this line
|
48
|
+
JR.run! do
|
49
|
+
JR.start_node({:storage => 'memory_storage', :name => 'worker_1', :server => ['localhost', 5001], :distributors => [['localhost', 5000]] })
|
50
|
+
end
|
51
|
+
```
|
52
|
+
Run 'application.rb' in one terminal window and 'worker.rb' in another.
|
53
|
+
Node connects to distributor, receives the job and works.
|
54
|
+
Cool! But it was the simplest example. See 'examples' directory and read the wiki (coming soon).
|
55
|
+
|
56
|
+
Features
|
12
57
|
=============
|
13
58
|
1. Client-server architecture
|
14
59
|
-----------------------------
|
@@ -18,7 +63,7 @@ If you don't have many jobs you can leave only one node which will be connected
|
|
18
63
|
2. High scalability
|
19
64
|
-------------------
|
20
65
|
Nodes and distributors are connected via TCP. So, you can run them on any machine you can connect to.
|
21
|
-
Nodes may use different
|
66
|
+
Nodes may use different storage or the same one. So, you can store vitally important jobs in relational database and
|
22
67
|
simple insignificant jobs in memory.
|
23
68
|
And more: your nodes may create jobs for others nodes and communicate with each other. See page [advance usage].
|
24
69
|
3. Full job control
|
@@ -13,25 +13,28 @@ module JobReactor
|
|
13
13
|
@@port
|
14
14
|
end
|
15
15
|
|
16
|
+
# Gets nodes
|
17
|
+
# You can monitor available nodes connections in you application.
|
18
|
+
# For example
|
19
|
+
# EM::PeriodicTimer.new(10) { JR::Logger.log nodes}
|
20
|
+
#
|
16
21
|
def nodes
|
17
22
|
@@nodes ||= []
|
18
23
|
end
|
19
24
|
|
20
|
-
# Contains connections pool
|
25
|
+
# Contains connections pool - all node connections
|
26
|
+
#
|
21
27
|
def connections
|
22
28
|
@@connections ||= []
|
23
29
|
end
|
24
30
|
|
25
31
|
#Starts distributor on given hast and port
|
26
|
-
|
32
|
+
#
|
27
33
|
def start(host, port)
|
28
34
|
@@host = host
|
29
35
|
@@port = port
|
30
|
-
EM.start_server(host, port, JobReactor::Distributor::Server, [host, port])
|
31
36
|
JR::Logger.log "Distributor listens #{host}:#{port}"
|
32
|
-
|
33
|
-
# JR::Logger.log('Available nodes: ' << JR::Distributor.connections.map(&:name).join(' '))
|
34
|
-
#end
|
37
|
+
EM.start_server(host, port, JobReactor::Distributor::Server)
|
35
38
|
end
|
36
39
|
|
37
40
|
# Tries to find available node connection
|
@@ -57,13 +60,11 @@ module JobReactor
|
|
57
60
|
# If job hash specified node, tries check if the node is available.
|
58
61
|
# If not, returns nil or tries to find any other free node if :always_use_specified_node == true
|
59
62
|
# If job hasn't any specified node, methods return any available connection or nil (and will be launched again in one second)
|
60
|
-
|
63
|
+
#
|
61
64
|
def get_connection(hash)
|
62
|
-
check_node_pool
|
63
65
|
if hash['node']
|
64
66
|
node_connection = connections.select{ |con| con.name == hash['node'] && con.name != hash['not_node']}.first
|
65
|
-
|
66
|
-
if node_connection.try(:available?)
|
67
|
+
if node_connection && node_connection.available?
|
67
68
|
node_connection
|
68
69
|
else
|
69
70
|
JR.config[:always_use_specified_node] ? nil : connections.select{ |con| con.available? && con.name != hash['not_node'] }.first
|
@@ -73,20 +74,5 @@ module JobReactor
|
|
73
74
|
end
|
74
75
|
end
|
75
76
|
|
76
|
-
# Checks node poll. If it is empty will fail after :when_node_pull_is_empty_will_raise_exception_after seconds
|
77
|
-
# The distributor will fail when number of timers raise to EM.get_max_timers which if default 100000 for the majority system
|
78
|
-
# To exit earlier may be useful for error detection
|
79
|
-
#
|
80
|
-
def check_node_pool
|
81
|
-
if connections.size == 0
|
82
|
-
JR::Logger.log 'Warning: Node pool is empty'
|
83
|
-
EM::Timer.new(JR.config[:when_node_pull_is_empty_will_raise_exception_after]) do
|
84
|
-
if connections.size == 0
|
85
|
-
raise JobReactor::NodePoolIsEmpty
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
77
|
end
|
92
78
|
end
|
@@ -17,7 +17,6 @@ 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[:when_node_pull_is_empty_will_raise_exception_after] = 3600
|
21
20
|
|
22
21
|
JR.config[:redis_host] = 'localhost'
|
23
22
|
JR.config[:redis_port] = 6379
|
@@ -1,22 +1,9 @@
|
|
1
1
|
module JobReactor
|
2
2
|
|
3
|
-
# The purpose of exceptions is in their names
|
4
|
-
# TODO
|
5
|
-
|
6
|
-
class NoJobsDefined < RuntimeError
|
7
|
-
end
|
8
3
|
class NoSuchJob < RuntimeError
|
9
4
|
end
|
10
5
|
class CancelJob < RuntimeError
|
11
6
|
end
|
12
|
-
class NodePoolIsEmpty < RuntimeError
|
13
|
-
end
|
14
|
-
class NoSuchNode < RuntimeError
|
15
|
-
end
|
16
|
-
class LostConnection < RuntimeError
|
17
|
-
end
|
18
|
-
class SchedulePeriodicJob < RuntimeError
|
19
|
-
end
|
20
7
|
|
21
8
|
end
|
22
9
|
|
@@ -18,6 +18,31 @@
|
|
18
18
|
# }
|
19
19
|
# }
|
20
20
|
# Names of callbacks and errbacks are optional and may be used just for description
|
21
|
+
#
|
22
|
+
# Job and job_callbacks are absolutely identical on the node side.
|
23
|
+
# They become callback of the Deferrable instance. 'job' is the first callback, 'job_callbacks' are the next
|
24
|
+
#
|
25
|
+
# Use job_callbacks to split your job into small parts.
|
26
|
+
# You can send additional arguments from one callback to another by merging them into 'args'.
|
27
|
+
# For example:
|
28
|
+
#
|
29
|
+
# job 'job' do |args|
|
30
|
+
# args.merge!(:another_arg => 'Hello')
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# job_callback 'job', 'job_callback' do |args|
|
34
|
+
# puts args[:another_arg]
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# 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
|
+
#
|
40
|
+
# Note, that callbacks and errbacks are called one after another synchronously in one EM tick.
|
41
|
+
#
|
42
|
+
# You also have :job_itself in your args. You can turn this option of by setting JR.config[:merge_job_itself_to_args] to false
|
43
|
+
# :job_itself point to Hash which has the following keys: "node", "id", name", "last_error", "run_at", "failed_at", "attempt", "period", "status"=>"error", "distributor", "on_success", "on_error"
|
44
|
+
# So, you can all information about the 'job' inside job.
|
45
|
+
#
|
21
46
|
|
22
47
|
module JobReactor
|
23
48
|
extend self
|
@@ -9,7 +9,7 @@ require 'job_reactor/job_reactor/storages'
|
|
9
9
|
module JobReactor
|
10
10
|
|
11
11
|
# Yes, we monkeypatched Ruby core class.
|
12
|
-
# Now all hashes
|
12
|
+
# Now all hashes has EM::Deferrable callbacks and errbacks.
|
13
13
|
# It is just for simplicity.
|
14
14
|
# It's cool use 'job = {}' instead 'job = JobHash.new.
|
15
15
|
# We are ready to discuss it and change.
|
@@ -35,7 +35,8 @@ module JobReactor
|
|
35
35
|
(@@ready ||= false) && EM.reactor_running?
|
36
36
|
end
|
37
37
|
|
38
|
-
#
|
38
|
+
# Parses jobs.
|
39
|
+
# Requires storage.
|
39
40
|
# Creates and start node.
|
40
41
|
#
|
41
42
|
def start_node(opts)
|
@@ -62,9 +63,36 @@ module JobReactor
|
|
62
63
|
# The method set initial arguments and send job to distributor which will send it to node.
|
63
64
|
# Options are :after and :period (for deferred and periodic jobs), and :node to specify the preferred node to launch job.
|
64
65
|
# Use :always_use_specified_node option to be sure that job will launched in the specified node.
|
65
|
-
# Job itself
|
66
|
+
# Job itself will be a hash with the following keys:
|
66
67
|
# name, args, make_after, last_error, run_at, failed_at, attempt, period, node, not_node, status, distributor, on_success, on_error.
|
67
|
-
#
|
68
|
+
#
|
69
|
+
# Simple job with arguments.
|
70
|
+
# Arguments should be a Hash.
|
71
|
+
# Arguments will be serialized using Marshal.dump before sending to node, so be sure that objects in args can be dumped.
|
72
|
+
# (Do not use procs, objects with singleton methods, etc ... ).
|
73
|
+
#
|
74
|
+
# Example:
|
75
|
+
# JR.enqueue 'job', {:arg1 => 'arg1', :arg2 => 'arg2'}
|
76
|
+
#
|
77
|
+
# You can add the following options:
|
78
|
+
# :run_at - run at given time;
|
79
|
+
# :after - run after some time (in seconds);
|
80
|
+
# :period - will make periodic job which will be launched every opts[:period] seconds;
|
81
|
+
# :node - to send job to the specific node;
|
82
|
+
# :not_node - to do not send job to the node;
|
83
|
+
#
|
84
|
+
# Example:
|
85
|
+
# JR.enqueue 'job', {:arg1 => 'arg1'}, {:period => 100, :node => 'my_favorite_node'}
|
86
|
+
# JR.enqueue 'job', {:arg1 => 'arg1'}, {:after => 10, :not_node => 'some_node'}
|
87
|
+
#
|
88
|
+
# 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.
|
89
|
+
# These feedbacks are the procs. The first is 'success feedback', the second - 'error feedback'.
|
90
|
+
# These feedback procs are called with 'job arguments' as arguments.
|
91
|
+
#
|
92
|
+
# Example:
|
93
|
+
# success = proc { |args| result = args }
|
94
|
+
# error = proc { |args| result = args }
|
95
|
+
# JR.enqueue 'job', { :arg1 => 'arg1'}, {}, success, error
|
68
96
|
#
|
69
97
|
def enqueue(name, args = { }, opts = { }, success_proc = nil, error_proc = nil)
|
70
98
|
hash = { 'name' => name, 'args' => args, 'attempt' => 0, 'status' => 'new' }
|
@@ -95,8 +123,6 @@ module JobReactor
|
|
95
123
|
#
|
96
124
|
# Then errbacks are attached.
|
97
125
|
# They are called when error occurs in callbacks.
|
98
|
-
# The last errback raise exception again to return job back to node workflow.
|
99
|
-
# See Node#do_job method to better understand how this works.
|
100
126
|
#
|
101
127
|
def make(hash) #new job is a Hash
|
102
128
|
raise NoSuchJob unless jr_job = JR.jobs[hash['name']]
|
data/lib/job_reactor/node.rb
CHANGED
@@ -41,7 +41,7 @@ module JobReactor
|
|
41
41
|
#
|
42
42
|
def connect_to(distributor)
|
43
43
|
if connections[distributor]
|
44
|
-
JR::Logger.log
|
44
|
+
JR::Logger.log "Searching for distributor #{distributor.join(' ')}"
|
45
45
|
connections[distributor].reconnect(*distributor)
|
46
46
|
else
|
47
47
|
connections.merge!(distributor => EM.connect(*distributor, Client, self, distributor))
|
@@ -126,7 +126,7 @@ module JobReactor
|
|
126
126
|
rescue JobReactor::CancelJob
|
127
127
|
cancel_job(job) #If it was cancelled we destroy it or set status 'cancelled'
|
128
128
|
rescue Exception => e #Recsue Exceptions in errbacks
|
129
|
-
job['args'].merge!(:errback_error => e)
|
129
|
+
job['args'].merge!(:errback_error => e) #So in args you now have :error and :errback_error
|
130
130
|
complete_rescue(job)
|
131
131
|
end
|
132
132
|
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.beta3
|
5
5
|
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,11 +10,11 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-06-
|
13
|
+
date: 2012-06-02 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: eventmachine
|
17
|
-
requirement:
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
@@ -22,10 +22,15 @@ dependencies:
|
|
22
22
|
version: '0'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
|
-
version_requirements:
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ! '>='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '0'
|
26
31
|
- !ruby/object:Gem::Dependency
|
27
32
|
name: em-redis
|
28
|
-
requirement:
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
29
34
|
none: false
|
30
35
|
requirements:
|
31
36
|
- - ! '>='
|
@@ -33,7 +38,12 @@ dependencies:
|
|
33
38
|
version: '0'
|
34
39
|
type: :runtime
|
35
40
|
prerelease: false
|
36
|
-
version_requirements:
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
37
47
|
description: ! " JobReactor is a library for creating and processing background
|
38
48
|
jobs.\n It is client-server distributed system based on EventMachine.\n"
|
39
49
|
email: anton.mishchuk@gmial.com
|
@@ -41,21 +51,21 @@ executables: []
|
|
41
51
|
extensions: []
|
42
52
|
extra_rdoc_files: []
|
43
53
|
files:
|
44
|
-
- lib/job_reactor.rb
|
45
|
-
- lib/job_reactor/job_reactor.rb
|
46
|
-
- lib/job_reactor/node.rb
|
47
|
-
- lib/job_reactor/storages/redis_storage.rb
|
48
|
-
- lib/job_reactor/storages/memory_storage.rb
|
49
|
-
- lib/job_reactor/distributor/client.rb
|
50
|
-
- lib/job_reactor/distributor/server.rb
|
51
|
-
- lib/job_reactor/node/client.rb
|
52
54
|
- lib/job_reactor/node/server.rb
|
53
|
-
- lib/job_reactor/
|
54
|
-
- lib/job_reactor/
|
55
|
-
- lib/job_reactor/
|
56
|
-
- lib/job_reactor/job_reactor/config.rb
|
55
|
+
- lib/job_reactor/node/client.rb
|
56
|
+
- lib/job_reactor/distributor/server.rb
|
57
|
+
- lib/job_reactor/distributor/client.rb
|
57
58
|
- lib/job_reactor/distributor.rb
|
58
59
|
- lib/job_reactor/logger.rb
|
60
|
+
- lib/job_reactor/storages/memory_storage.rb
|
61
|
+
- lib/job_reactor/storages/redis_storage.rb
|
62
|
+
- lib/job_reactor/job_reactor/storages.rb
|
63
|
+
- lib/job_reactor/job_reactor/config.rb
|
64
|
+
- lib/job_reactor/job_reactor/job_parser.rb
|
65
|
+
- lib/job_reactor/job_reactor/exceptions.rb
|
66
|
+
- lib/job_reactor/job_reactor.rb
|
67
|
+
- lib/job_reactor/node.rb
|
68
|
+
- lib/job_reactor.rb
|
59
69
|
- README.markdown
|
60
70
|
homepage: http://github.com/antonmi/job_reactor
|
61
71
|
licenses: []
|
@@ -77,7 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
77
87
|
version: 1.3.1
|
78
88
|
requirements: []
|
79
89
|
rubyforge_project:
|
80
|
-
rubygems_version: 1.8.
|
90
|
+
rubygems_version: 1.8.24
|
81
91
|
signing_key:
|
82
92
|
specification_version: 3
|
83
93
|
summary: Simple, powerful and high scalable job queueing and background workers system
|