delayed_job_unique_key 0.0.4 → 0.1.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/lib/delayed_job_unique_key.rb +8 -0
- data/lib/delayed_job_unique_key/active_record_job.rb +12 -0
- data/lib/delayed_job_unique_key/base.rb +52 -0
- data/lib/delayed_job_unique_key/version.rb +3 -0
- data/lib/generators/delayed_job_unique_key/install_generator.rb +17 -0
- data/lib/generators/delayed_job_unique_key/templates/add_unique_key_migration.rb +11 -0
- metadata +63 -174
- data/MIT-LICENSE +0 -20
- data/README.textile +0 -246
- data/contrib/delayed_job.monitrc +0 -14
- data/contrib/delayed_job_multiple.monitrc +0 -23
- data/lib/delayed/backend/base.rb +0 -152
- data/lib/delayed/backend/shared_spec.rb +0 -566
- data/lib/delayed/command.rb +0 -101
- data/lib/delayed/deserialization_error.rb +0 -4
- data/lib/delayed/lifecycle.rb +0 -84
- data/lib/delayed/message_sending.rb +0 -54
- data/lib/delayed/performable_mailer.rb +0 -21
- data/lib/delayed/performable_method.rb +0 -33
- data/lib/delayed/plugin.rb +0 -15
- data/lib/delayed/plugins/clear_locks.rb +0 -15
- data/lib/delayed/psych_ext.rb +0 -75
- data/lib/delayed/railtie.rb +0 -16
- data/lib/delayed/recipes.rb +0 -50
- data/lib/delayed/serialization/active_record.rb +0 -19
- data/lib/delayed/syck_ext.rb +0 -34
- data/lib/delayed/tasks.rb +0 -11
- data/lib/delayed/worker.rb +0 -222
- data/lib/delayed/yaml_ext.rb +0 -10
- data/lib/delayed_job.rb +0 -22
- data/lib/generators/delayed_job/delayed_job_generator.rb +0 -11
- data/lib/generators/delayed_job/templates/script +0 -5
- data/recipes/delayed_job.rb +0 -1
- data/spec/autoloaded/clazz.rb +0 -7
- data/spec/autoloaded/instance_clazz.rb +0 -6
- data/spec/autoloaded/instance_struct.rb +0 -6
- data/spec/autoloaded/struct.rb +0 -7
- data/spec/delayed/backend/test.rb +0 -113
- data/spec/delayed/serialization/test.rb +0 -0
- data/spec/fixtures/bad_alias.yml +0 -1
- data/spec/lifecycle_spec.rb +0 -107
- data/spec/message_sending_spec.rb +0 -116
- data/spec/performable_mailer_spec.rb +0 -46
- data/spec/performable_method_spec.rb +0 -89
- data/spec/sample_jobs.rb +0 -75
- data/spec/spec_helper.rb +0 -45
- data/spec/test_backend_spec.rb +0 -13
- data/spec/worker_spec.rb +0 -19
- data/spec/yaml_ext_spec.rb +0 -41
@@ -1,19 +0,0 @@
|
|
1
|
-
if defined?(ActiveRecord)
|
2
|
-
class ActiveRecord::Base
|
3
|
-
yaml_as "tag:ruby.yaml.org,2002:ActiveRecord"
|
4
|
-
|
5
|
-
def self.yaml_new(klass, tag, val)
|
6
|
-
if ActiveRecord::VERSION::MAJOR == 3
|
7
|
-
klass.unscoped.find(val['attributes'][klass.primary_key])
|
8
|
-
else # Rails 2
|
9
|
-
klass.with_exclusive_scope { klass.find(val['attributes'][klass.primary_key]) }
|
10
|
-
end
|
11
|
-
rescue ActiveRecord::RecordNotFound
|
12
|
-
raise Delayed::DeserializationError
|
13
|
-
end
|
14
|
-
|
15
|
-
def to_yaml_properties
|
16
|
-
['@attributes']
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
data/lib/delayed/syck_ext.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
class Module
|
2
|
-
yaml_as "tag:ruby.yaml.org,2002:module"
|
3
|
-
|
4
|
-
def self.yaml_new(klass, tag, val)
|
5
|
-
klass
|
6
|
-
end
|
7
|
-
|
8
|
-
def to_yaml(options = {})
|
9
|
-
YAML.quick_emit(nil, options) do |out|
|
10
|
-
out.scalar(taguri, name, :plain)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def yaml_tag_read_class(name)
|
15
|
-
# Constantize the object so that ActiveSupport can attempt
|
16
|
-
# its auto loading magic. Will raise LoadError if not successful.
|
17
|
-
name.constantize
|
18
|
-
name
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
class Class
|
23
|
-
yaml_as "tag:ruby.yaml.org,2002:class"
|
24
|
-
remove_method :to_yaml if respond_to?(:to_yaml) && method(:to_yaml).owner == Class # use Module's to_yaml
|
25
|
-
end
|
26
|
-
|
27
|
-
class Struct
|
28
|
-
def self.yaml_tag_read_class(name)
|
29
|
-
# Constantize the object so that ActiveSupport can attempt
|
30
|
-
# its auto loading magic. Will raise LoadError if not successful.
|
31
|
-
name.constantize
|
32
|
-
"Struct::#{ name }"
|
33
|
-
end
|
34
|
-
end
|
data/lib/delayed/tasks.rb
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
namespace :jobs do
|
2
|
-
desc "Clear the delayed_job queue."
|
3
|
-
task :clear => :environment do
|
4
|
-
Delayed::Job.delete_all
|
5
|
-
end
|
6
|
-
|
7
|
-
desc "Start a delayed_job worker."
|
8
|
-
task :work => :environment do
|
9
|
-
Delayed::Worker.new(:min_priority => ENV['MIN_PRIORITY'], :max_priority => ENV['MAX_PRIORITY'], :queues => (ENV['QUEUES'] || ENV['QUEUE'] || '').split(','), :quiet => false).start
|
10
|
-
end
|
11
|
-
end
|
data/lib/delayed/worker.rb
DELETED
@@ -1,222 +0,0 @@
|
|
1
|
-
require 'timeout'
|
2
|
-
require 'active_support/core_ext/numeric/time'
|
3
|
-
require 'active_support/core_ext/class/attribute_accessors'
|
4
|
-
require 'active_support/core_ext/kernel'
|
5
|
-
require 'active_support/core_ext/enumerable'
|
6
|
-
require 'logger'
|
7
|
-
|
8
|
-
module Delayed
|
9
|
-
class Worker
|
10
|
-
cattr_accessor :min_priority, :max_priority, :max_attempts, :max_run_time, :default_priority, :sleep_delay, :logger, :delay_jobs, :queues
|
11
|
-
self.sleep_delay = 5
|
12
|
-
self.max_attempts = 25
|
13
|
-
self.max_run_time = 4.hours
|
14
|
-
self.default_priority = 0
|
15
|
-
self.delay_jobs = true
|
16
|
-
self.queues = []
|
17
|
-
|
18
|
-
# Add or remove plugins in this list before the worker is instantiated
|
19
|
-
cattr_accessor :plugins
|
20
|
-
self.plugins = [Delayed::Plugins::ClearLocks]
|
21
|
-
|
22
|
-
# By default failed jobs are destroyed after too many attempts. If you want to keep them around
|
23
|
-
# (perhaps to inspect the reason for the failure), set this to false.
|
24
|
-
cattr_accessor :destroy_failed_jobs
|
25
|
-
self.destroy_failed_jobs = true
|
26
|
-
|
27
|
-
self.logger = if defined?(Rails)
|
28
|
-
Rails.logger
|
29
|
-
elsif defined?(RAILS_DEFAULT_LOGGER)
|
30
|
-
RAILS_DEFAULT_LOGGER
|
31
|
-
end
|
32
|
-
|
33
|
-
# name_prefix is ignored if name is set directly
|
34
|
-
attr_accessor :name_prefix
|
35
|
-
|
36
|
-
cattr_reader :backend
|
37
|
-
|
38
|
-
def self.backend=(backend)
|
39
|
-
if backend.is_a? Symbol
|
40
|
-
require "delayed/serialization/#{backend}" if YAML.parser.class.name =~ /syck/i
|
41
|
-
require "delayed/backend/#{backend}"
|
42
|
-
backend = "Delayed::Backend::#{backend.to_s.classify}::Job".constantize
|
43
|
-
end
|
44
|
-
@@backend = backend
|
45
|
-
silence_warnings { ::Delayed.const_set(:Job, backend) }
|
46
|
-
end
|
47
|
-
|
48
|
-
def self.guess_backend
|
49
|
-
warn "[DEPRECATION] guess_backend is deprecated. Please remove it from your code."
|
50
|
-
end
|
51
|
-
|
52
|
-
def self.before_fork
|
53
|
-
unless @files_to_reopen
|
54
|
-
@files_to_reopen = []
|
55
|
-
ObjectSpace.each_object(File) do |file|
|
56
|
-
@files_to_reopen << file unless file.closed?
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
backend.before_fork
|
61
|
-
end
|
62
|
-
|
63
|
-
def self.after_fork
|
64
|
-
# Re-open file handles
|
65
|
-
@files_to_reopen.each do |file|
|
66
|
-
begin
|
67
|
-
file.reopen file.path, "a+"
|
68
|
-
file.sync = true
|
69
|
-
rescue ::Exception
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
backend.after_fork
|
74
|
-
end
|
75
|
-
|
76
|
-
def self.lifecycle
|
77
|
-
@lifecycle ||= Delayed::Lifecycle.new
|
78
|
-
end
|
79
|
-
|
80
|
-
def initialize(options={})
|
81
|
-
@quiet = options.has_key?(:quiet) ? options[:quiet] : true
|
82
|
-
self.class.min_priority = options[:min_priority] if options.has_key?(:min_priority)
|
83
|
-
self.class.max_priority = options[:max_priority] if options.has_key?(:max_priority)
|
84
|
-
self.class.sleep_delay = options[:sleep_delay] if options.has_key?(:sleep_delay)
|
85
|
-
self.class.queues = options[:queues] if options.has_key?(:queues)
|
86
|
-
|
87
|
-
self.plugins.each { |klass| klass.new }
|
88
|
-
end
|
89
|
-
|
90
|
-
# Every worker has a unique name which by default is the pid of the process. There are some
|
91
|
-
# advantages to overriding this with something which survives worker retarts: Workers can#
|
92
|
-
# safely resume working on tasks which are locked by themselves. The worker will assume that
|
93
|
-
# it crashed before.
|
94
|
-
def name
|
95
|
-
return @name unless @name.nil?
|
96
|
-
"#{@name_prefix}host:#{Socket.gethostname} pid:#{Process.pid}" rescue "#{@name_prefix}pid:#{Process.pid}"
|
97
|
-
end
|
98
|
-
|
99
|
-
# Sets the name of the worker.
|
100
|
-
# Setting the name to nil will reset the default worker name
|
101
|
-
def name=(val)
|
102
|
-
@name = val
|
103
|
-
end
|
104
|
-
|
105
|
-
def start
|
106
|
-
trap('TERM') { say 'Exiting...'; stop }
|
107
|
-
trap('INT') { say 'Exiting...'; stop }
|
108
|
-
|
109
|
-
say "Starting job worker"
|
110
|
-
|
111
|
-
self.class.lifecycle.run_callbacks(:execute, self) do
|
112
|
-
loop do
|
113
|
-
self.class.lifecycle.run_callbacks(:loop, self) do
|
114
|
-
result = nil
|
115
|
-
|
116
|
-
realtime = Benchmark.realtime do
|
117
|
-
result = work_off
|
118
|
-
end
|
119
|
-
|
120
|
-
count = result.sum
|
121
|
-
|
122
|
-
break if @exit
|
123
|
-
|
124
|
-
if count.zero?
|
125
|
-
sleep(self.class.sleep_delay)
|
126
|
-
else
|
127
|
-
say "#{count} jobs processed at %.4f j/s, %d failed ..." % [count / realtime, result.last]
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
break if @exit
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
def stop
|
137
|
-
@exit = true
|
138
|
-
end
|
139
|
-
|
140
|
-
# Do num jobs and return stats on success/failure.
|
141
|
-
# Exit early if interrupted.
|
142
|
-
def work_off(num = 100)
|
143
|
-
success, failure = 0, 0
|
144
|
-
|
145
|
-
num.times do
|
146
|
-
case reserve_and_run_one_job
|
147
|
-
when true
|
148
|
-
success += 1
|
149
|
-
when false
|
150
|
-
failure += 1
|
151
|
-
else
|
152
|
-
break # leave if no work could be done
|
153
|
-
end
|
154
|
-
break if $exit # leave if we're exiting
|
155
|
-
end
|
156
|
-
|
157
|
-
return [success, failure]
|
158
|
-
end
|
159
|
-
|
160
|
-
def run(job)
|
161
|
-
runtime = Benchmark.realtime do
|
162
|
-
Timeout.timeout(self.class.max_run_time.to_i) { job.invoke_job }
|
163
|
-
job.destroy
|
164
|
-
end
|
165
|
-
say "#{job.name} completed after %.4f" % runtime
|
166
|
-
return true # did work
|
167
|
-
rescue DeserializationError => error
|
168
|
-
job.last_error = "{#{error.message}\n#{error.backtrace.join("\n")}"
|
169
|
-
failed(job)
|
170
|
-
rescue Exception => error
|
171
|
-
self.class.lifecycle.run_callbacks(:error, self, job){ handle_failed_job(job, error) }
|
172
|
-
return false # work failed
|
173
|
-
end
|
174
|
-
|
175
|
-
# Reschedule the job in the future (when a job fails).
|
176
|
-
# Uses an exponential scale depending on the number of failed attempts.
|
177
|
-
def reschedule(job, time = nil)
|
178
|
-
if (job.attempts += 1) < max_attempts(job)
|
179
|
-
time ||= job.reschedule_at
|
180
|
-
job.run_at = time
|
181
|
-
job.unlock
|
182
|
-
job.save!
|
183
|
-
else
|
184
|
-
say "PERMANENTLY removing #{job.name} because of #{job.attempts} consecutive failures.", Logger::INFO
|
185
|
-
failed(job)
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
def failed(job)
|
190
|
-
self.class.lifecycle.run_callbacks(:failure, self, job) do
|
191
|
-
job.hook(:failure)
|
192
|
-
self.class.destroy_failed_jobs ? job.destroy : job.fail!
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
def say(text, level = Logger::INFO)
|
197
|
-
text = "[Worker(#{name})] #{text}"
|
198
|
-
puts text unless @quiet
|
199
|
-
logger.add level, "#{Time.now.strftime('%FT%T%z')}: #{text}" if logger
|
200
|
-
end
|
201
|
-
|
202
|
-
def max_attempts(job)
|
203
|
-
job.max_attempts || self.class.max_attempts
|
204
|
-
end
|
205
|
-
|
206
|
-
protected
|
207
|
-
|
208
|
-
def handle_failed_job(job, error)
|
209
|
-
job.last_error = "{#{error.message}\n#{error.backtrace.join("\n")}"
|
210
|
-
say "#{job.name} failed with #{error.class.name}: #{error.message} - #{job.attempts} failed attempts", Logger::ERROR
|
211
|
-
reschedule(job)
|
212
|
-
end
|
213
|
-
|
214
|
-
# Run the next job we can get an exclusive lock on.
|
215
|
-
# If no jobs are left we return nil
|
216
|
-
def reserve_and_run_one_job
|
217
|
-
job = Delayed::Job.reserve(self)
|
218
|
-
self.class.lifecycle.run_callbacks(:perform, self, job){ result = run(job) } if job
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
end
|
data/lib/delayed/yaml_ext.rb
DELETED
@@ -1,10 +0,0 @@
|
|
1
|
-
# These extensions allow properly serializing and autoloading of
|
2
|
-
# Classes, Modules and Structs
|
3
|
-
|
4
|
-
require 'yaml'
|
5
|
-
if YAML.parser.class.name =~ /syck/i
|
6
|
-
require File.expand_path('../syck_ext', __FILE__)
|
7
|
-
require File.expand_path('../serialization/active_record', __FILE__)
|
8
|
-
else
|
9
|
-
require File.expand_path('../psych_ext', __FILE__)
|
10
|
-
end
|
data/lib/delayed_job.rb
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
require 'active_support'
|
2
|
-
|
3
|
-
require File.dirname(__FILE__) + '/delayed/message_sending'
|
4
|
-
require File.dirname(__FILE__) + '/delayed/performable_method'
|
5
|
-
|
6
|
-
# PerformableMailer is compatible with ActionMailer 3 (and possibly 3.1)
|
7
|
-
if defined?(ActionMailer)
|
8
|
-
require 'action_mailer/version'
|
9
|
-
require File.dirname(__FILE__) + '/delayed/performable_mailer' if 3 == ActionMailer::VERSION::MAJOR
|
10
|
-
end
|
11
|
-
|
12
|
-
require File.dirname(__FILE__) + '/delayed/yaml_ext'
|
13
|
-
require File.dirname(__FILE__) + '/delayed/lifecycle'
|
14
|
-
require File.dirname(__FILE__) + '/delayed/plugin'
|
15
|
-
require File.dirname(__FILE__) + '/delayed/plugins/clear_locks'
|
16
|
-
require File.dirname(__FILE__) + '/delayed/backend/base'
|
17
|
-
require File.dirname(__FILE__) + '/delayed/worker'
|
18
|
-
require File.dirname(__FILE__) + '/delayed/deserialization_error'
|
19
|
-
require File.dirname(__FILE__) + '/delayed/railtie' if defined?(Rails::Railtie)
|
20
|
-
|
21
|
-
Object.send(:include, Delayed::MessageSending)
|
22
|
-
Module.send(:include, Delayed::MessageSending::ClassMethods)
|
@@ -1,11 +0,0 @@
|
|
1
|
-
require 'rails/generators'
|
2
|
-
|
3
|
-
class DelayedJobGenerator < Rails::Generators::Base
|
4
|
-
|
5
|
-
self.source_paths << File.join(File.dirname(__FILE__), 'templates')
|
6
|
-
|
7
|
-
def create_script_file
|
8
|
-
template 'script', 'script/delayed_job'
|
9
|
-
chmod 'script/delayed_job', 0755
|
10
|
-
end
|
11
|
-
end
|
data/recipes/delayed_job.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'delayed', 'recipes'))
|
data/spec/autoloaded/clazz.rb
DELETED
data/spec/autoloaded/struct.rb
DELETED
@@ -1,113 +0,0 @@
|
|
1
|
-
require 'ostruct'
|
2
|
-
|
3
|
-
# An in-memory backend suitable only for testing. Tries to behave as if it were an ORM.
|
4
|
-
module Delayed
|
5
|
-
module Backend
|
6
|
-
module Test
|
7
|
-
class Job
|
8
|
-
attr_accessor :id
|
9
|
-
attr_accessor :priority
|
10
|
-
attr_accessor :attempts
|
11
|
-
attr_accessor :handler
|
12
|
-
attr_accessor :last_error
|
13
|
-
attr_accessor :run_at
|
14
|
-
attr_accessor :locked_at
|
15
|
-
attr_accessor :locked_by
|
16
|
-
attr_accessor :failed_at
|
17
|
-
attr_accessor :queue
|
18
|
-
attr_accessor :unique_key
|
19
|
-
|
20
|
-
include Delayed::Backend::Base
|
21
|
-
|
22
|
-
cattr_accessor :id
|
23
|
-
self.id = 0
|
24
|
-
|
25
|
-
def initialize(hash = {})
|
26
|
-
self.attempts = 0
|
27
|
-
self.priority = 0
|
28
|
-
self.id = (self.class.id += 1)
|
29
|
-
hash.each{|k,v| send(:"#{k}=", v)}
|
30
|
-
end
|
31
|
-
|
32
|
-
@jobs = []
|
33
|
-
def self.all
|
34
|
-
@jobs
|
35
|
-
end
|
36
|
-
|
37
|
-
def self.count
|
38
|
-
all.size
|
39
|
-
end
|
40
|
-
|
41
|
-
def self.delete_all
|
42
|
-
all.clear
|
43
|
-
end
|
44
|
-
|
45
|
-
def self.create(attrs = {})
|
46
|
-
new(attrs).tap do |o|
|
47
|
-
o.save
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def self.create!(*args); create(*args); end
|
52
|
-
|
53
|
-
def self.clear_locks!(worker_name)
|
54
|
-
all.select{|j| j.locked_by == worker_name}.each {|j| j.locked_by = nil; j.locked_at = nil}
|
55
|
-
end
|
56
|
-
|
57
|
-
# Find a few candidate jobs to run (in case some immediately get locked by others).
|
58
|
-
def self.find_available(worker_name, limit = 5, max_run_time = Worker.max_run_time)
|
59
|
-
jobs = all.select do |j|
|
60
|
-
j.run_at <= db_time_now &&
|
61
|
-
(j.locked_at.nil? || j.locked_at < db_time_now - max_run_time || j.locked_by == worker_name) &&
|
62
|
-
j.failed_at.nil?
|
63
|
-
end
|
64
|
-
|
65
|
-
jobs = jobs.select{|j| Worker.queues.include?(j.queue)} if Worker.queues.any?
|
66
|
-
jobs = jobs.select{|j| j.priority >= Worker.min_priority} if Worker.min_priority
|
67
|
-
jobs = jobs.select{|j| j.priority <= Worker.max_priority} if Worker.max_priority
|
68
|
-
jobs.sort_by{|j| [j.priority, j.run_at]}[0..limit-1]
|
69
|
-
end
|
70
|
-
|
71
|
-
# Lock this job for this worker.
|
72
|
-
# Returns true if we have the lock, false otherwise.
|
73
|
-
def lock_exclusively!(max_run_time, worker)
|
74
|
-
now = self.class.db_time_now
|
75
|
-
if locked_by != worker
|
76
|
-
# We don't own this job so we will update the locked_by name and the locked_at
|
77
|
-
self.locked_at = now
|
78
|
-
self.locked_by = worker
|
79
|
-
end
|
80
|
-
|
81
|
-
return true
|
82
|
-
end
|
83
|
-
|
84
|
-
def self.db_time_now
|
85
|
-
Time.current
|
86
|
-
end
|
87
|
-
|
88
|
-
def update_attributes(attrs = {})
|
89
|
-
attrs.each{|k,v| send(:"#{k}=", v)}
|
90
|
-
save
|
91
|
-
end
|
92
|
-
|
93
|
-
def destroy
|
94
|
-
self.class.all.delete(self)
|
95
|
-
end
|
96
|
-
|
97
|
-
def save
|
98
|
-
self.run_at ||= Time.current
|
99
|
-
|
100
|
-
self.class.all << self unless self.class.all.include?(self)
|
101
|
-
true
|
102
|
-
end
|
103
|
-
|
104
|
-
def save!; save; end
|
105
|
-
|
106
|
-
def reload
|
107
|
-
reset
|
108
|
-
self
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|