resque 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of resque might be problematic. Click here for more details.

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
@@ -365,6 +365,8 @@ Resque workers respond to a few different signals:
365
365
  * `QUIT` - Wait for child to finish processing then exit
366
366
  * `TERM` / `INT` - Immediately kill child then exit
367
367
  * `USR1` - Immediately kill child but don't exit
368
+ * `USR2` - Don't start to process any new jobs
369
+ * `CONT` - Start to process new jobs again after a USR2
368
370
 
369
371
  If you want to gracefully shutdown a Resque worker, use `QUIT`.
370
372
 
@@ -374,6 +376,10 @@ Resque assumes the parent process is in a bad state and shuts down.
374
376
 
375
377
  If you want to kill a stale or stuck child and shutdown, use `TERM`
376
378
 
379
+ If you want to stop processing jobs, but want to leave the worker running
380
+ (for example, to temporarily alleviate load), use `USR2` to stop processing,
381
+ then `CONT` to start it again.
382
+
377
383
  ### Mysql::Error: MySQL server has gone away
378
384
 
379
385
  If your workers remain idle for too long they may lose their MySQL
@@ -570,7 +576,7 @@ Now start your application:
570
576
 
571
577
  That's it! You can now create Resque jobs from within your app.
572
578
 
573
- To start a worker, add this to your Rakefile in RAILS_ROOT:
579
+ To start a worker, add this to your Rakefile in `RAILS_ROOT`:
574
580
 
575
581
  require 'resque/tasks'
576
582
 
@@ -607,9 +613,9 @@ Resque has a `redis` setter which can be given a string or a Redis
607
613
  object. This means if you're already using Redis in your app, Resque
608
614
  can re-use the existing connection.
609
615
 
