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.
Files changed (49) hide show
  1. data/lib/delayed_job_unique_key.rb +8 -0
  2. data/lib/delayed_job_unique_key/active_record_job.rb +12 -0
  3. data/lib/delayed_job_unique_key/base.rb +52 -0
  4. data/lib/delayed_job_unique_key/version.rb +3 -0
  5. data/lib/generators/delayed_job_unique_key/install_generator.rb +17 -0
  6. data/lib/generators/delayed_job_unique_key/templates/add_unique_key_migration.rb +11 -0
  7. metadata +63 -174
  8. data/MIT-LICENSE +0 -20
  9. data/README.textile +0 -246
  10. data/contrib/delayed_job.monitrc +0 -14
  11. data/contrib/delayed_job_multiple.monitrc +0 -23
  12. data/lib/delayed/backend/base.rb +0 -152
  13. data/lib/delayed/backend/shared_spec.rb +0 -566
  14. data/lib/delayed/command.rb +0 -101
  15. data/lib/delayed/deserialization_error.rb +0 -4
  16. data/lib/delayed/lifecycle.rb +0 -84
  17. data/lib/delayed/message_sending.rb +0 -54
  18. data/lib/delayed/performable_mailer.rb +0 -21
  19. data/lib/delayed/performable_method.rb +0 -33
  20. data/lib/delayed/plugin.rb +0 -15
  21. data/lib/delayed/plugins/clear_locks.rb +0 -15
  22. data/lib/delayed/psych_ext.rb +0 -75
  23. data/lib/delayed/railtie.rb +0 -16
  24. data/lib/delayed/recipes.rb +0 -50
  25. data/lib/delayed/serialization/active_record.rb +0 -19
  26. data/lib/delayed/syck_ext.rb +0 -34
  27. data/lib/delayed/tasks.rb +0 -11
  28. data/lib/delayed/worker.rb +0 -222
  29. data/lib/delayed/yaml_ext.rb +0 -10
  30. data/lib/delayed_job.rb +0 -22
  31. data/lib/generators/delayed_job/delayed_job_generator.rb +0 -11
  32. data/lib/generators/delayed_job/templates/script +0 -5
  33. data/recipes/delayed_job.rb +0 -1
  34. data/spec/autoloaded/clazz.rb +0 -7
  35. data/spec/autoloaded/instance_clazz.rb +0 -6
  36. data/spec/autoloaded/instance_struct.rb +0 -6
  37. data/spec/autoloaded/struct.rb +0 -7
  38. data/spec/delayed/backend/test.rb +0 -113
  39. data/spec/delayed/serialization/test.rb +0 -0
  40. data/spec/fixtures/bad_alias.yml +0 -1
  41. data/spec/lifecycle_spec.rb +0 -107
  42. data/spec/message_sending_spec.rb +0 -116
  43. data/spec/performable_mailer_spec.rb +0 -46
  44. data/spec/performable_method_spec.rb +0 -89
  45. data/spec/sample_jobs.rb +0 -75
  46. data/spec/spec_helper.rb +0 -45
  47. data/spec/test_backend_spec.rb +0 -13
  48. data/spec/worker_spec.rb +0 -19
  49. 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'environment'))
4
- require 'delayed/command'
5
- Delayed::Command.new(ARGV).daemonize
@@ -1 +0,0 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'delayed', 'recipes'))
@@ -1,7 +0,0 @@
1
- # Make sure this file does not get required manually
2
- module Autoloaded
3
- class Clazz
4
- def perform
5
- end
6
- end
7
- end
@@ -1,6 +0,0 @@
1
- module Autoloaded
2
- class InstanceClazz
3
- def perform
4
- end
5
- end
6
- end
@@ -1,6 +0,0 @@
1
- module Autoloaded
2
- class InstanceStruct < ::Struct.new(nil)
3
- def perform
4
- end
5
- end
6
- end
@@ -1,7 +0,0 @@
1
- # Make sure this file does not get required manually
2
- module Autoloaded
3
- class Struct < ::Struct.new(nil)
4
- def perform
5
- end
6
- end
7
- end
@@ -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