resque 1.20.0 → 1.21.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,13 @@
1
+ ## 1.21.0 (2012-07-02)
2
+
3
+ * Add a flag to make sure failure hooks are only ran once (jakemack, #546)
4
+ * Support updated MultiJSON API (@twinturbo)
5
+ * Fix worker logging in monit example config (@twinturbo)
6
+ * loosen dependency of redis-namespace to 1.x, support for redis-rb 3.0.x
7
+ * change '%' to '$' in the 'stop program' command for monit
8
+ * UTF8 sanitize exception messages when there's a failure (@brianmario, #507)
9
+ * don't share a redis connection between parent and child (@jsanders, #588)
10
+
1
11
  ## 1.20.0 (2012-02-17)
2
12
 
3
13
  * Fixed demos for ruby 1.9 (@BMorearty, #445)
data/Rakefile CHANGED
@@ -20,18 +20,11 @@ require 'rake/testtask'
20
20
 
21
21
  task :default => :test
22
22
 
23
- if command?(:rg)
24
- desc "Run the test suite with rg"
25
- task :test do
26
- Dir['test/**/*_test.rb'].each do |f|
27
- sh("rg #{f}")
28
- end
29
- end
30
- else
31
- Rake::TestTask.new do |test|
32
- test.libs << "test"
33
- test.test_files = FileList['test/**/*_test.rb']
34
- end
23
+ Rake::TestTask.new do |test|
24
+ test.verbose = true
25
+ test.libs << "test"
26
+ test.libs << "lib"
27
+ test.test_files = FileList['test/**/*_test.rb']
35
28
  end
36
29
 
37
30
  if command? :kicker
@@ -74,5 +67,4 @@ task :publish do
74
67
  sh "git push origin v#{Resque::Version}"
75
68
  sh "git push origin master"
76
69
  sh "git clean -fd"
77
- exec "rake pages"
78
70
  end
@@ -13,6 +13,8 @@ require 'resque/job'
13
13
  require 'resque/worker'
14
14
  require 'resque/plugin'
15
15
 
16
+ require 'resque/vendor/utf8_util'
17
+
16
18
  module Resque
17
19
  include Helpers
18
20
  extend self
@@ -77,9 +79,7 @@ module Resque
77
79
 
78
80
  # Set a proc that will be called in the parent process before the
79
81
  # worker forks for the first time.
80
- def before_first_fork=(before_first_fork)
81
- @before_first_fork = before_first_fork
82
- end
82
+ attr_writer :before_first_fork
83
83
 
84
84
  # The `before_fork` hook will be run in the **parent** process
85
85
  # before every job, so be careful- any changes you make will be
@@ -92,9 +92,7 @@ module Resque
92
92
  end
93
93
 
94
94
  # Set the before_fork proc.
95
- def before_fork=(before_fork)
96
- @before_fork = before_fork
97
- end
95
+ attr_writer :before_fork
98
96
 
99
97
  # The `after_fork` hook will be run in the child process and is passed
100
98
  # the current job. Any changes you make, therefore, will only live as
@@ -107,25 +105,18 @@ module Resque
107
105
  end
108
106
 
109
107
  # Set the after_fork proc.
110
- def after_fork=(after_fork)
111
- @after_fork = after_fork
112
- end
108
+ attr_writer :after_fork
113
109
 
114
110
  def to_s
115
111
  "Resque Client connected to #{redis_id}"
116
112
  end
117
113
 
114
+ attr_accessor :inline
115
+
118
116
  # If 'inline' is true Resque will call #perform method inline
119
117
  # without queuing it into Redis and without any Resque callbacks.
120
118
  # The 'inline' is false Resque jobs will be put in queue regularly.
121
- def inline?
122
- @inline
123
- end
124
- alias_method :inline, :inline?
125
-
126
- def inline=(inline)
127
- @inline = inline
128
- end
119
+ alias :inline? :inline
129
120
 
130
121
  #
131
122
  # queue manipulation
@@ -8,7 +8,7 @@ module Resque
8
8
  :failed_at => Time.now.strftime("%Y/%m/%d %H:%M:%S %Z"),
9
9
  :payload => payload,
10
10
  :exception => exception.class.to_s,
11
- :error => exception.to_s,
11
+ :error => UTF8Util.clean(exception.to_s),
12
12
  :backtrace => filter_backtrace(Array(exception.backtrace)),
13
13
  :worker => worker.to_s,
14
14
  :queue => queue
@@ -2,8 +2,10 @@ require 'multi_json'
2
2
 
3
3
  # OkJson won't work because it doesn't serialize symbols
4
4
  # in the same way yajl and json do.
5
- if MultiJson.engine.to_s == 'MultiJson::Engines::OkJson'
6
- raise "Please install the yajl-ruby or json gem"
5
+ if MultiJson.respond_to?(:adapter)
6
+ raise "Please install the yajl-ruby or json gem" if MultiJson.adapter.to_s == 'MultiJson::Adapters::OkJson'
7
+ elsif MultiJson.respond_to?(:engine)
8
+ raise "Please install the yajl-ruby or json gem" if MultiJson.engine.to_s == 'MultiJson::Engines::OkJson'
7
9
  end
8
10
 
9
11
  module Resque
@@ -19,7 +21,12 @@ module Resque
19
21
  # Given a Ruby object, returns a string suitable for storage in a
20
22
  # queue.
21
23
  def encode(object)
22
- ::MultiJson.encode(object)
24
+ if MultiJson.respond_to?(:dump) && MultiJson.respond_to?(:load)
25
+ MultiJson.dump object
26
+ else
27
+ MultiJson.encode object
28
+ end
29
+
23
30
  end
24
31
 
25
32
  # Given a string, returns a Ruby object.
@@ -27,7 +34,11 @@ module Resque
27
34
  return unless object
28
35
 
29
36
  begin
30
- ::MultiJson.decode(object)
37
+ if MultiJson.respond_to?(:dump) && MultiJson.respond_to?(:load)
38
+ MultiJson.load object
39
+ else
40
+ MultiJson.decode object
41
+ end
31
42
  rescue ::MultiJson::DecodeError => e
32
43
  raise DecodeException, e.message, e.backtrace
33
44
  end
@@ -32,6 +32,7 @@ module Resque
32
32
  def initialize(queue, payload)
33
33
  @queue = queue
34
34
  @payload = payload
35
+ @failure_hooks_ran = false
35
36
  end
36
37
 
37
38
  # Creates a job by placing it on a queue. Expects a string queue
@@ -210,14 +211,17 @@ module Resque
210
211
  @after_hooks ||= Plugin.after_hooks(payload_class)
211
212
  end
212
213
 
213
- def failure_hooks
214
+ def failure_hooks
214
215
  @failure_hooks ||= Plugin.failure_hooks(payload_class)
215
216
  end
216
-
217
+
217
218
  def run_failure_hooks(exception)
218
- job_args = args || []
219
- failure_hooks.each { |hook| payload_class.send(hook, exception, *job_args) }
219
+ begin
220
+ job_args = args || []
221
+ failure_hooks.each { |hook| payload_class.send(hook, exception, *job_args) } unless @failure_hooks_ran
222
+ ensure
223
+ @failure_hooks_ran = true
224
+ end
220
225
  end
221
-
222
226
  end
223
227
  end
@@ -0,0 +1,26 @@
1
+ module UTF8Util
2
+ # use '?' intsead of the unicode replace char, since that is 3 bytes
3
+ # and can increase the string size if it's done a lot
4
+ REPLACEMENT_CHAR = "?"
5
+
6
+ # Replace invalid UTF-8 character sequences with a replacement character
7
+ #
8
+ # Returns self as valid UTF-8.
9
+ def self.clean!(str)
10
+ raise NotImplementedError
11
+ end
12
+
13
+ # Replace invalid UTF-8 character sequences with a replacement character
14
+ #
15
+ # Returns a copy of this String as valid UTF-8.
16
+ def self.clean(str)
17
+ clean!(str.dup)
18
+ end
19
+
20
+ end
21
+
22
+ if RUBY_VERSION <= '1.9'
23
+ require 'resque/vendor/utf8_util/utf8_util_18'
24
+ else
25
+ require 'resque/vendor/utf8_util/utf8_util_19'
26
+ end
@@ -0,0 +1,91 @@
1
+ require 'strscan'
2
+
3
+ module UTF8Util
4
+ HIGH_BIT_RANGE = /[\x80-\xff]/
5
+
6
+ # Check if this String is valid UTF-8
7
+ #
8
+ # Returns true or false.
9
+ def self.valid?(str)
10
+ sc = StringScanner.new(str)
11
+
12
+ while sc.skip_until(HIGH_BIT_RANGE)
13
+ sc.pos -= 1
14
+
15
+ if !sequence_length(sc)
16
+ return false
17
+ end
18
+ end
19
+
20
+ true
21
+ end
22
+
23
+ # Replace invalid UTF-8 character sequences with a replacement character
24
+ #
25
+ # Returns self as valid UTF-8.
26
+ def self.clean!(str)
27
+ sc = StringScanner.new(str)
28
+ while sc.skip_until(HIGH_BIT_RANGE)
29
+ pos = sc.pos = sc.pos-1
30
+
31
+ if !sequence_length(sc)
32
+ str[pos] = REPLACEMENT_CHAR
33
+ end
34
+ end
35
+
36
+ str
37
+ end
38
+
39
+ # Validate the UTF-8 sequence at the current scanner position.
40
+ #
41
+ # scanner - StringScanner instance so we can advance the pointer as we verify.
42
+ #
43
+ # Returns The length in bytes of this UTF-8 sequence, false if invalid.
44
+ def self.sequence_length(scanner)
45
+ leader = scanner.get_byte[0]
46
+
47
+ if (leader >> 5) == 0x6
48
+ if check_next_sequence(scanner)
49
+ return 2
50
+ else
51
+ scanner.pos -= 1
52
+ end
53
+ elsif (leader >> 4) == 0x0e
54
+ if check_next_sequence(scanner)
55
+ if check_next_sequence(scanner)
56
+ return 3
57
+ else
58
+ scanner.pos -= 2
59
+ end
60
+ else
61
+ scanner.pos -= 1
62
+ end
63
+ elsif (leader >> 3) == 0x1e
64
+ if check_next_sequence(scanner)
65
+ if check_next_sequence(scanner)
66
+ if check_next_sequence(scanner)
67
+ return 4
68
+ else
69
+ scanner.pos -= 3
70
+ end
71
+ else
72
+ scanner.pos -= 2
73
+ end
74
+ else
75
+ scanner.pos -= 1
76
+ end
77
+ end
78
+
79
+ false
80
+ end
81
+
82
+ private
83
+
84
+ # Read another byte off the scanner oving the scan position forward one place
85
+ #
86
+ # Returns nothing.
87
+ def self.check_next_sequence(scanner)
88
+ byte = scanner.get_byte[0]
89
+ (byte >> 6) == 0x2
90
+ end
91
+ end
@@ -0,0 +1,5 @@
1
+ module UTF8Util
2
+ def self.clean!(str)
3
+ str.force_encoding("binary").encode("UTF-8", :invalid => :replace, :undef => :replace, :replace => REPLACEMENT_CHAR)
4
+ end
5
+ end
@@ -1,3 +1,3 @@
1
1
  module Resque
2
- Version = VERSION = '1.20.0'
2
+ Version = VERSION = '1.21.0'
3
3
  end
@@ -89,6 +89,8 @@ module Resque
89
89
  # removed without needing to restart workers using this method.
90
90
  def initialize(*queues)
91
91
  @queues = queues.map { |queue| queue.to_s.strip }
92
+ @shutdown = nil
93
+ @paused = nil
92
94
  validate_queues
93
95
  end
94
96
 
@@ -138,6 +140,7 @@ module Resque
138
140
  Process.wait(@child)
139
141
  else
140
142
  procline "Processing #{job.queue} since #{Time.now.to_i}"
143
+ redis.client.reconnect # Don't share connection with parent
141
144
  perform(job, &block)
142
145
  exit! unless @cant_fork
143
146
  end
@@ -0,0 +1,23 @@
1
+ require 'test_helper'
2
+ require 'resque/failure/redis'
3
+
4
+ context "Resque::Failure::Redis" do
5
+ setup do
6
+ @bad_string = [39, 250, 141, 168, 138, 191, 52, 211, 159, 86, 93, 95, 39].map { |c| c.chr }.join
7
+ exception = StandardError.exception(@bad_string)
8
+ worker = Resque::Worker.new(:test)
9
+ queue = "queue"
10
+ payload = { "class" => Object, "args" => 3 }
11
+ @redis_backend = Resque::Failure::Redis.new(exception, worker, queue, payload)
12
+ end
13
+
14
+ test 'cleans up bad strings before saving the failure, in order to prevent errors on the resque UI' do
15
+ # test assumption: the bad string should not be able to round trip though JSON
16
+ assert_raises(MultiJson::DecodeError) {
17
+ MultiJson.decode(MultiJson.encode(@bad_string))
18
+ }
19
+
20
+ @redis_backend.save
21
+ Resque::Failure::Redis.all # should not raise an error
22
+ end
23
+ end
@@ -1,13 +1,13 @@
1
1
  require 'rubygems'
2
- require 'bundler'
3
- Bundler.setup(:default, :test)
4
- Bundler.require(:default, :test)
5
2
 
6
3
  dir = File.dirname(File.expand_path(__FILE__))
7
4
  $LOAD_PATH.unshift dir + '/../lib'
8
5
  $TESTING = true
9
6
  require 'test/unit'
10
7
 
8
+ require 'redis/namespace'
9
+ require 'resque'
10
+
11
11
  begin
12
12
  require 'leftright'
13
13
  rescue LoadError
@@ -147,12 +147,14 @@ end
147
147
  class Time
148
148
  # Thanks, Timecop
149
149
  class << self
150
+ attr_accessor :fake_time
151
+
150
152
  alias_method :now_without_mock_time, :now
151
153
 
152
- def now_with_mock_time
153
- $fake_time || now_without_mock_time
154
+ def now
155
+ fake_time || now_without_mock_time
154
156
  end
155
-
156
- alias_method :now, :now_with_mock_time
157
157
  end
158
+
159
+ self.fake_time = nil
158
160
  end
@@ -43,7 +43,7 @@ context "Resque::Worker" do
43
43
  def self.on_failure_record_failure(exception, *job_args)
44
44
  @@exception = exception
45
45
  end
46
-
46
+
47
47
  def self.exception
48
48
  @@exception
49
49
  end
@@ -57,6 +57,29 @@ context "Resque::Worker" do
57
57
  assert(SimpleJobWithFailureHandling.exception.kind_of?(Resque::DirtyExit))
58
58
  end
59
59
 
60
+ class ::SimpleFailingJob
61
+ @@exception_count = 0
62
+
63
+ def self.on_failure_record_failure(exception, *job_args)
64
+ @@exception_count += 1
65
+ end
66
+
67
+ def self.exception_count
68
+ @@exception_count
69
+ end
70
+
71
+ def self.perform
72
+ raise Exception.new
73
+ end
74
+ end
75
+
76
+ test "only calls failure hook once on exception" do
77
+ job = Resque::Job.new(:jobs, {'class' => 'SimpleFailingJob', 'args' => ""})
78
+ @worker.perform(job)
79
+ assert_equal 1, Resque::Failure.count
80
+ assert_equal 1, SimpleFailingJob.exception_count
81
+ end
82
+
60
83
  test "can peek at failed jobs" do
61
84
  10.times { Resque::Job.create(:jobs, BadJob) }
62
85
  @worker.work(0)
@@ -355,16 +378,22 @@ context "Resque::Worker" do
355
378
  end
356
379
 
357
380
  test "very verbose works in the afternoon" do
358
- require 'time'
359
- $last_puts = ""
360
- $fake_time = Time.parse("15:44:33 2011-03-02")
361
- singleton = class << @worker; self end
362
- singleton.send :define_method, :puts, lambda { |thing| $last_puts = thing }
381
+ begin
382
+ require 'time'
383
+ last_puts = ""
384
+ Time.fake_time = Time.parse("15:44:33 2011-03-02")
363
385
 
364
- @worker.very_verbose = true
365
- @worker.log("some log text")
386
+ @worker.extend(Module.new {
387
+ define_method(:puts) { |thing| last_puts = thing }
388
+ })
366
389
 
367
- assert_match /\*\* \[15:44:33 2011-03-02\] \d+: some log text/, $last_puts
390
+ @worker.very_verbose = true
391
+ @worker.log("some log text")
392
+
393
+ assert_match /\*\* \[15:44:33 2011-03-02\] \d+: some log text/, last_puts
394
+ ensure
395
+ Time.fake_time = nil
396
+ end
368
397
  end
369
398
 
370
399
  test "Will call an after_fork hook after forking" do
@@ -382,7 +411,7 @@ context "Resque::Worker" do
382
411
  test "returns PID of running process" do
383
412
  assert_equal @worker.to_s.split(":")[1].to_i, @worker.pid
384
413
  end
385
-
414
+
386
415
  test "requeue failed queue" do
387
416
  queue = 'good_job'
388
417
  Resque::Failure.create(:exception => Exception.new, :worker => Resque::Worker.new(queue), :queue => queue, :payload => {'class' => 'GoodJob'})
@@ -402,4 +431,10 @@ context "Resque::Worker" do
402
431
  assert_equal queue2, Resque::Failure.all(0)['queue']
403
432
  assert_equal 1, Resque::Failure.count
404
433
  end
434
+
435
+ test "reconnects to redis after fork" do
436
+ original_connection = Resque.redis.client.connection.instance_variable_get("@sock")
437
+ @worker.work(0)
438
+ assert_not_equal original_connection, Resque.redis.client.connection.instance_variable_get("@sock")
439
+ end
405
440
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resque
3
3
  version: !ruby/object:Gem::Version
4
- hash: 71
4
+ hash: 67
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
- - 20
8
+ - 21
9
9
  - 0
10
- version: 1.20.0
10
+ version: 1.21.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Chris Wanstrath
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2012-02-17 00:00:00 Z
19
+ date: 2012-07-03 00:00:00 Z
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
22
  name: redis-namespace
@@ -26,12 +26,11 @@ dependencies:
26
26
  requirements:
27
27
  - - ~>
28
28
  - !ruby/object:Gem::Version
29
- hash: 19
29
+ hash: 15
30
30
  segments:
31
31
  - 1
32
32
  - 0
33
- - 2
34
- version: 1.0.2
33
+ version: "1.0"
35
34
  type: :runtime
36
35
  version_requirements: *id001
37
36
  - !ruby/object:Gem::Dependency
@@ -127,6 +126,9 @@ files:
127
126
  - lib/resque/plugin.rb
128
127
  - lib/resque/server.rb
129
128
  - lib/resque/helpers.rb
129
+ - lib/resque/vendor/utf8_util.rb
130
+ - lib/resque/vendor/utf8_util/utf8_util_19.rb
131
+ - lib/resque/vendor/utf8_util/utf8_util_18.rb
130
132
  - lib/resque/tasks.rb
131
133
  - lib/resque/stat.rb
132
134
  - lib/resque/failure/hoptoad.rb
@@ -141,6 +143,7 @@ files:
141
143
  - test/airbrake_test.rb
142
144
  - test/job_hooks_test.rb
143
145
  - test/plugin_test.rb
146
+ - test/resque_failure_redis_test.rb
144
147
  - test/redis-test-cluster.conf
145
148
  - test/resque-web_test.rb
146
149
  - test/redis-test.conf
@@ -178,7 +181,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
178
181
  requirements: []
179
182
 
180
183
  rubyforge_project:
181
- rubygems_version: 1.8.11
184
+ rubygems_version: 1.8.21
182
185
  signing_key:
183
186
  specification_version: 3
184
187
  summary: Resque is a Redis-backed queueing system.