resque-mongo 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## 1.4.0 (2010-02-11)
2
+
3
+ * Fallback when unable to bind QUIT and USR1 for Windows and JRuby.
4
+ * Fallback when no `Kernel.fork` is provided (for IronRuby).
5
+ * Web: Rounded corners in Firefox
6
+ * Cut down system calls in `Worker#prune_dead_workers`
7
+ * Enable switching DB in a Redis server from config
8
+ * Support USR2 and CONT to stop and start job processing.
9
+ * Web: Add example failing job
10
+ * Bugfix: `Worker#unregister_worker` shouldn't call `done_working`
11
+ * Bugfix: Example god config now restarts Resque properly.
12
+ * Multiple failure backends now permitted.
13
+ * Hoptoad failure backend updated to new API
14
+
1
15
  ## 1.3.1 (2010-01-11)
2
16
 
3
17
  * Vegas bugfix: Don't error without a config
@@ -383,6 +383,8 @@ Resque workers respond to a few different signals:
383
383
  * `QUIT` - Wait for child to finish processing then exit
384
384
  * `TERM` / `INT` - Immediately kill child then exit
385
385
  * `USR1` - Immediately kill child but don't exit
386
+ * `USR2` - Don't start to process any new jobs
387
+ * `CONT` - Start to process new jobs again after a USR2
386
388
 
387
389
  If you want to gracefully shutdown a Resque worker, use `QUIT`.
388
390
 
@@ -392,6 +394,10 @@ Resque assumes the parent process is in a bad state and shuts down.
392
394
 
393
395
  If you want to kill a stale or stuck child and shutdown, use `TERM`
394
396
 
397
+ If you want to stop processing jobs, but want to leave the worker running
398
+ (for example, to temporarily alleviate load), use `USR2` to stop processing,
399
+ then `CONT` to start it again.
400
+
395
401
  ### Mysql::Error: MySQL server has gone away
396
402
 
397
403
  If your workers remain idle for too long they may lose their MySQL
@@ -627,7 +633,7 @@ can re-use the existing connection.
627
633
 
628
634
  String: `Resque.redis = 'localhost:6379'`
629
635
 
630
- Redis: `Redus.redis = $redis`
636
+ Redis: `Resque.redis = $redis`
631
637
 
632
638
  For our rails app we have a `config/initializers/resque.rb` file where
633
639
  we load `config/resque.yml` by hand and set the Redis information
@@ -657,6 +663,25 @@ this way we can tell our Sinatra app about the config file:
657
663
  Now everyone is on the same page.
658
664
 
659
665
 
666
+ Namespaces
667
+ ----------
668
+
669
+ If you're running multiple, separate instances of Resque you may want
670
+ to namespace the keyspaces so they do not overlap. This is not unlike
671
+ the approach taken by many memcached clients.
672
+
673
+ This feature is provided by the [redis-namespace][rs] library, which
674
+ Resque uses by default to separate the keys it manages from other keys
675
+ in your Redis server.
676
+
677
+ Simply use the `Resque.redis.namespace` accessor:
678
+
679
+ Resque.redis.namespace = "resque:GitHub"
680
+
681
+ We recommend sticking this in your initializer somewhere after Redis
682
+ is configured.
683
+
684
+
660
685
  Demo
661
686
  ----
662
687
 
@@ -752,4 +777,4 @@ Chris Wanstrath :: chris@ozmm.org :: @defunkt
752
777
  [1]: http://help.github.com/forking/
753
778
  [2]: http://github.com/defunkt/resque/issues
754
779
  [sv]: http://semver.org/
755
- [resque]: http://github.com/defunct/resque
780
+ [rs]: http://github.com/defunkt/redis-namespace
@@ -15,6 +15,12 @@ module Demo
15
15
  out << '<input type="submit" value="Create New Job"/>'
16
16
  out << '&nbsp;&nbsp;<a href="/resque/">View Resque</a>'
