tresque 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,201 @@
1
+ require 'tresque/resque_spec/ext'
2
+ require 'tresque/resque_spec/helpers'
3
+ require 'tresque/resque_spec/matchers'
4
+
5
+ module ResqueSpec
6
+
7
+ # methods to be made accessible for every spec
8
+ module BaseMethods
9
+
10
+ def run_resque_workers(options = {})
11
+ quantity = options.fetch(:quantity, 1)
12
+
13
+ quantity.times do
14
+ ResqueSpec.perform_all!
15
+ end
16
+ end
17
+
18
+ end
19
+
20
+
21
+ include ::Resque::Helpers
22
+ extend self
23
+
24
+ attr_accessor :inline
25
+ attr_accessor :disable_ext
26
+
27
+ def dequeue(queue_name, klass, *args)
28
+ queue_by_name(queue_name).delete_if do |job|
29
+ job[:class] == klass.to_s && args.empty? || job[:args] == args
30
+ end
31
+ end
32
+
33
+ def enqueue(queue_name, klass, *args)
34
+ perform_or_store(queue_name, :class => klass.to_s, :args => args)
35
+ end
36
+
37
+ def perform_next(queue_name)
38
+ perform(queue_name, shift_queue(queue_name))
39
+ end
40
+
41
+ def perform_all(queue_name)
42
+ while job = shift_queue(queue_name)
43
+ perform(queue_name, job)
44
+ end
45
+ end
46
+
47
+ def shift_queue(queue_name)
48
+ # pass scheduled ones
49
+ array = queue_by_name(queue_name) || []
50
+ index = nil
51
+ array.each_with_index do |hash, i|
52
+ if hash[:time].to_i <= Time.now.to_i
53
+ index = i
54
+ break
55
+ end
56
+ end
57
+ return nil unless index
58
+ array.delete_at(index)
59
+ end
60
+
61
+ def pop(queue_name)
62
+ return unless payload = shift_queue(queue_name)
63
+ new_job(queue_name, payload)
64
+ end
65
+
66
+ def queue_by_name(name)
67
+ queues[name.to_s]
68
+ end
69
+
70
+ def queue_for(klass)
71
+ queue_by_name(queue_name(klass))
72
+ end
73
+
74
+ def peek(queue_name, start = 0, count = 1)
75
+ queue_by_name(queue_name).slice(start, count)
76
+ end
77
+
78
+ def queue_name(klass)
79
+ if klass.is_a?(String)
80
+ klass = Kernel.const_get(klass) rescue nil
81
+ end
82
+
83
+ name_from_instance_var(klass) or
84
+ name_from_queue_accessor(klass) or
85
+ raise ::Resque::NoQueueError.new("Jobs must be placed onto a queue.")
86
+ end
87
+
88
+ def queues
89
+ @queues ||= Hash.new {|h,k| h[k] = []}
90
+ end
91
+
92
+ def delete_all(queue_name)
93
+ queue = "queue:#{queue_name}"
94
+ Resque.redis.del(queue)
95
+ reset!
96
+ end
97
+
98
+ def perform_all!
99
+ ::TResque::Registry.queues.each do |queue_name|
100
+ ResqueSpec.perform_all(queue_name)
101
+ end
102
+ end
103
+
104
+ # Check if we have queued a delayed worker for `klass` with `*args`
105
+ # Very slow, checks every `delayed` key
106
+ def delayed?(klass, *args)
107
+ klass = klass.to_s unless klass.is_a? String
108
+ [*Resque.redis.keys("delayed:*")].each do |key|
109
+ [*Resque.redis.lrange(key, 0, -1)].each do |item|
110
+ parsed_item = JSON.parse(item)
111
+ return true if parsed_item['class'] == klass && parsed_item['args'] == [*args]
112
+ end
113
+ end
114
+
115
+ false
116
+ end
117
+
118
+ # Get back the key of the delayed worker if it exists
119
+ def delayed_key(klass, *args)
120
+ klass = klass.to_s unless klass.is_a? String
121
+ [*Resque.redis.keys("delayed:*")].each do |key|
122
+ [*Resque.redis.lrange(key, 0, -1)].each do |item|
123
+ parsed_item = JSON.parse(item)
124
+ return key if parsed_item['class'] == klass && parsed_item['args'] == [*args]
125
+ end
126
+ end
127
+
128
+ nil
129
+ end
130
+
131
+ # check if the worker has a lock with the provided args
132
+ def locked?(klass, *args)
133
+ klass = klass.to_s unless klass.is_a? String
134
+
135
+ key = "lock:#{klass}-#{[*args].join('-')}"
136
+ Resque.redis.keys(key).present?
137
+ end
138
+
139
+ def clear_locked!
140
+ [*Resque.redis.keys("lock:*")].each do |key|
141
+ Resque.redis.del(key)
142
+ end
143
+ end
144
+
145
+ def clear_all!
146
+ [*Resque.redis.keys].each do |key|
147
+ Resque.redis.del(key)
148
+ end
149
+ end
150
+
151
+ def reset!
152
+ clear_all!
153
+ queues.clear
154
+ self.inline = false
155
+ end
156
+
157
+ private
158
+
159
+ def name_from_instance_var(klass)
160
+ klass.instance_variable_get(:@queue)
161
+ end
162
+
163
+ def name_from_queue_accessor(klass)
164
+ klass.respond_to?(:queue) and klass.queue
165
+ end
166
+
167
+ def new_job(queue_name, payload)
168
+ Resque::Job.new(queue_name, payload_with_string_keys(payload))
169
+ end
170
+
171
+ def perform(queue_name, payload)
172
+ prev = ENV['QUEUE']
173
+ ENV['QUEUE'] = queue_name
174
+ new_job(queue_name, payload).perform
175
+ ensure
176
+ ENV['QUEUE'] = prev
177
+ end
178
+
179
+ def perform_or_store(queue_name, payload)
180
+ if inline
181
+ perform(queue_name, payload)
182
+ else
183
+ store(queue_name, payload)
184
+ end
185
+ end
186
+
187
+ def store(queue_name, payload)
188
+ queue_by_name(queue_name) << payload
189
+ end
190
+
191
+ def payload_with_string_keys(payload)
192
+ {
193
+ 'class' => payload[:class],
194
+ 'args' => decode(encode(payload[:args])),
195
+ 'stored_at' => payload[:stored_at]
196
+ }
197
+ end
198
+ end
199
+
200
+ config = RSpec.configuration
201
+ config.include ::ResqueSpec::Helpers
@@ -0,0 +1,93 @@
1
+ require_relative 'resque_spec'
2
+
3
+ module ResqueSpec
4
+ module SchedulerExt
5
+ def self.extended(klass)
6
+ if klass.respond_to? :enqueue_at
7
+ klass.instance_eval do
8
+ alias :enqueue_at_without_resque_spec :enqueue_at
9
+ alias :enqueue_in_without_resque_spec :enqueue_in
10
+ alias :remove_delayed_without_resque_spec :remove_delayed
11
+ end
12
+ end
13
+ klass.extend(ResqueSpec::SchedulerExtMethods)
14
+ end
15
+ end
16
+
17
+ module SchedulerExtMethods
18
+ def enqueue_at(time, klass, *args)
19
+ return enqueue_at_without_resque_spec(time, klass, *args) if ResqueSpec.disable_ext && respond_to?(:enqueue_at_without_resque_spec)
20
+
21
+ ResqueSpec.enqueue_at(time, klass, *args)
22
+ end
23
+
24
+ def enqueue_at_with_queue(queue, time, klass, *args)
25
+ return enqueue_at_with_queue_without_resque_spec(time, klass, *args) if ResqueSpec.disable_ext && respond_to?(:enqueue_at_with_queue_without_resque_spec)
26
+
27
+ ResqueSpec.enqueue_at_with_queue(queue, time, klass, *args)
28
+ end
29
+
30
+ def enqueue_in(time, klass, *args)
31
+ return enqueue_in_without_resque_spec(time, klass, *args) if ResqueSpec.disable_ext && respond_to?(:enqueue_in_without_resque_spec)
32
+
33
+ ResqueSpec.enqueue_in(time, klass, *args)
34
+ end
35
+
36
+ def enqueue_in_with_queue(queue, time, klass, *args)
37
+ return enqueue_in_with_queue_without_resque_spec(time, klass, *args) if ResqueSpec.disable_ext && respond_to?(:enqueue_in_with_queue_without_resque_spec)
38
+
39
+ ResqueSpec.enqueue_in_with_queue(queue, time, klass, *args)
40
+ end
41
+
42
+ def remove_delayed(klass, *args)
43
+ return remove_delayed_without_resque_spec(klass, *args) if ResqueSpec.disable_ext && respond_to?(:remove_delayed_without_resque_spec)
44
+
45
+ ResqueSpec.remove_delayed(klass, *args)
46
+ end
47
+ end
48
+
49
+ def enqueue_at(time, klass, *args)
50
+ enqueue_at_with_queue(schedule_queue_name(klass), time, klass, *args)
51
+ end
52
+
53
+ def enqueue_at_with_queue(queue, time, klass, *args)
54
+ is_time?(time)
55
+ perform_or_store(queue, :class => klass.to_s, :time => time, :stored_at => Time.now, :args => args)
56
+ end
57
+
58
+ def enqueue_in(time, klass, *args)
59
+ enqueue_at(Time.now + time, klass, *args)
60
+ end
61
+
62
+ def enqueue_in_with_queue(queue, time, klass, *args)
63
+ enqueue_at_with_queue(queue, Time.now + time, klass, *args)
64
+ end
65
+
66
+ def remove_delayed(klass, *args)
67
+ sched_queue = queue_by_name(schedule_queue_name(klass))
68
+ count_before_remove = sched_queue.length
69
+ sched_queue.delete_if do |job|
70
+ job[:class] == klass.to_s && job[:args] == args
71
+ end
72
+ # Return number of removed items to match Resque Scheduler behaviour
73
+ count_before_remove - sched_queue.length
74
+ end
75
+
76
+ def schedule_for(klass)
77
+ queue_by_name(schedule_queue_name(klass))
78
+ end
79
+
80
+ private
81
+
82
+ def is_time?(time)
83
+ time.to_i
84
+ end
85
+
86
+ def schedule_queue_name(klass)
87
+ # "#{queue_name(klass)}_scheduled"
88
+ # we use the real one
89
+ queue_name(klass)
90
+ end
91
+ end
92
+
93
+ Resque.extend(ResqueSpec::SchedulerExt)
@@ -0,0 +1,2 @@
1
+ require 'tresque/resque_spec/resque_spec'
2
+ require 'tresque/resque_spec/scheduler'
@@ -0,0 +1,27 @@
1
+ module TResque
2
+ module Spec
3
+ module Delay
4
+
5
+ def delay_object
6
+ TResque::Delay::InvocationProxy.any_instance
7
+ end
8
+
9
+ def without_delay
10
+ return yield unless Rails.env.test?
11
+
12
+ q = ENV['QUEUE']
13
+ qs = ENV['QUEUES']
14
+
15
+ ENV['QUEUE'] = "v3_default"
16
+ ENV['QUEUES'] = nil
17
+
18
+ yield
19
+
20
+ ensure
21
+ ENV['QUEUE'] = q
22
+ ENV['QUEUES'] = qs
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,16 @@
1
+ module TResque
2
+ module Util
3
+ extend self
4
+
5
+ def normalize(val)
6
+ val.to_s.gsub(/\W/, "_").downcase
7
+ end
8
+
9
+ def calculate_namespace_from_class(klass)
10
+ klass = klass.class unless klass.is_a?(Class)
11
+ pieces = klass.name.to_s.split("::")
12
+ return "worker" if pieces.size < 2
13
+ pieces.first.underscore
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module TResque
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,228 @@
1
+ require 'resque-retry'
2
+ require 'digest/sha1'
3
+
4
+ module TResque
5
+ module Worker
6
+ extend ::ActiveSupport::Concern
7
+
8
+ mattr_accessor :skip_check_queues
9
+
10
+ included do
11
+ extend ::Resque::Plugins::ExponentialBackoff
12
+ TResque::Registry.worker(self)
13
+ end
14
+
15
+ module ClassMethods
16
+ def queue(name=nil)
17
+ return @requeue_in_queue if !name && @requeue_in_queue
18
+ return @queue if !name && @queue
19
+ name ||= :default
20
+ full_queue("#{app_key}_#{name}")
21
+ end
22
+
23
+ def full_queue(name)
24
+ @queue = name.to_s
25
+ end
26
+
27
+ def application(app_key)
28
+ @app_key = Util.normalize(app_key)
29
+ end
30
+
31
+ def app_key
32
+ return @app_key if @app_key
33
+ @app_key = Util.calculate_namespace_from_class(self)
34
+ end
35
+
36
+ def enqueue(options = {})
37
+ options = options.with_indifferent_access
38
+
39
+ options[:locale] ||= I18n.locale.to_s
40
+ options[:tz] ||= Time.zone.name
41
+
42
+ run_at = options.delete(:run_at)
43
+ if options[:full_queue]
44
+ queue_name = options[:full_queue]
45
+ elsif options[:queue] || options[:queue_namespace]
46
+ namespace = options[:queue_namespace] || self.app_key
47
+ queue = options[:queue] || "default"
48
+ queue_name = "#{namespace}_#{queue}"
49
+ else
50
+ queue_name = self.queue
51
+ end
52
+
53
+ if queue_name == "t_resque_default"
54
+ message = "QUEUE_ERROR (#{self.class.name}): #{queue_name} will not be worked!"
55
+ Rails.logger.error(message)
56
+ puts message if Rails.env.test?
57
+ end
58
+
59
+ if !TResque::Worker.skip_check_queues && !TResque::Registry.queues.include?(queue_name)
60
+ message = "QUEUE_ERROR (#{self.class.name}): #{queue_name} will not be worked!"
61
+ Rails.logger.error(message)
62
+ puts message if Rails.env.test?
63
+ end
64
+
65
+ options[:full_queue] = queue_name
66
+ if run_at
67
+ Resque.enqueue_at_with_queue(queue_name, run_at, self, options)
68
+ else
69
+ Resque.enqueue_to(queue_name, self, options)
70
+ end
71
+
72
+ # too many events
73
+ # QueueBus.publish_log(:worker_enqueued, {
74
+ # options: options,
75
+ # queue_name: queue_name,
76
+ # worker_name: self.name.to_s,
77
+ # run_at: run_at.to_i
78
+ # }) unless Rails.env.test?
79
+
80
+ options
81
+ end
82
+
83
+ def perform(options)
84
+ Waistband.clear_logs if Waistband.config.logging
85
+
86
+ @previous_locale, @previous_zone = I18n.locale, Time.zone
87
+
88
+ options = options.with_indifferent_access
89
+ obj = self.new(options.except(:locale, :tz, :bus_locale, :bus_timezone))
90
+
91
+ locale = obj.respond_to?(:calculate_locale, true) ? obj.send(:calculate_locale) : nil
92
+ locale ||= options[:locale]
93
+ locale ||= options[:bus_locale]
94
+ locale ||= I18n.locale if Rails.env.production? # don't crash in production, use default
95
+
96
+ zone = obj.respond_to?(:calculate_timezone, true) ? obj.send(:calculate_timezone) : nil
97
+ zone ||= options[:tz]
98
+ zone ||= options[:bus_timezone]
99
+
100
+ I18n.locale = locale
101
+ Time.zone = zone
102
+
103
+ # too many events
104
+ # QueueBus.publish_log(:worker_perform, {
105
+ # options: options,
106
+ # worker_name: self.name.to_s,
107
+ # locale: locale,
108
+ # time_zone: zone
109
+ # }) unless Rails.env.test?
110
+
111
+ obj.worker_perform
112
+ rescue Resque::Job::DontPerform
113
+ # it's cool
114
+ ensure
115
+ # write waistband logs
116
+ Waistband.write_logs(nil) if Waistband.config.logging
117
+ # reset
118
+ I18n.locale, Time.zone = @previous_locale, @previous_zone
119
+ end
120
+
121
+ def inputs(*args)
122
+ args.each do |name|
123
+ define_method name do
124
+ enqueued_options[name]
125
+ end
126
+ end
127
+ end
128
+ alias_method :input, :inputs
129
+
130
+ def turn_retry_off
131
+ @retry_limit = 0
132
+ end
133
+
134
+ def lock_namespace(val)
135
+ @lock_namespace = val.to_s
136
+ end
137
+
138
+ def worker_lock(*args)
139
+ raise ("worker_lock: what should i lock on?") if args.size == 0
140
+ extend ::TResque::WorkerLock
141
+ @worker_lock_attributes = args.collect(&:to_s)
142
+ end
143
+
144
+ def queue_lock(*args)
145
+ raise ("queue_lock: what should i lock on?") if args.size == 0
146
+ extend ::TResque::QueueLock
147
+ @queue_lock_attributes = args.collect(&:to_s)
148
+ end
149
+
150
+ def get_lock_namespace(options)
151
+ @lock_namespace ||= self.name
152
+ end
153
+
154
+ def get_queue_lock_attributes(options)
155
+ @queue_lock_attributes ||= []
156
+ end
157
+
158
+ def get_worker_lock_attributes(options)
159
+ @worker_lock_attributes ||= []
160
+ end
161
+
162
+ def queue_lock_key(options)
163
+ options_lock_key(options, get_queue_lock_attributes(options))
164
+ end
165
+
166
+ def worker_lock_key(options)
167
+ options_lock_key(options, get_worker_lock_attributes(options))
168
+ end
169
+
170
+ def options_lock_key(options, keys)
171
+ return nil unless keys # not actually locking
172
+
173
+ keys = ["all"] if keys.size == 0
174
+ keys = options.keys if keys.size == 1 && keys.first == "all"
175
+ keys.sort!
176
+
177
+ vals = [get_lock_namespace(options)]
178
+ keys.each do |key|
179
+ vals << key
180
+ vals << options[key].to_s
181
+ end
182
+ Digest::SHA1.hexdigest(vals.join("-"))
183
+ end
184
+
185
+ # make sure we put it back in the same queue
186
+ # @failure_hooks_already_ran on https://github.com/defunkt/resque/tree/1-x-stable
187
+ # to prevent running twice
188
+ def on_failure_aaa(exception, *args)
189
+ # note: sorted alphabetically
190
+ # queue needs to be set for rety to work (know what queue in Requeue.class_to_queue)
191
+ @requeue_in_queue = args[0]["full_queue"]
192
+ end
193
+
194
+ def on_failure_zzz(exception, *args)
195
+ # note: sorted alphabetically
196
+ @requeue_in_queue = nil
197
+ end
198
+ end
199
+
200
+ attr_reader :enqueued_options
201
+ def initialize(options = {})
202
+ @enqueued_options = options.with_indifferent_access
203
+ end
204
+
205
+ def worker_perform
206
+ to_call = self.enqueued_options[:action] || :work
207
+ send(to_call)
208
+ end
209
+
210
+ def requeue_delay_seconds
211
+ 1
212
+ end
213
+
214
+ def requeue
215
+ self.enqueued_options["run_at"] = nil
216
+ delay = self.requeue_delay_seconds
217
+ if delay > 0
218
+ self.enqueued_options["run_at"] = delay.seconds.from_now
219
+ end
220
+ self.class.enqueue(self.enqueued_options)
221
+ end
222
+
223
+ def requeue!
224
+ requeue
225
+ raise Resque::Job::DontPerform
226
+ end
227
+ end
228
+ end