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 hasn't 'rails' integration for the time being.
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 the wiki (coming soon).
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. So, you can store vitally important jobs in relational database and
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 [advance usage].
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 requests], [websockets], [etc.], [etc.], and [etc]. See page [advance usage].
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
- Links:
132
- ------
133
- [0]: http://rubyeventmachine.com/
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
@@ -5,6 +5,7 @@ require 'job_reactor/job_reactor/config'
5
5
  require 'job_reactor/job_reactor/job_parser'
6
6
  require 'job_reactor/job_reactor/exceptions'
7
7
  require 'job_reactor/job_reactor/storages'
8
+ require 'job_reactor/storages/redis_monitor'
8
9
 
9
10
  module JobReactor
10
11
 
@@ -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
- if job = JR.make(hash) #If we decide fail silently. See JR.make
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] - 1
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['attempt'].to_i < JobReactor.config[:max_attempt]
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.beta3
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-02 00:00:00.000000000 Z
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