17
17
  out << '</form>'
18
+
19
+ out << "<form action='/failing' method='POST''>"
20
+ out << '<input type="submit" value="Create Failing New Job"/>'
21
+ out << '&nbsp;&nbsp;<a href="/resque/">View Resque</a>'
22
+ out << '</form>'
23
+
18
24
  out << "</body></html>"
19
25
  out
20
26
  end
@@ -23,5 +29,10 @@ module Demo
23
29
  Resque.enqueue(Job, params)
24
30
  redirect "/"
25
31
  end
32
+
33
+ post '/failing' do
34
+ Resque.enqueue(FailingJob, params)
35
+ redirect "/"
36
+ end
26
37
  end
27
38
  end
@@ -9,4 +9,14 @@ module Demo
9
9
  puts "Processed a job!"
10
10
  end
11
11
  end
12
+
13
+ module FailingJob
14
+ @queue = :failing
15
+
16
+ def self.perform(params)
17
+ sleep 1
18
+ raise 'not processable!'
19
+ puts "Processed a job!"
20
+ end
21
+ end
12
22
  end
@@ -7,7 +7,8 @@ num_workers.times do |num|
7
7
  w.name = "resque-#{num}"
8
8
  w.group = 'resque'
9
9
  w.interval = 30.seconds
10
- w.start = "env QUEUE=critical,high,low /usr/bin/rake -f #{rails_root}/Rakefile #{rails_env} resque:work"
10
+ w.env = {"QUEUE"=>"critical,high,low", "RAILS_ENV"=>rails_env}
11
+ w.start = "/usr/bin/rake -f #{rails_root}/Rakefile environment resque:work"
11
12
 
12
13
  w.uid = 'git'
13
14
  w.gid = 'git'
@@ -198,6 +198,12 @@ module Resque
198
198
  Worker.working
199
199
  end
200
200
 
201
+ # A shortcut to unregister_worker
202
+ # useful for command line tool
203
+ def remove_worker(worker_id)
204
+ worker = Resque::Worker.find(worker_id)
205
+ worker.unregister_worker
206
+ end
201
207
 
202
208
  #
203
209
  # stats
@@ -1,4 +1,5 @@
1
1
  require 'net/http'
2
+ require 'builder'
2
3
 
3
4
  module Resque
4
5
  module Failure
@@ -12,6 +13,9 @@ module Resque
12
13
  # config.subdomain = 'your_hoptoad_subdomain'
13
14
  # end
14
15
  class Hoptoad < Base
