resque-mongo 1.3.1 → 1.4.0

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/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