canvas-jobs 0.9.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.
- checksums.yaml +7 -0
- data/db/migrate/20101216224513_create_delayed_jobs.rb +40 -0
- data/db/migrate/20110208031356_add_delayed_jobs_tag.rb +14 -0
- data/db/migrate/20110426161613_add_delayed_jobs_max_attempts.rb +13 -0
- data/db/migrate/20110516225834_add_delayed_jobs_strand.rb +14 -0
- data/db/migrate/20110531144916_cleanup_delayed_jobs_indexes.rb +26 -0
- data/db/migrate/20110610213249_optimize_delayed_jobs.rb +40 -0
- data/db/migrate/20110831210257_add_delayed_jobs_next_in_strand.rb +52 -0
- data/db/migrate/20120510004759_delayed_jobs_delete_trigger_lock_for_update.rb +31 -0
- data/db/migrate/20120531150712_drop_psql_jobs_pop_fn.rb +15 -0
- data/db/migrate/20120607164022_delayed_jobs_use_advisory_locks.rb +80 -0
- data/db/migrate/20120607181141_index_jobs_on_locked_by.rb +15 -0
- data/db/migrate/20120608191051_add_jobs_run_at_index.rb +15 -0
- data/db/migrate/20120927184213_change_delayed_jobs_handler_to_text.rb +13 -0
- data/db/migrate/20140505215131_add_failed_jobs_original_job_id.rb +13 -0
- data/db/migrate/20140505215510_copy_failed_jobs_original_id.rb +13 -0
- data/db/migrate/20140505223637_drop_failed_jobs_original_id.rb +13 -0
- data/db/migrate/20140512213941_add_source_to_jobs.rb +15 -0
- data/lib/canvas-jobs.rb +1 -0
- data/lib/delayed/backend/active_record.rb +297 -0
- data/lib/delayed/backend/base.rb +317 -0
- data/lib/delayed/backend/redis/bulk_update.lua +40 -0
- data/lib/delayed/backend/redis/destroy_job.lua +2 -0
- data/lib/delayed/backend/redis/enqueue.lua +29 -0
- data/lib/delayed/backend/redis/fail_job.lua +5 -0
- data/lib/delayed/backend/redis/find_available.lua +3 -0
- data/lib/delayed/backend/redis/functions.rb +57 -0
- data/lib/delayed/backend/redis/get_and_lock_next_available.lua +17 -0
- data/lib/delayed/backend/redis/includes/jobs_common.lua +203 -0
- data/lib/delayed/backend/redis/job.rb +481 -0
- data/lib/delayed/backend/redis/set_running.lua +5 -0
- data/lib/delayed/backend/redis/tickle_strand.lua +2 -0
- data/lib/delayed/batch.rb +56 -0
- data/lib/delayed/engine.rb +4 -0
- data/lib/delayed/job_tracking.rb +31 -0
- data/lib/delayed/lifecycle.rb +83 -0
- data/lib/delayed/message_sending.rb +130 -0
- data/lib/delayed/performable_method.rb +42 -0
- data/lib/delayed/periodic.rb +81 -0
- data/lib/delayed/pool.rb +335 -0
- data/lib/delayed/settings.rb +32 -0
- data/lib/delayed/version.rb +3 -0
- data/lib/delayed/worker.rb +213 -0
- data/lib/delayed/yaml_extensions.rb +63 -0
- data/lib/delayed_job.rb +40 -0
- data/spec/active_record_job_spec.rb +61 -0
- data/spec/gemfiles/32.gemfile +6 -0
- data/spec/gemfiles/40.gemfile +6 -0
- data/spec/gemfiles/41.gemfile +6 -0
- data/spec/gemfiles/42.gemfile +6 -0
- data/spec/migrate/20140924140513_add_story_table.rb +7 -0
- data/spec/redis_job_spec.rb +77 -0
- data/spec/sample_jobs.rb +26 -0
- data/spec/shared/delayed_batch.rb +85 -0
- data/spec/shared/delayed_method.rb +419 -0
- data/spec/shared/performable_method.rb +52 -0
- data/spec/shared/shared_backend.rb +836 -0
- data/spec/shared/worker.rb +291 -0
- data/spec/shared_jobs_specs.rb +13 -0
- data/spec/spec_helper.rb +91 -0
- metadata +329 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
# New definitions for YAML to aid in serialization and deserialization of delayed jobs.
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'syck'
|
5
|
+
# this code needs to be updated to work with the new Psych YAML engine in ruby 1.9.x
|
6
|
+
# for now we force Syck
|
7
|
+
YAML::ENGINE.yamler = 'syck' if defined?(YAML::ENGINE) && YAML::ENGINE.yamler != 'syck'
|
8
|
+
|
9
|
+
# First, tell YAML how to load a Module. This depends on Rails .constantize and autoloading.
|
10
|
+
YAML.add_ruby_type("object:Module") do |type, val|
|
11
|
+
val.constantize
|
12
|
+
end
|
13
|
+
|
14
|
+
class Module
|
15
|
+
def to_yaml(opts = {})
|
16
|
+
YAML.quick_emit(self.object_id, opts) do |out|
|
17
|
+
out.scalar(taguri, name)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Now we have to do the same for Class.
|
23
|
+
YAML.add_ruby_type("object:Class") do |type, val|
|
24
|
+
val.constantize
|
25
|
+
end
|
26
|
+
|
27
|
+
class Class
|
28
|
+
def to_yaml(opts = {})
|
29
|
+
YAML.quick_emit(self.object_id, opts) do |out|
|
30
|
+
out.scalar(taguri, name)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Now, tell YAML how to intelligently load ActiveRecord objects, using the
|
36
|
+
# database rather than just serializing their attributes to the YAML. This
|
37
|
+
# ensures the object is up to date when we use it in the job.
|
38
|
+
class ActiveRecord::Base
|
39
|
+
yaml_as "tag:ruby.yaml.org,2002:ActiveRecord"
|
40
|
+
|
41
|
+
def to_yaml(opts = {})
|
42
|
+
if id.nil?
|
43
|
+
raise("Can't serialize unsaved ActiveRecord object for delayed job: #{self.inspect}")
|
44
|
+
end
|
45
|
+
YAML.quick_emit(self.object_id, opts) do |out|
|
46
|
+
out.scalar(taguri, id.to_s)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.yaml_new(klass, tag, val)
|
51
|
+
klass.find(val)
|
52
|
+
rescue ActiveRecord::RecordNotFound
|
53
|
+
raise Delayed::Backend::RecordNotFound, "Couldn't find #{klass} with id #{val.inspect}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Load Module/Class from yaml tag.
|
58
|
+
class Module
|
59
|
+
def yaml_tag_read_class(name)
|
60
|
+
name.constantize
|
61
|
+
name
|
62
|
+
end
|
63
|
+
end
|
data/lib/delayed_job.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
module Delayed
|
2
|
+
MIN_PRIORITY = 0
|
3
|
+
HIGH_PRIORITY = 0
|
4
|
+
NORMAL_PRIORITY = 10
|
5
|
+
LOW_PRIORITY = 20
|
6
|
+
LOWER_PRIORITY = 50
|
7
|
+
MAX_PRIORITY = 1_000_000
|
8
|
+
|
9
|
+
def self.select_backend(backend)
|
10
|
+
remove_const(:Job) if defined?(::Delayed::Job)
|
11
|
+
const_set(:Job, backend)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
require 'rails'
|
16
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
17
|
+
require 'active_record'
|
18
|
+
require 'after_transaction_commit'
|
19
|
+
|
20
|
+
require 'delayed/settings'
|
21
|
+
require 'delayed/yaml_extensions'
|
22
|
+
|
23
|
+
require 'delayed/backend/base'
|
24
|
+
require 'delayed/backend/active_record'
|
25
|
+
require 'delayed/backend/redis/job'
|
26
|
+
require 'delayed/batch'
|
27
|
+
require 'delayed/job_tracking'
|
28
|
+
require 'delayed/lifecycle'
|
29
|
+
require 'delayed/message_sending'
|
30
|
+
require 'delayed/performable_method'
|
31
|
+
require 'delayed/periodic'
|
32
|
+
require 'delayed/pool'
|
33
|
+
require 'delayed/worker'
|
34
|
+
|
35
|
+
require 'delayed/engine'
|
36
|
+
|
37
|
+
Delayed.select_backend(Delayed::Backend::ActiveRecord::Job)
|
38
|
+
|
39
|
+
Object.send(:include, Delayed::MessageSending)
|
40
|
+
Module.send(:include, Delayed::MessageSending::ClassMethods)
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require File.expand_path("../spec_helper", __FILE__)
|
2
|
+
|
3
|
+
describe 'Delayed::Backed::ActiveRecord::Job' do
|
4
|
+
before :all do
|
5
|
+
Delayed.select_backend(Delayed::Backend::ActiveRecord::Job)
|
6
|
+
end
|
7
|
+
|
8
|
+
after :all do
|
9
|
+
Delayed.send(:remove_const, :Job)
|
10
|
+
end
|
11
|
+
|
12
|
+
before do
|
13
|
+
Delayed::Job.delete_all
|
14
|
+
Delayed::Job::Failed.delete_all
|
15
|
+
end
|
16
|
+
|
17
|
+
include_examples 'a delayed_jobs implementation'
|
18
|
+
|
19
|
+
it "should recover as well as possible from a failure failing a job" do
|
20
|
+
allow(Delayed::Job::Failed).to receive(:create).and_raise(RuntimeError)
|
21
|
+
job = "test".send_later_enqueue_args :reverse, no_delay: true
|
22
|
+
job_id = job.id
|
23
|
+
proc { job.fail! }.should raise_error
|
24
|
+
proc { Delayed::Job.find(job_id) }.should raise_error(ActiveRecord::RecordNotFound)
|
25
|
+
Delayed::Job.count.should == 0
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when another worker has worked on a task since the job was found to be available, it" do
|
29
|
+
before :each do
|
30
|
+
@job = Delayed::Job.create :payload_object => SimpleJob.new
|
31
|
+
@job_copy_for_worker_2 = Delayed::Job.find(@job.id)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should not allow a second worker to get exclusive access if already successfully processed by worker1" do
|
35
|
+
@job.destroy
|
36
|
+
@job_copy_for_worker_2.lock_exclusively!('worker2').should == false
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should not allow a second worker to get exclusive access if failed to be processed by worker1 and run_at time is now in future (due to backing off behaviour)" do
|
40
|
+
@job.update_attributes(:attempts => 1, :run_at => 1.day.from_now)
|
41
|
+
@job_copy_for_worker_2.lock_exclusively!('worker2').should == false
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should select the next job at random if enabled" do
|
45
|
+
begin
|
46
|
+
Delayed::Settings.select_random_from_batch = true
|
47
|
+
15.times { "test".send_later :length }
|
48
|
+
founds = []
|
49
|
+
15.times do
|
50
|
+
job = Delayed::Job.get_and_lock_next_available('tester')
|
51
|
+
founds << job
|
52
|
+
job.unlock
|
53
|
+
job.save!
|
54
|
+
end
|
55
|
+
founds.uniq.size.should > 1
|
56
|
+
ensure
|
57
|
+
Delayed::Settings.select_random_from_batch = false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require File.expand_path("../spec_helper", __FILE__)
|
2
|
+
|
3
|
+
describe 'Delayed::Backend::Redis::Job' do
|
4
|
+
before :all do
|
5
|
+
Delayed.select_backend(Delayed::Backend::Redis::Job)
|
6
|
+
end
|
7
|
+
|
8
|
+
after :all do
|
9
|
+
Delayed.send(:remove_const, :Job)
|
10
|
+
end
|
11
|
+
|
12
|
+
before do
|
13
|
+
Delayed::Job.redis.flushdb
|
14
|
+
end
|
15
|
+
|
16
|
+
include_examples 'a delayed_jobs implementation'
|
17
|
+
|
18
|
+
describe "tickle_strand" do
|
19
|
+
it "should continue trying to tickle until the strand is empty" do
|
20
|
+
jobs = []
|
21
|
+
3.times { jobs << "test".send_later_enqueue_args(:to_s, :strand => "s1", :no_delay => true) }
|
22
|
+
job = "test".send_later_enqueue_args(:to_s, :strand => "s1", :no_delay => true)
|
23
|
+
# manually delete the first jobs, bypassing the strand book-keeping
|
24
|
+
jobs.each { |j| Delayed::Job.redis.del(Delayed::Job::Keys::JOB[j.id]) }
|
25
|
+
Delayed::Job.redis.llen(Delayed::Job::Keys::STRAND['s1']).should == 4
|
26
|
+
job.destroy
|
27
|
+
Delayed::Job.redis.llen(Delayed::Job::Keys::STRAND['s1']).should == 0
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should tickle until it finds an existing job" do
|
31
|
+
jobs = []
|
32
|
+
3.times { jobs << "test".send_later_enqueue_args(:to_s, :strand => "s1", :no_delay => true) }
|
33
|
+
job = "test".send_later_enqueue_args(:to_s, :strand => "s1", :no_delay => true)
|
34
|
+
# manually delete the first jobs, bypassing the strand book-keeping
|
35
|
+
jobs[0...-1].each { |j| Delayed::Job.redis.del(Delayed::Job::Keys::JOB[j.id]) }
|
36
|
+
Delayed::Job.redis.llen(Delayed::Job::Keys::STRAND['s1']).should == 4
|
37
|
+
jobs[-1].destroy
|
38
|
+
Delayed::Job.redis.lrange(Delayed::Job::Keys::STRAND['s1'], 0, -1).should == [job.id]
|
39
|
+
found = [Delayed::Job.get_and_lock_next_available('test worker'),
|
40
|
+
Delayed::Job.get_and_lock_next_available('test worker')]
|
41
|
+
found.should =~ [job, nil]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "missing jobs in queues" do
|
46
|
+
before do
|
47
|
+
@job = "test".send_later_enqueue_args(:to_s, :no_delay => true)
|
48
|
+
@job2 = "test".send_later_enqueue_args(:to_s, :no_delay => true)
|
49
|
+
# manually delete the job from redis
|
50
|
+
Delayed::Job.redis.del(Delayed::Job::Keys::JOB[@job.id])
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should discard when trying to lock" do
|
54
|
+
found = [Delayed::Job.get_and_lock_next_available("test worker"),
|
55
|
+
Delayed::Job.get_and_lock_next_available("test worker")]
|
56
|
+
found.should =~ [@job2, nil]
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should filter for find_available" do
|
60
|
+
found = [Delayed::Job.find_available(1),
|
61
|
+
Delayed::Job.find_available(1)]
|
62
|
+
found.should be_include([@job2])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "send_later" do
|
67
|
+
it "should schedule job on transaction commit" do
|
68
|
+
before_count = Delayed::Job.jobs_count(:current)
|
69
|
+
ActiveRecord::Base.transaction do
|
70
|
+
job = "string".send_later :reverse
|
71
|
+
job.should be_nil
|
72
|
+
Delayed::Job.jobs_count(:current).should == before_count
|
73
|
+
end
|
74
|
+
Delayed::Job.jobs_count(:current) == before_count + 1
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/spec/sample_jobs.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
class SimpleJob
|
2
|
+
cattr_accessor :runs; self.runs = 0
|
3
|
+
def perform; @@runs += 1; end
|
4
|
+
end
|
5
|
+
|
6
|
+
class ErrorJob
|
7
|
+
cattr_accessor :runs; self.runs = 0
|
8
|
+
def perform; raise 'did not work'; end
|
9
|
+
|
10
|
+
cattr_accessor :failure_runs; self.failure_runs = 0
|
11
|
+
def on_failure(error); @@failure_runs += 1; end
|
12
|
+
|
13
|
+
cattr_accessor :permanent_failure_runs; self.permanent_failure_runs = 0
|
14
|
+
def on_permanent_failure(error); @@permanent_failure_runs += 1; end
|
15
|
+
end
|
16
|
+
|
17
|
+
class LongRunningJob
|
18
|
+
def perform; sleep 250; end
|
19
|
+
end
|
20
|
+
|
21
|
+
module M
|
22
|
+
class ModuleJob
|
23
|
+
cattr_accessor :runs; self.runs = 0
|
24
|
+
def perform; @@runs += 1; end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
shared_examples_for 'Delayed::Batch' do
|
2
|
+
context "batching" do
|
3
|
+
it "should batch up all deferrable delayed methods" do
|
4
|
+
later = 1.hour.from_now
|
5
|
+
Delayed::Batch.serial_batch {
|
6
|
+
"string".send_later_enqueue_args(:size, no_delay: true).should be true
|
7
|
+
"string".send_later_enqueue_args(:reverse, run_at: later, no_delay: true).should be_truthy # won't be batched, it'll get its own job
|
8
|
+
"string".send_later_enqueue_args(:gsub, { no_delay: true }, /./, "!").should be_truthy
|
9
|
+
}
|
10
|
+
batch_jobs = Delayed::Job.find_available(5)
|
11
|
+
regular_jobs = Delayed::Job.list_jobs(:future, 5)
|
12
|
+
regular_jobs.size.should == 1
|
13
|
+
regular_jobs.first.batch?.should == false
|
14
|
+
batch_jobs.size.should == 1
|
15
|
+
batch_job = batch_jobs.first
|
16
|
+
batch_job.batch?.should == true
|
17
|
+
batch_job.payload_object.mode.should == :serial
|
18
|
+
batch_job.payload_object.jobs.map { |j| [j.payload_object.object, j.payload_object.method, j.payload_object.args] }.should == [
|
19
|
+
["string", :size, []],
|
20
|
+
["string", :gsub, [/./, "!"]]
|
21
|
+
]
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should not let you invoke it directly" do
|
25
|
+
later = 1.hour.from_now
|
26
|
+
Delayed::Batch.serial_batch {
|
27
|
+
"string".send_later_enqueue_args(:size, no_delay: true).should be true
|
28
|
+
"string".send_later_enqueue_args(:gsub, { no_delay: true }, /./, "!").should be true
|
29
|
+
}
|
30
|
+
Delayed::Job.jobs_count(:current).should == 1
|
31
|
+
job = Delayed::Job.find_available(1).first
|
32
|
+
expect{ job.invoke_job }.to raise_error
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should create valid jobs" do
|
36
|
+
Delayed::Batch.serial_batch {
|
37
|
+
"string".send_later_enqueue_args(:size, no_delay: true).should be true
|
38
|
+
"string".send_later_enqueue_args(:gsub, { no_delay: true }, /./, "!").should be true
|
39
|
+
}
|
40
|
+
Delayed::Job.jobs_count(:current).should == 1
|
41
|
+
|
42
|
+
batch_job = Delayed::Job.find_available(1).first
|
43
|
+
batch_job.batch?.should == true
|
44
|
+
jobs = batch_job.payload_object.jobs
|
45
|
+
jobs.size.should == 2
|
46
|
+
jobs[0].should be_new_record
|
47
|
+
jobs[0].payload_object.class.should == Delayed::PerformableMethod
|
48
|
+
jobs[0].payload_object.method.should == :size
|
49
|
+
jobs[0].payload_object.args.should == []
|
50
|
+
jobs[0].payload_object.perform.should == 6
|
51
|
+
jobs[1].should be_new_record
|
52
|
+
jobs[1].payload_object.class.should == Delayed::PerformableMethod
|
53
|
+
jobs[1].payload_object.method.should == :gsub
|
54
|
+
jobs[1].payload_object.args.should == [/./, "!"]
|
55
|
+
jobs[1].payload_object.perform.should == "!!!!!!"
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should create a different batch for each priority" do
|
59
|
+
later = 1.hour.from_now
|
60
|
+
Delayed::Batch.serial_batch {
|
61
|
+
"string".send_later_enqueue_args(:size, :priority => Delayed::LOW_PRIORITY, :no_delay => true).should be true
|
62
|
+
"string".send_later_enqueue_args(:gsub, { :no_delay => true }, /./, "!").should be true
|
63
|
+
}
|
64
|
+
Delayed::Job.jobs_count(:current).should == 2
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should use the given priority for all, if specified" do
|
68
|
+
Delayed::Batch.serial_batch(:priority => 11) {
|
69
|
+
"string".send_later_enqueue_args(:size, :priority => 20, :no_delay => true).should be true
|
70
|
+
"string".send_later_enqueue_args(:gsub, { :priority => 15, :no_delay => true }, /./, "!").should be true
|
71
|
+
}
|
72
|
+
Delayed::Job.jobs_count(:current).should == 1
|
73
|
+
Delayed::Job.find_available(1).first.priority.should == 11
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should just create the job, if there's only one in the batch" do
|
77
|
+
Delayed::Batch.serial_batch(:priority => 11) {
|
78
|
+
"string".send_later_enqueue_args(:size, no_delay: true).should be true
|
79
|
+
}
|
80
|
+
Delayed::Job.jobs_count(:current).should == 1
|
81
|
+
Delayed::Job.find_available(1).first.tag.should == "String#size"
|
82
|
+
Delayed::Job.find_available(1).first.priority.should == 11
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,419 @@
|
|
1
|
+
shared_examples_for 'random ruby objects' do
|
2
|
+
def set_queue(name)
|
3
|
+
old_name = Delayed::Settings.queue
|
4
|
+
Delayed::Settings.queue = name
|
5
|
+
ensure
|
6
|
+
Delayed::Settings.queue = old_name
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should respond_to :send_later method" do
|
10
|
+
Object.new.respond_to?(:send_later)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should raise a ArgumentError if send_later is called but the target method doesn't exist" do
|
14
|
+
lambda { Object.new.send_later(:method_that_deos_not_exist) }.should raise_error(NoMethodError)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should add a new entry to the job table when send_later is called on it" do
|
18
|
+
lambda { Object.new.send_later(:to_s) }.should change { Delayed::Job.jobs_count(:current) }.by(1)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should add a new entry to the job table when send_later_with_queue is called on it" do
|
22
|
+
lambda { Object.new.send_later_with_queue(:to_s, "testqueue") }.should change { Delayed::Job.jobs_count(:current, "testqueue") }.by(1)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should add a new entry to the job table when send_later is called on the class" do
|
26
|
+
lambda { Object.send_later(:to_s) }.should change { Delayed::Job.jobs_count(:current) }.by(1)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should add a new entry to the job table when send_later_with_queue is called on the class" do
|
30
|
+
lambda { Object.send_later_with_queue(:to_s, "testqueue") }.should change { Delayed::Job.jobs_count(:current, "testqueue") }.by(1)
|
31
|
+
end
|
32
|
+
|
33
|
+
context "class methods" do
|
34
|
+
context "add_send_later_methods" do
|
35
|
+
it "should work with default_async" do
|
36
|
+
class TestObject
|
37
|
+
attr_reader :ran
|
38
|
+
def test_method; @ran = true; end
|
39
|
+
add_send_later_methods :test_method, {}, true
|
40
|
+
end
|
41
|
+
obj = TestObject.new
|
42
|
+
lambda { obj.test_method }.should change { Delayed::Job.jobs_count(:current) }.by(1)
|
43
|
+
lambda { obj.test_method_with_send_later }.should change { Delayed::Job.jobs_count(:current) }.by(1)
|
44
|
+
obj.ran.should be_falsey
|
45
|
+
lambda { obj.test_method_without_send_later }.should_not change { Delayed::Job.jobs_count(:current) }
|
46
|
+
obj.ran.should be true
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should work without default_async" do
|
50
|
+
class TestObject
|
51
|
+
attr_accessor :ran
|
52
|
+
def test_method; @ran = true; end
|
53
|
+
add_send_later_methods :test_method, {}, false
|
54
|
+
end
|
55
|
+
obj = TestObject.new
|
56
|
+
lambda { obj.test_method_with_send_later }.should change { Delayed::Job.jobs_count(:current) }.by(1)
|
57
|
+
obj.ran.should be_falsey
|
58
|
+
lambda { obj.test_method }.should_not change { Delayed::Job.jobs_count(:current) }
|
59
|
+
obj.ran.should be true
|
60
|
+
obj.ran = false
|
61
|
+
obj.ran.should be false
|
62
|
+
lambda { obj.test_method_without_send_later }.should_not change { Delayed::Job.jobs_count(:current) }
|
63
|
+
obj.ran.should be true
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should send along enqueue args and args default async" do
|
67
|
+
class TestObject
|
68
|
+
attr_reader :ran
|
69
|
+
def test_method(*args); @ran = args; end
|
70
|
+
add_send_later_methods(:test_method, {:enqueue_arg_1 => :thing}, true)
|
71
|
+
end
|
72
|
+
obj = TestObject.new
|
73
|
+
method = double()
|
74
|
+
expect(Delayed::PerformableMethod).to receive(:new).with(obj, :test_method_without_send_later, [1,2,3]).and_return(method)
|
75
|
+
expect(Delayed::Job).to receive(:enqueue).with(method, :enqueue_arg_1 => :thing)
|
76
|
+
obj.test_method(1,2,3)
|
77
|
+
expect(Delayed::PerformableMethod).to receive(:new).with(obj, :test_method_without_send_later, [4]).and_return(method)
|
78
|
+
expect(Delayed::Job).to receive(:enqueue).with(method, :enqueue_arg_1 => :thing)
|
79
|
+
obj.test_method(4)
|
80
|
+
expect(Delayed::PerformableMethod).to receive(:new).with(obj, :test_method_without_send_later, [6]).and_return(method)
|
81
|
+
expect(Delayed::Job).to receive(:enqueue).with(method, :enqueue_arg_1 => :thing)
|
82
|
+
obj.test_method_with_send_later(6)
|
83
|
+
expect(Delayed::PerformableMethod).to receive(:new).with(obj, :test_method_without_send_later, [5,6]).and_return(method)
|
84
|
+
expect(Delayed::Job).to receive(:enqueue).with(method, :enqueue_arg_1 => :thing)
|
85
|
+
obj.test_method_with_send_later(5,6)
|
86
|
+
obj.ran.should be_nil
|
87
|
+
obj.test_method_without_send_later(7)
|
88
|
+
obj.ran.should == [7]
|
89
|
+
obj.ran = nil
|
90
|
+
obj.ran.should == nil
|
91
|
+
obj.test_method_without_send_later(8,9)
|
92
|
+
obj.ran.should == [8,9]
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should send along enqueue args and args without default async" do
|
96
|
+
class TestObject
|
97
|
+
attr_reader :ran
|
98
|
+
def test_method(*args); @ran = args; end
|
99
|
+
add_send_later_methods(:test_method, {:enqueue_arg_2 => :thing2}, false)
|
100
|
+
end
|
101
|
+
obj = TestObject.new
|
102
|
+
method = double()
|
103
|
+
expect(Delayed::PerformableMethod).to receive(:new).with(obj, :test_method_without_send_later, [6]).and_return(method)
|
104
|
+
expect(Delayed::Job).to receive(:enqueue).with(method, :enqueue_arg_2 => :thing2)
|
105
|
+
obj.test_method_with_send_later(6)
|
106
|
+
expect(Delayed::PerformableMethod).to receive(:new).with(obj, :test_method_without_send_later, [5,6]).and_return(method)
|
107
|
+
expect(Delayed::Job).to receive(:enqueue).with(method, :enqueue_arg_2 => :thing2)
|
108
|
+
obj.test_method_with_send_later(5,6)
|
109
|
+
obj.ran.should be_nil
|
110
|
+
obj.test_method(1,2,3)
|
111
|
+
obj.ran.should == [1,2,3]
|
112
|
+
obj.ran = nil
|
113
|
+
obj.ran.should == nil
|
114
|
+
obj.test_method(4)
|
115
|
+
obj.ran.should == [4]
|
116
|
+
obj.ran = nil
|
117
|
+
obj.ran.should == nil
|
118
|
+
obj.test_method_without_send_later(7)
|
119
|
+
obj.ran.should == [7]
|
120
|
+
obj.ran = nil
|
121
|
+
obj.ran.should == nil
|
122
|
+
obj.test_method_without_send_later(8,9)
|
123
|
+
obj.ran.should == [8,9]
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should handle punctuation correctly with default_async" do
|
127
|
+
class TestObject
|
128
|
+
attr_reader :ran
|
129
|
+
def test_method?; @ran = true; end
|
130
|
+
add_send_later_methods :test_method?, {}, true
|
131
|
+
end
|
132
|
+
obj = TestObject.new
|
133
|
+
lambda { obj.test_method? }.should change { Delayed::Job.jobs_count(:current) }.by(1)
|
134
|
+
lambda { obj.test_method_with_send_later? }.should change { Delayed::Job.jobs_count(:current) }.by(1)
|
135
|
+
obj.ran.should be_falsey
|
136
|
+
lambda { obj.test_method_without_send_later? }.should_not change { Delayed::Job.jobs_count(:current) }
|
137
|
+
obj.ran.should be true
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should handle punctuation correctly without default_async" do
|
141
|
+
class TestObject
|
142
|
+
attr_accessor :ran
|
143
|
+
def test_method?; @ran = true; end
|
144
|
+
add_send_later_methods :test_method?, {}, false
|
145
|
+
end
|
146
|
+
obj = TestObject.new
|
147
|
+
lambda { obj.test_method_with_send_later? }.should change { Delayed::Job.jobs_count(:current) }.by(1)
|
148
|
+
obj.ran.should be_falsey
|
149
|
+
lambda { obj.test_method? }.should_not change { Delayed::Job.jobs_count(:current) }
|
150
|
+
obj.ran.should be true
|
151
|
+
obj.ran = false
|
152
|
+
obj.ran.should be false
|
153
|
+
lambda { obj.test_method_without_send_later? }.should_not change { Delayed::Job.jobs_count(:current) }
|
154
|
+
obj.ran.should be true
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should handle assignment punctuation correctly with default_async" do
|
158
|
+
class TestObject
|
159
|
+
attr_reader :ran
|
160
|
+
def test_method=(val); @ran = val; end
|
161
|
+
add_send_later_methods :test_method=, {}, true
|
162
|
+
end
|
163
|
+
obj = TestObject.new
|
164
|
+
lambda { obj.test_method = 3 }.should change { Delayed::Job.jobs_count(:current) }.by(1)
|
165
|
+
lambda { obj.test_method_with_send_later = 4 }.should change { Delayed::Job.jobs_count(:current) }.by(1)
|
166
|
+
obj.ran.should be_nil
|
167
|
+
lambda { obj.test_method_without_send_later = 5 }.should_not change { Delayed::Job.jobs_count(:current) }
|
168
|
+
obj.ran.should == 5
|
169
|
+
end
|
170
|
+
|
171
|
+
it "should handle assignment punctuation correctly without default_async" do
|
172
|
+
class TestObject
|
173
|
+
attr_accessor :ran
|
174
|
+
def test_method=(val); @ran = val; end
|
175
|
+
add_send_later_methods :test_method=, {}, false
|
176
|
+
end
|
177
|
+
obj = TestObject.new
|
178
|
+
lambda { obj.test_method_with_send_later = 1 }.should change { Delayed::Job.jobs_count(:current) }.by(1)
|
179
|
+
obj.ran.should be_nil
|
180
|
+
lambda { obj.test_method = 2 }.should_not change { Delayed::Job.jobs_count(:current) }
|
181
|
+
obj.ran.should == 2
|
182
|
+
lambda { obj.test_method_without_send_later = 3 }.should_not change { Delayed::Job.jobs_count(:current) }
|
183
|
+
obj.ran.should == 3
|
184
|
+
end
|
185
|
+
|
186
|
+
it "should correctly sort out method accessibility with default async" do
|
187
|
+
class TestObject1
|
188
|
+
def test_method; end
|
189
|
+
add_send_later_methods :test_method, {}, true
|
190
|
+
end
|
191
|
+
class TestObject2
|
192
|
+
protected
|
193
|
+
def test_method; end
|
194
|
+
add_send_later_methods :test_method, {}, true
|
195
|
+
end
|
196
|
+
class TestObject3
|
197
|
+
private
|
198
|
+
def test_method; end
|
199
|
+
add_send_later_methods :test_method, {}, true
|
200
|
+
end
|
201
|
+
TestObject1.public_method_defined?(:test_method).should be true
|
202
|
+
TestObject2.public_method_defined?(:test_method).should be false
|
203
|
+
TestObject3.public_method_defined?(:test_method).should be false
|
204
|
+
TestObject2.protected_method_defined?(:test_method).should be true
|
205
|
+
TestObject3.protected_method_defined?(:test_method).should be false
|
206
|
+
TestObject3.private_method_defined?(:test_method).should be true
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
context "handle_asynchonously_if_production" do
|
211
|
+
it "should work in production" do
|
212
|
+
expect(Rails.env).to receive(:production?).and_return(true)
|
213
|
+
class TestObject; end
|
214
|
+
expect(TestObject).to receive(:add_send_later_methods).with(:test_method, {}, true)
|
215
|
+
class TestObject
|
216
|
+
def test_method; end
|
217
|
+
handle_asynchronously_if_production :test_method
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
it "should work in other environments" do
|
222
|
+
class TestObject; end
|
223
|
+
expect(TestObject).to receive(:add_send_later_methods).with(:test_method, {}, false)
|
224
|
+
class TestObject
|
225
|
+
def test_method; end
|
226
|
+
handle_asynchronously_if_production :test_method
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
it "should pass along enqueue args in production" do
|
231
|
+
expect(Rails.env).to receive(:production?).and_return(true)
|
232
|
+
class TestObject; end
|
233
|
+
expect(TestObject).to receive(:add_send_later_methods).with(:test_method, {:enqueue_arg_1 => true}, true)
|
234
|
+
class TestObject
|
235
|
+
def test_method; end
|
236
|
+
handle_asynchronously_if_production :test_method, :enqueue_arg_1 => true
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
it "should pass along enqueue args in other environments" do
|
241
|
+
class TestObject; end
|
242
|
+
expect(TestObject).to receive(:add_send_later_methods).with(:test_method, {:enqueue_arg_2 => "thing", :enqueue_arg_3 => 4}, false)
|
243
|
+
class TestObject
|
244
|
+
def test_method; end
|
245
|
+
handle_asynchronously_if_production :test_method, :enqueue_arg_2 => "thing", :enqueue_arg_3 => 4
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
context "handle_asynchronously" do
|
251
|
+
it "should work without enqueue_args" do
|
252
|
+
class TestObject; end
|
253
|
+
expect(TestObject).to receive(:add_send_later_methods).with(:test_method, {}, true)
|
254
|
+
class TestObject
|
255
|
+
def test_method; end
|
256
|
+
handle_asynchronously :test_method
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
it "should work with enqueue_args" do
|
261
|
+
class TestObject; end
|
262
|
+
expect(TestObject).to receive(:add_send_later_methods).with(:test_method, {:enqueue_arg_1 => :thing}, true)
|
263
|
+
class TestObject
|
264
|
+
def test_method; end
|
265
|
+
handle_asynchronously :test_method, :enqueue_arg_1 => :thing
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
context "handle_asynchronously_with_queue" do
|
271
|
+
it "should pass along the queue" do
|
272
|
+
class TestObject; end
|
273
|
+
expect(TestObject).to receive(:add_send_later_methods).with(:test_method, {:queue => "myqueue"}, true)
|
274
|
+
class TestObject
|
275
|
+
def test_method; end
|
276
|
+
handle_asynchronously_with_queue :test_method, "myqueue"
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
it "should call send later on methods which are wrapped with handle_asynchronously" do
|
283
|
+
story = Story.create :text => 'Once upon...'
|
284
|
+
|
285
|
+
expect { story.whatever(1, 5) }.to change { Delayed::Job.jobs_count(:current) }.by(1)
|
286
|
+
|
287
|
+
job = Delayed::Job.list_jobs(:current, 1).first
|
288
|
+
job.payload_object.class.should == Delayed::PerformableMethod
|
289
|
+
job.payload_object.method.should == :whatever_without_send_later
|
290
|
+
job.payload_object.args.should == [1, 5]
|
291
|
+
job.payload_object.perform.should == 'Once upon...'
|
292
|
+
end
|
293
|
+
|
294
|
+
it "should call send later on methods which are wrapped with handle_asynchronously_with_queue" do
|
295
|
+
story = Story.create :text => 'Once upon...'
|
296
|
+
|
297
|
+
expect { story.whatever_else(1, 5) }.to change { Delayed::Job.jobs_count(:current, "testqueue") }.by(1)
|
298
|
+
|
299
|
+
job = Delayed::Job.list_jobs(:current, 1, 0, "testqueue").first
|
300
|
+
job.payload_object.class.should == Delayed::PerformableMethod
|
301
|
+
job.payload_object.method.should == :whatever_else_without_send_later
|
302
|
+
job.payload_object.args.should == [1, 5]
|
303
|
+
job.payload_object.perform.should == 'Once upon...'
|
304
|
+
end
|
305
|
+
|
306
|
+
context "send_later" do
|
307
|
+
it "should use the default queue if there is one" do
|
308
|
+
set_queue("testqueue") do
|
309
|
+
"string".send_later :reverse
|
310
|
+
job = Delayed::Job.list_jobs(:current, 1).first
|
311
|
+
job.queue.should == "testqueue"
|
312
|
+
|
313
|
+
"string".send_later :reverse, :queue => nil
|
314
|
+
job2 = Delayed::Job.list_jobs(:current, 2).last
|
315
|
+
job2.queue.should == "testqueue"
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
it "should require a queue" do
|
320
|
+
expect { set_queue(nil) }.to raise_error(ArgumentError)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
context "send_at" do
|
325
|
+
it "should queue a new job" do
|
326
|
+
lambda do
|
327
|
+
"string".send_at(1.hour.from_now, :length)
|
328
|
+
end.should change { Delayed::Job.jobs_count(:future) }.by(1)
|
329
|
+
end
|
330
|
+
|
331
|
+
it "should schedule the job in the future" do
|
332
|
+
time = 1.hour.from_now
|
333
|
+
"string".send_at(time, :length)
|
334
|
+
job = Delayed::Job.list_jobs(:future, 1).first
|
335
|
+
job.run_at.to_i.should == time.to_i
|
336
|
+
end
|
337
|
+
|
338
|
+
it "should store payload as PerformableMethod" do
|
339
|
+
"string".send_at(1.hour.from_now, :count, 'r')
|
340
|
+
job = Delayed::Job.list_jobs(:future, 1).first
|
341
|
+
job.payload_object.class.should == Delayed::PerformableMethod
|
342
|
+
job.payload_object.method.should == :count
|
343
|
+
job.payload_object.args.should == ['r']
|
344
|
+
job.payload_object.perform.should == 1
|
345
|
+
end
|
346
|
+
|
347
|
+
it "should use the default queue if there is one" do
|
348
|
+
set_queue("testqueue") do
|
349
|
+
"string".send_at 1.hour.from_now, :reverse
|
350
|
+
job = Delayed::Job.list_jobs(:current, 1).first
|
351
|
+
job.queue.should == "testqueue"
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
context "send_at_with_queue" do
|
357
|
+
it "should queue a new job" do
|
358
|
+
lambda do
|
359
|
+
"string".send_at_with_queue(1.hour.from_now, :length, "testqueue")
|
360
|
+
end.should change { Delayed::Job.jobs_count(:future, "testqueue") }.by(1)
|
361
|
+
end
|
362
|
+
|
363
|
+
it "should schedule the job in the future" do
|
364
|
+
time = 1.hour.from_now
|
365
|
+
"string".send_at_with_queue(time, :length, "testqueue")
|
366
|
+
job = Delayed::Job.list_jobs(:future, 1, 0, "testqueue").first
|
367
|
+
job.run_at.to_i.should == time.to_i
|
368
|
+
end
|
369
|
+
|
370
|
+
it "should override the default queue" do
|
371
|
+
set_queue("default_queue") do
|
372
|
+
"string".send_at_with_queue(1.hour.from_now, :length, "testqueue")
|
373
|
+
job = Delayed::Job.list_jobs(:future, 1, 0, "testqueue").first
|
374
|
+
job.queue.should == "testqueue"
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
it "should store payload as PerformableMethod" do
|
379
|
+
"string".send_at_with_queue(1.hour.from_now, :count, "testqueue", 'r')
|
380
|
+
job = Delayed::Job.list_jobs(:future, 1, 0, "testqueue").first
|
381
|
+
job.payload_object.class.should == Delayed::PerformableMethod
|
382
|
+
job.payload_object.method.should == :count
|
383
|
+
job.payload_object.args.should == ['r']
|
384
|
+
job.payload_object.perform.should == 1
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
describe "send_later_unless_in_job" do
|
389
|
+
module UnlessInJob
|
390
|
+
@runs = 0
|
391
|
+
def self.runs; @runs; end
|
392
|
+
|
393
|
+
def self.run
|
394
|
+
@runs += 1
|
395
|
+
end
|
396
|
+
|
397
|
+
def self.run_later
|
398
|
+
self.send_later_unless_in_job :run
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
before do
|
403
|
+
UnlessInJob.class_eval { @runs = 0 }
|
404
|
+
end
|
405
|
+
|
406
|
+
it "should perform immediately if in job" do
|
407
|
+
UnlessInJob.send_later :run_later
|
408
|
+
job = Delayed::Job.list_jobs(:current, 1).first
|
409
|
+
job.invoke_job
|
410
|
+
UnlessInJob.runs.should == 1
|
411
|
+
end
|
412
|
+
|
413
|
+
it "should queue up for later if not in job" do
|
414
|
+
UnlessInJob.run_later
|
415
|
+
UnlessInJob.runs.should == 0
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
end
|