610
- String: `Resque.redis = 'localhost:6379'
616
+ String: `Resque.redis = 'localhost:6379'`
611
617
 
612
- Redis: `Redus.redis = $redis`
618
+ Redis: `Resque.redis = $redis`
613
619
 
614
620
  For our rails app we have a `config/initializers/resque.rb` file where
615
621
  we load `config/resque.yml` by hand and set the Redis information
@@ -634,11 +640,30 @@ And our initializer:
634
640
  Easy peasy! Why not just use `RAILS_ROOT` and `RAILS_ENV`? Because
635
641
  this way we can tell our Sinatra app about the config file:
636
642
 
637
- $ RAILS_ENV=production resque-web rails_root/config/initializers/resque.rb
643
+ $ RAILS_ENV=production resque-web rails_root/config/initializers/resque.rb
638
644
 
639
645
  Now everyone is on the same page.
640
646
 
641
647
 
648
+ Namespaces
649
+ ----------
650
+
651
+ If you're running multiple, separate instances of Resque you may want
652
+ to namespace the keyspaces so they do not overlap. This is not unlike
653
+ the approach taken by many memcached clients.
654
+
655
+ This feature is provided by the [redis-namespace][rs] library, which
656
+ Resque uses by default to separate the keys it manages from other keys
657
+ in your Redis server.
658
+
659
+ Simply use the `Resque.redis.namespace` accessor:
660
+
661
+ Resque.redis.namespace = "resque:GitHub"
662
+
663
+ We recommend sticking this in your initializer somewhere after Redis
664
+ is configured.
665
+
666
+
642
667
  Demo
643
668
  ----
644
669
 
@@ -734,3 +759,4 @@ Chris Wanstrath :: chris@ozmm.org :: @defunkt
734
759
  [1]: http://help.github.com/forking/
735
760
  [2]: http://github.com/defunkt/resque/issues
736
761
  [sv]: http://semver.org/
762
+ [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'
@@ -20,12 +20,16 @@ module Resque
20
20
  include Helpers
21
21
  extend self
22
22
 
23
- # Accepts a 'hostname:port' string or a Redis server.
23
+ # Accepts:
24
+ # 1. A 'hostname:port' string
25
+ # 2. A 'hostname:port:db' string (to select the Redis db)
26
+ # 3. An instance of `Redis`
24
27
  def redis=(server)
25
28
  case server
26
29
  when String
27
- host, port = server.split(':')
28
- redis = Redis.new(:host => host, :port => port, :thread_safe => true)
30
+ host, port, db = server.split(':')
31
+ redis = Redis.new(:host => host, :port => port,
32
+ :thread_safe => true, :db => db)
29
33
  @redis = Redis::Namespace.new(:resque, :redis => redis)
30
34
  when Redis
31
35
  @redis = Redis::Namespace.new(:resque, :redis => server)
@@ -160,6 +164,12 @@ module Resque
160
164
  Worker.working
161
165
  end
162
166
 
167
+ # A shortcut to unregister_worker
168
+ # useful for command line tool
169
+ def remove_worker(worker_id)
170
+ worker = Resque::Worker.find(worker_id)
171
+ worker.unregister_worker
172
+ end
163
173
 
164
174
  #
165
175
  # 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
@@ -4,7 +4,7 @@ body { padding:0; margin:0; }
4
4
  .header { background:#000; padding:8px 5% 0 5%; border-bottom:1px solid #444;border-bottom:5px solid #ce1212;}
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
9
  .header ul li.current a { background:#ce1212; font-weight:bold; color:#fff;}
10
10
 
@@ -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
@@ -92,14 +92,14 @@ module Resque
92
92
  #
93
93
  # The following events occur during a worker's life cycle:
94
94
  #
95
- # 1. startup: Signals are registered, dead workers are pruned,
96
- # and this worker is registered.
97
- # 2. work loop: Jobs are pulled from a queue and processed
98
- # 3. teardown: This worker is unregistered.
95
+ # 1. Startup: Signals are registered, dead workers are pruned,
96
+ # and this worker is registered.
97
+ # 2. Work loop: Jobs are pulled from a queue and processed.
98
+ # 3. Teardown: This worker is unregistered.
99
99
  #
100
- # Can be passed an integered representing the polling
101
- # frequency. The default is 5 seconds, but for a semi-active site
102
- # you may want to use a smaller value.
100
+ # Can be passed an integer representing the polling frequency.
101
+ # The default is 5 seconds, but for a semi-active site you may
102
+ # want to use a smaller value.
103
103
  #
104
104
  # Also accepts a block which will be passed the job as soon as it
105
105
  # has completed processing. Useful for testing.
@@ -110,7 +110,7 @@ module Resque
110
110
  loop do
111
111
  break if @shutdown
112
112
 
113
- if job = reserve
113
+ if not @paused and job = reserve
114
114
  log "got: #{job.inspect}"
115
115
 
116
116
  if @child = fork
@@ -131,7 +131,7 @@ module Resque
131
131
  else
132
132
  break if interval.to_i == 0
133
133
  log! "Sleeping for #{interval.to_i}"
134
- $0 = "resque: Waiting for #{@queues.join(',')}"
134
+ $0 = @paused ? "resque: Paused" : "resque: Waiting for #{@queues.join(',')}"
135
135
  sleep interval.to_i
136
136
  end
137
137
  end
@@ -189,7 +189,12 @@ module Resque
189
189
  return if @cant_fork
190
190
 
191
191
  begin
192
- Kernel.fork
192
+ # IronRuby doesn't support `Kernel.fork` yet
193
+ if Kernel.respond_to?(:fork)
194
+ Kernel.fork
195
+ else
196
+ raise NotImplementedError
197
+ end
193
198
  rescue NotImplementedError
194
199
  @cant_fork = true
195
200
  nil
@@ -218,13 +223,21 @@ module Resque
218
223
  # INT: Shutdown immediately, stop processing jobs.
219
224
  # QUIT: Shutdown after the current job has finished processing.
220
225
  # USR1: Kill the forked child immediately, continue processing jobs.
226
+ # USR2: Don't process any new jobs
227
+ # CONT: Start processing jobs again after a USR2
221
228
  def register_signal_handlers
222
229
  trap('TERM') { shutdown! }
223
230
  trap('INT') { shutdown! }
224
- unless defined? JRUBY_VERSION
231
+
232
+ begin
225
233
  trap('QUIT') { shutdown }
226
234
  trap('USR1') { kill_child }
235
+ trap('USR2') { pause_processing }
236
+ trap('CONT') { unpause_processing }
237
+ rescue ArgumentError
238
+ warn "Signals QUIT, USR1, USR2, and/or CONT not supported."
227
239
  end
240
+
228
241
  log! "Registered signals"
229
242
  end
230
243
 
@@ -255,21 +268,36 @@ module Resque
255
268
  end
256
269
  end
257
270
 
271
+ # Stop processing jobs after the current one has completed (if we're
272
+ # currently running one).
273
+ def pause_processing
274
+ log "USR2 received; pausing job processing"
275
+ @paused = true
276
+ end
277
+
278
+ # Start processing jobs again after a pause
279
+ def unpause_processing
280
+ log "CONT received; resuming job processing"
281
+ @paused = false
282
+ end
283
+
258
284
  # Looks for any workers which should be running on this server
259
285
  # and, if they're not, removes them from Redis.
260
286
  #
261
287
  # This is a form of garbage collection. If a server is killed by a
262
288
  # hard shutdown, power failure, or something else beyond our
263
- # control, the Resque workers will not die gracefully and therefor
289
+ # control, the Resque workers will not die gracefully and therefore
264
290
  # will leave stale state information in Redis.
265
291
  #
266
292
  # By checking the current Redis state against the actual
267
293
  # environment, we can determine if Redis is old and clean it up a bit.
268
294
  def prune_dead_workers
269
- Worker.all.each do |worker|
295
+ all_workers = Worker.all
296
+ known_workers = worker_pids unless all_workers.empty?
297
+ all_workers.each do |worker|
270
298
  host, pid, queues = worker.id.split(':')
271
299
  next unless host == hostname
272
- next if worker_pids.include?(pid)
300
+ next if known_workers.include?(pid)
273
301
  log! "Pruning dead worker: #{worker}"
274
302
  worker.unregister_worker
275
303
  end
@@ -284,8 +312,6 @@ module Resque
284
312
 
285
313
  # Unregisters ourself as a worker. Useful when shutting down.
286
314
  def unregister_worker
287
- done_working
288
-
289
315
  redis.srem(:workers, self)
290
316
  redis.del("worker:#{self}:started")
291
317
 
@@ -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
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
  - Chris Wanstrath
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-01-11 00:00:00 -08:00
12
+ date: 2010-02-11 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -100,6 +100,7 @@ files:
100
100
  - lib/resque/failure.rb
101
101
  - lib/resque/failure/base.rb
102
102
  - lib/resque/failure/hoptoad.rb
103
+ - lib/resque/failure/multiple.rb
103
104
  - lib/resque/failure/redis.rb
104
105
  - lib/resque/helpers.rb
105
106
  - lib/resque/job.rb