job_reactor 0.5.0.beta2 → 0.5.0.beta3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|