16
+ #from the hoptoad plugin
17
+ INPUT_FORMAT = %r{^([^:]+):(\d+)(?::in `([^']+)')?$}.freeze
18
+
15
19
  class << self
16
20
  attr_accessor :secure, :api_key, :subdomain
17
21
  end
@@ -31,38 +35,25 @@ module Resque
31
35
  Resque::Failure.backend = self
32
36
  end
33
37
 
34
- def save
35
- data = {
36
- :api_key => api_key,
37
- :error_class => exception.class.name,
38
- :error_message => "#{exception.class.name}: #{exception.message}",
39
- :backtrace => exception.backtrace,
40
- :environment => {},
41
- :session => {},
42
- :request => {
43
- :params => payload.merge(:worker => worker.to_s, :queue => queue.to_s)
44
- }
45
- }
38
+
46
39
 
47
- send_to_hoptoad(:notice => data)
48
- end
49
-
50
- def send_to_hoptoad(data)
40
+ def save
51
41
  http = use_ssl? ? :https : :http
52
- url = URI.parse("#{http}://hoptoadapp.com/notices/")
42
+ url = URI.parse("#{http}://hoptoadapp.com/notifier_api/v2/notices")
53
43
 
54
44
  http = Net::HTTP.new(url.host, url.port)
55
45
  headers = {
56
- 'Content-type' => 'application/json',
46
+ 'Content-type' => 'text/xml',
57
47
  'Accept' => 'text/xml, application/xml'
58
48
  }
59
49
 
60
50
  http.read_timeout = 5 # seconds
61
51
  http.open_timeout = 2 # seconds
52
+
62
53
  http.use_ssl = use_ssl?
63
54
 
64
55
  begin
65
- response = http.post(url.path, Resque.encode(data), headers)
56
+ response = http.post(url.path, xml, headers)
66
57
  rescue TimeoutError => e
67
58
  log "Timeout while contacting the Hoptoad server."
68
59
  end
@@ -75,6 +66,49 @@ module Resque
75
66
  log "Hoptoad Failure: #{response.class}\n#{body}"
76
67
  end
77
68
  end
69
+
70
+ def xml
71
+ x = Builder::XmlMarkup.new
72
+ x.instruct!
73
+ x.notice :version=>"2.0" do
74
+ x.tag! "api-key", api_key
75
+ x.notifier do
76
+ x.name "Resqueue"
77
+ x.version "0.1"
78
+ x.url "http://github.com/defunkt/resque"
79
+ end
80
+ x.error do
81
+ x.class exception.class.name
82
+ x.message "#{exception.class.name}: #{exception.message}"
83
+ x.backtrace do
84
+ fill_in_backtrace_lines(x)
85
+ end
86
+ end
87
+ x.request do
88
+ x.url queue.to_s
89
+ x.component worker.to_s
90
+ x.params do
91
+ x.var :key=>"payload_class" do
92
+ x.text! payload["class"].to_s
93
+ end
94
+ x.var :key=>"payload_args" do
95
+ x.text! payload["args"].to_s
96
+ end
97
+ end
98
+ end
99
+ x.tag!("server-environment") do
100
+ x.tag!("environment-name",RAILS_ENV)
101
+ end
102
+
103
+ end
104
+ end
105
+
106
+ def fill_in_backtrace_lines(x)
107
+ exception.backtrace.each do |unparsed_line|
108
+ _, file, number, method = unparsed_line.match(INPUT_FORMAT).to_a
109
+ x.line :file=>file,:number=>number
110
+ end
111
+ end
78
112
 
79
113
  def use_ssl?
80
114
  self.class.secure
@@ -0,0 +1,44 @@
1
+ module Resque
2
+ module Failure
3
+ # A Failure backend that uses multiple backends
4
+ # delegates all queries to the first backend
5
+ class Multiple < Base
6
+
7
+ class << self
8
+ attr_accessor :classes
9
+ end
10
+
11
+ def self.configure
12
+ yield self
13
+ Resque::Failure.backend = self
14
+ end
15
+
16
+ def initialize(*args)
17
+ @backends = self.class.classes.map {|klass| klass.new(*args)}
18
+ end
19
+ def save
20
+ @backends.each(&:save)
21
+ end
22
+
23
+ # The number of failures.
24
+ def self.count
25
+ classes.first.count
26
+ end
27
+
28
+ # Returns a paginated array of failure objects.
29
+ def self.all(start = 0, count = 1)
30
+ classes.first.all(start,count)
31
+ end
32
+
33
+ # A URL where someone can go to view failures.
34
+ def self.url
35
+ classes.first.url
36
+ end
37
+
38
+ # Clear all failure objects
39
+ def self.clear
40
+ classes.first.clear
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,23 +1,23 @@
1
1
  html { background:#efefef; font-family:Arial, Verdana, sans-serif; font-size:13px; }
2
2
  body { padding:0; margin:0; }
3
3
 
4
- .header { background:#000; padding:8px 5% 0 5%; border-bottom:1px solid #444;border-bottom:5px solid #ce1212;}
4
+ .header { background:#000; padding:8px 5% 0 5%; border-bottom:1px solid #444;border-bottom:5px solid #429234;}
5
5
  .header h1 { color:#333; font-size:90%; font-weight:bold; margin-bottom:6px;}
6
6
  .header ul li { display:inline;}
7
- .header ul li a { color:#fff; text-decoration:none; margin-right:10px; display:inline-block; padding:8px; -webkit-border-top-right-radius:6px; -webkit-border-top-left-radius:6px; }
7
+ .header ul li a { color:#fff; text-decoration:none; margin-right:10px; display:inline-block; padding:8px; -webkit-border-top-right-radius:6px; -webkit-border-top-left-radius:6px; -moz-border-radius-topleft:6px; -moz-border-radius-topright:6px; }
8
8
  .header ul li a:hover { background:#333;}
9
- .header ul li.current a { background:#ce1212; font-weight:bold; color:#fff;}
9
+ .header ul li.current a { background:#429234; font-weight:bold; color:#fff;}
10
10
 
11
- .subnav { padding:2px 5% 7px 5%; background:#ce1212; font-size:90%;}
11
+ .subnav { padding:2px 5% 7px 5%; background:#429234; font-size:90%;}
12
12
  .subnav li { display:inline;}
13
- .subnav li a { color:#fff; text-decoration:none; margin-right:10px; display:inline-block; background:#dd5b5b; padding:5px; -webkit-border-radius:3px; -moz-border-radius:3px;}
14
- .subnav li.current a { background:#fff; font-weight:bold; color:#ce1212;}
13
+ .subnav li a { color:#fff; text-decoration:none; margin-right:10px; display:inline-block; background:#55ad46; padding:5px; -webkit-border-radius:3px; -moz-border-radius:3px;}
14
+ .subnav li.current a { background:#fff; font-weight:bold; color:#429234;}
15
15
  .subnav li a:active { background:#b00909;}
16
16
 
17
17
  #main { padding:10px 5%; background:#fff; overflow:hidden; }
18
18
  #main .logo { float:right; margin:10px;}
19
19
  #main span.hl { background:#efefef; padding:2px;}
20
- #main h1 { margin:10px 0; font-size:190%; font-weight:bold; color:#ce1212;}
20
+ #main h1 { margin:10px 0; font-size:190%; font-weight:bold; color:#429234;}
21
21
  #main h2 { margin:10px 0; font-size:130%;}
22
22
  #main table { width:100%; margin:10px 0;}
23
23
  #main table tr td, #main table tr th { border:1px solid #ccc; padding:6px;}
@@ -31,8 +31,8 @@ body { padding:0; margin:0; }
31
31
 
32
32
  #main table.queues { width:40%;}
33
33
  #main table.queues td.queue { font-weight:bold; width:50%;}
34
- #main table.queues tr.failed td { background:#ffecec; border-top:2px solid #d37474; font-size:90%; color:#d37474;}
35
- #main table.queues tr.failed td a{ color:#d37474;}
34
+ #main table.queues tr.failed td { background:#ebffed; border-top:2px solid #6fd380; font-size:90%; color:#6fd380;}
35
+ #main table.queues tr.failed td a{ color:#6fd380;}
36
36
 
37
37
  #main table.jobs td.class { font-family:Monaco, "Courier New", monospace; font-size:90%; width:50%;}
38
38
  #main table.jobs td.args{ width:50%;}
@@ -72,4 +72,4 @@ body { padding:0; margin:0; }
72
72
  #main p.pagination a.less { float:left;}
73
73
  #main p.pagination a.more { float:right;}
74
74
 
75
- #main form.clear-failed {float:right; margin-top:-10px;}
75
+ #main form.clear-failed {float:right; margin-top:-10px;}
@@ -1,3 +1,3 @@
1
1
  module Resque
2
- Version = '1.3.1'
2
+ Version = '1.4.0'
3
3
  end
@@ -89,14 +89,14 @@ module Resque
89
89
  #
90
90
  # The following events occur during a worker's life cycle:
91
91
  #
92
- # 1. startup: Signals are registered, dead workers are pruned,
93
- # and this worker is registered.
94
- # 2. work loop: Jobs are pulled from a queue and processed
95
- # 3. teardown: This worker is unregistered.
92
+ # 1. Startup: Signals are registered, dead workers are pruned,
93
+ # and this worker is registered.
94
+ # 2. Work loop: Jobs are pulled from a queue and processed.
95
+ # 3. Teardown: This worker is unregistered.
96
96
  #
97
- # Can be passed an integered representing the polling
98
- # frequency. The default is 5 seconds, but for a semi-active site
99
- # you may want to use a smaller value.
97
+ # Can be passed an integer representing the polling frequency.
98
+ # The default is 5 seconds, but for a semi-active site you may
99
+ # want to use a smaller value.
100
100
  #
101
101
  # Also accepts a block which will be passed the job as soon as it
102
102
  # has completed processing. Useful for testing.
@@ -107,7 +107,7 @@ module Resque
107
107
  loop do
108
108
  break if @shutdown
109
109
 
110
- if job = reserve
110
+ if not @paused and job = reserve
111
111
  log "got: #{job.inspect}"
112
112
 
113
113
  if @child = fork
@@ -128,7 +128,7 @@ module Resque
128
128
  else
129
129
  break if interval.to_i == 0
130
130
  log! "Sleeping for #{interval.to_i}"
131
- $0 = "resque: Waiting for #{@queues.join(',')}"
131
+ $0 = @paused ? "resque: Paused" : "resque: Waiting for #{@queues.join(',')}"
132
132
  sleep interval.to_i
133
133
  end
134
134
  end
@@ -186,7 +186,12 @@ module Resque
186
186
  return if @cant_fork
187
187
 
188
188
  begin
189
- Kernel.fork
189
+ # IronRuby doesn't support `Kernel.fork` yet
190
+ if Kernel.respond_to?(:fork)
191
+ Kernel.fork
192
+ else
193
+ raise NotImplementedError
194
+ end
190
195
  rescue NotImplementedError
191
196
  @cant_fork = true
192
197
  nil
@@ -215,13 +220,21 @@ module Resque
215
220
  # INT: Shutdown immediately, stop processing jobs.
216
221
  # QUIT: Shutdown after the current job has finished processing.
217
222
  # USR1: Kill the forked child immediately, continue processing jobs.
223
+ # USR2: Don't process any new jobs
224
+ # CONT: Start processing jobs again after a USR2
218
225
  def register_signal_handlers
219
226
  trap('TERM') { shutdown! }
220
227
  trap('INT') { shutdown! }
221
- unless defined? JRUBY_VERSION
228
+
229
+ begin
222
230
  trap('QUIT') { shutdown }
223
231
  trap('USR1') { kill_child }
232
+ trap('USR2') { pause_processing }
233
+ trap('CONT') { unpause_processing }
234
+ rescue ArgumentError
235
+ warn "Signals QUIT, USR1, USR2, and/or CONT not supported."
224
236
  end
237
+
225
238
  log! "Registered signals"
226
239
  end
227
240
 
@@ -252,21 +265,36 @@ module Resque
252
265
  end
253
266
  end
254
267
 
268
+ # Stop processing jobs after the current one has completed (if we're
269
+ # currently running one).
270
+ def pause_processing
271
+ log "USR2 received; pausing job processing"
272
+ @paused = true
273
+ end
274
+
275
+ # Start processing jobs again after a pause
276
+ def unpause_processing
277
+ log "CONT received; resuming job processing"
278
+ @paused = false
279
+ end
280
+
255
281
  # Looks for any workers which should be running on this server
256
282
  # and, if they're not, removes them from Redis.
257
283
  #
258
284
  # This is a form of garbage collection. If a server is killed by a
259
285
  # hard shutdown, power failure, or something else beyond our
260
- # control, the Resque workers will not die gracefully and therefor
286
+ # control, the Resque workers will not die gracefully and therefore
261
287
  # will leave stale state information in Redis.
262
288
  #
263
289
  # By checking the current Redis state against the actual
264
290
  # environment, we can determine if Redis is old and clean it up a bit.
265
291
  def prune_dead_workers
266
- Worker.all.each do |worker|
292
+ all_workers = Worker.all
293
+ known_workers = worker_pids unless all_workers.empty?
294
+ all_workers.each do |worker|
267
295
  host, pid, queues = worker.to_s.split(':')
268
296
  next unless host == hostname
269
- next if worker_pids.include?(pid)
297
+ next if known_workers.include?(pid)
270
298
  log! "Pruning dead worker: #{worker}"
271
299
  worker.unregister_worker
272
300
  end
@@ -281,9 +309,8 @@ module Resque
281
309
 
282
310
  # Unregisters ourself as a worker. Useful when shutting down.
283
311
  def unregister_worker
284
- done_working
285
-
286
312
  mongo_workers.remove(:worker => self.to_s)
313
+
287
314
  Stat.clear("processed:#{self}")
288
315
  Stat.clear("failed:#{self}")
289
316
  end
@@ -29,7 +29,8 @@ class RedisRunner
29
29
  def self.start
30
30
  puts 'Detach with Ctrl+\ Re-attach with rake redis:attach'
31
31
  sleep 1
32
- exec "dtach -A #{dtach_socket} redis-server #{redisconfdir}"
32
+ command = "dtach -A #{dtach_socket} redis-server #{redisconfdir}"
33
+ sh command
33
34
  end
34
35
 
35
36
  def self.attach
@@ -80,8 +81,9 @@ namespace :redis do
80
81
  puts "Installed redis-benchmark, redis-cli and redis-server to #{bin_dir}"
81
82
 
82
83
  ENV['PREFIX'] and conf_dir = "#{ENV['PREFIX']}/etc" or conf_dir = '/etc'
83
- unless File.exists?("#{conf_dir}")
84
- sh "cp /tmp/redis/redis.conf #{conf_dir}"
84
+ unless File.exists?("#{conf_dir}/redis.conf")
85
+ sh "mkdir #{conf_dir}" unless File.exists?("#{conf_dir}")
86
+ sh "cp /tmp/redis/redis.conf #{conf_dir}/redis.conf"
85
87
  puts "Installed redis.conf to #{conf_dir} \n You should look at this file!"
86
88
  end
87
89
  end
@@ -114,6 +114,14 @@ context "Resque::Worker" do
114
114
  assert_equal [], Resque.workers
115
115
  end
116
116
 
117
+ test "removes worker with stringified id" do
118
+ @worker.work(0) do
119
+ worker_id = Resque.workers[0].to_s
120
+ Resque.remove_worker(worker_id)
121
+ assert_equal [], Resque.workers
122
+ end
123
+ end
124
+
117
125
  test "records what it is working on" do
118
126
  @worker.work(0) do
119
127
  task = @worker.job
@@ -226,4 +234,9 @@ context "Resque::Worker" do
226
234
  assert_equal 1, Resque.workers.size
227
235
  end
228
236
  end
237
+
238
+ test "Processed jobs count" do
239
+ @worker.work(0)
240
+ assert_equal 1, Resque.info[:processed]
241
+ end
229
242
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resque-mongo
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christos Trochalakis
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-01-28 00:00:00 +02:00
12
+ date: 2010-02-12 00:00:00 +02:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -91,6 +91,7 @@ files:
91
91
  - lib/resque/failure/base.rb
92
92
  - lib/resque/failure/hoptoad.rb
93
93
  - lib/resque/failure/mongo.rb
94
+ - lib/resque/failure/multiple.rb
94
95
  - lib/resque/helpers.rb
95
96
  - lib/resque/job.rb
96
97
  - lib/resque/server.rb