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 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
- Coming soon, see examples
10
-
11
- Main features
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 storages or the same one. So, you can store vitally important jobs in relational database and
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
- #EM.add_periodic_timer(5) do
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
- JR::Logger.log("WARNING: Node #{hash['node']} is not available") unless node_connection
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 hash EM::Deferrable callbacks and errbacks.
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
- # Requires storage
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 is a hash with the following keys:
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
- # TODO examples.
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']]
@@ -41,7 +41,7 @@ module JobReactor
41
41
  #
42
42
  def connect_to(distributor)
43
43
  if connections[distributor]
44
- JR::Logger.log 'Searching for distributors ...'
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.beta2
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-01 00:00:00.000000000 Z
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: &83843190 !ruby/object:Gem::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: *83843190
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: &83842960 !ruby/object:Gem::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: *83842960
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/job_reactor/job_parser.rb
54
- - lib/job_reactor/job_reactor/storages.rb
55
- - lib/job_reactor/job_reactor/exceptions.rb
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.6
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