delayed_cron_job 0.7.2 → 0.7.3
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 +5 -5
- data/.gitignore +2 -1
- data/.travis.yml +1 -1
- data/README.md +113 -5
- data/lib/delayed_cron_job/backend/updatable_cron.rb +13 -1
- data/lib/delayed_cron_job/plugin.rb +11 -17
- data/lib/delayed_cron_job/version.rb +1 -1
- data/spec/delayed_cron_job_spec.rb +45 -9
- data/spec/spec_helper.rb +1 -1
- metadata +6 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4fd3901277379b8a03de511f904d2fc6c4ef3ccf1e3ed968592f2c2a6f097c7a
|
4
|
+
data.tar.gz: caed3c5171d1c4be7860196607f377dc37a1b5f852f61e468bb9e73559a84176
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 06bd045e0eeb858c8eb226b286bc8de8a063a655fd2598b086fa9c7bf41c08b6c28ecd5d8767cb02793f950ed4253f6eb8ea551e93a497cae0ef6cc8cd10aed4
|
7
|
+
data.tar.gz: aa2ec557d380171cb518ce64ec2c558eecc97c8855040e48a4b95b0669aa911861ebb09e03ab3ee8551de0e4531efa79cf5b108f1e1071ff618178d9063486a7
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Delayed::Cron::Job
|
2
2
|
|
3
|
-
|
3
|
+
[](https://travis-ci.org/codez/delayed_cron_job)
|
4
4
|
|
5
5
|
Delayed::Cron::Job is an extension to Delayed::Job that allows you to set
|
6
6
|
cron expressions for your jobs to run repeatedly.
|
@@ -28,16 +28,123 @@ There are no additional steps for `delayed_job_mongoid`.
|
|
28
28
|
|
29
29
|
When enqueuing a job, simply pass the `cron` option, e.g.:
|
30
30
|
|
31
|
-
|
31
|
+
```ruby
|
32
|
+
Delayed::Job.enqueue(MyRepeatedJob.new, cron: '15 */6 * * 1-5')
|
33
|
+
```
|
32
34
|
|
33
|
-
Or, when using
|
35
|
+
Or, when using ActiveJob:
|
34
36
|
|
35
|
-
|
37
|
+
```ruby
|
38
|
+
MyJob.set(cron: '*/5 * * * *').perform_later
|
39
|
+
```
|
36
40
|
|
37
41
|
Any crontab compatible cron expressions are supported (see `man 5 crontab`).
|
38
42
|
The credits for the `Cronline` class used go to
|
39
43
|
[rufus-scheduler](https://github.com/jmettraux/rufus-scheduler).
|
40
44
|
|
45
|
+
## Scheduling
|
46
|
+
|
47
|
+
Usually, you want to schedule all existing cron jobs when deploying the
|
48
|
+
application. Using a common super class makes this simple.
|
49
|
+
|
50
|
+
### Custom CronJob superclass
|
51
|
+
|
52
|
+
`app/jobs/cron_job.rb`:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
# Default configuration in `app/jobs/application_job.rb`, or subclass
|
56
|
+
# ActiveJob::Base .
|
57
|
+
class CronJob < ApplicationJob
|
58
|
+
|
59
|
+
class_attribute :cron_expression
|
60
|
+
|
61
|
+
class << self
|
62
|
+
|
63
|
+
def schedule
|
64
|
+
set(cron: cron_expression).perform_later unless scheduled?
|
65
|
+
end
|
66
|
+
|
67
|
+
def remove
|
68
|
+
delayed_job.destroy if scheduled?
|
69
|
+
end
|
70
|
+
|
71
|
+
def scheduled?
|
72
|
+
delayed_job.present?
|
73
|
+
end
|
74
|
+
|
75
|
+
def delayed_job
|
76
|
+
Delayed::Job
|
77
|
+
.where('handler LIKE ?', "%job_class: #{name}%")
|
78
|
+
.first
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
### Example Job inheriting from CronJob
|
86
|
+
|
87
|
+
Then, an example job that triggers E-Mail-sending with default cron time at
|
88
|
+
noon every day:
|
89
|
+
|
90
|
+
`app/jobs/noon_job.rb`:
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
|
94
|
+
# Note that it inherits from `CronJob`
|
95
|
+
class NoonJob < CronJob
|
96
|
+
# set the (default) cron expression
|
97
|
+
self.cron_expression = '0 12 * * *'
|
98
|
+
|
99
|
+
# will enqueue the mailing delivery job
|
100
|
+
def perform
|
101
|
+
UserMailer.daily_notice(User.first).deliver_later
|
102
|
+
end
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
### Scheduling "trigger"
|
107
|
+
|
108
|
+
Jobs with a `cron` definition are rescheduled automatically only when a job
|
109
|
+
instance finished its work. So there needs to be an initial scheduling of all
|
110
|
+
cron jobs. If you do not want to do this manually (e.g. using `rails console`)
|
111
|
+
or with your application logic, you can e.g. hook into the `rails db:*` rake
|
112
|
+
tasks:
|
113
|
+
|
114
|
+
`lib/tasks/jobs.rake`:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
namespace :db do
|
118
|
+
desc 'Schedule all cron jobs'
|
119
|
+
task :schedule_jobs => :environment do
|
120
|
+
# Need to load all jobs definitions in order to find subclasses
|
121
|
+
glob = Rails.root.join('app', 'jobs', '**', '*_job.rb')
|
122
|
+
Dir.glob(glob).each { |file| require file }
|
123
|
+
CronJob.subclasses.each { |job| job.schedule }
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# invoke schedule_jobs automatically after every migration and schema load.
|
128
|
+
%w(db:migrate db:schema:load).each do |task|
|
129
|
+
Rake::Task[task].enhance do
|
130
|
+
Rake::Task['db:schedule_jobs'].invoke
|
131
|
+
end
|
132
|
+
end
|
133
|
+
```
|
134
|
+
|
135
|
+
Now, if you run `rails db:migrate`, `rails db:schema:load` or `rails
|
136
|
+
db:schedule_jobs` all jobs inheriting from `CronJob` are scheduled.
|
137
|
+
|
138
|
+
*If you are not using ActiveJob, the same approach may be used with minor
|
139
|
+
adjustments.*
|
140
|
+
|
141
|
+
### Changing the schedule
|
142
|
+
|
143
|
+
Note that if you have a CronJob scheduled and change its `cron_expression` in
|
144
|
+
its source file, you will have to remove any scheduled instances of the Job and
|
145
|
+
reschedule it (e.g. with the snippet above: `rails db:migrate`). This is because
|
146
|
+
the `cron_expression` is already persisted in the database (as `cron`).
|
147
|
+
|
41
148
|
## Details
|
42
149
|
|
43
150
|
The initial `run_at` value is computed during the `#enqueue` method call.
|
@@ -71,4 +178,5 @@ jobs.
|
|
71
178
|
## License
|
72
179
|
|
73
180
|
Delayed::Cron::Job is released under the terms of the MIT License.
|
74
|
-
Copyright 2014-
|
181
|
+
Copyright 2014-2020 Pascal Zumkehr. See [LICENSE](LICENSE) for further
|
182
|
+
information.
|
@@ -4,6 +4,7 @@ module DelayedCronJob
|
|
4
4
|
|
5
5
|
def self.included(klass)
|
6
6
|
klass.send(:before_save, :set_next_run_at, :if => :cron_changed?)
|
7
|
+
klass.attr_accessor :schedule_instead_of_destroy
|
7
8
|
end
|
8
9
|
|
9
10
|
def set_next_run_at
|
@@ -12,6 +13,17 @@ module DelayedCronJob
|
|
12
13
|
end
|
13
14
|
end
|
14
15
|
|
16
|
+
def destroy
|
17
|
+
super unless schedule_instead_of_destroy
|
18
|
+
end
|
19
|
+
|
20
|
+
def schedule_next_run
|
21
|
+
self.attempts += 1
|
22
|
+
unlock
|
23
|
+
set_next_run_at
|
24
|
+
save!
|
25
|
+
end
|
26
|
+
|
15
27
|
end
|
16
28
|
end
|
17
|
-
end
|
29
|
+
end
|
@@ -17,7 +17,6 @@ module DelayedCronJob
|
|
17
17
|
worker.job_say(job,
|
18
18
|
"FAILED with #{$ERROR_INFO.class.name}: #{$ERROR_INFO.message}",
|
19
19
|
Logger::ERROR)
|
20
|
-
job.destroy
|
21
20
|
else
|
22
21
|
# No cron job - proceed as normal
|
23
22
|
block.call(worker, job)
|
@@ -26,31 +25,26 @@ module DelayedCronJob
|
|
26
25
|
|
27
26
|
# Reset the last_error to have the correct status of the last run.
|
28
27
|
lifecycle.before(:perform) do |worker, job|
|
29
|
-
if cron?(job)
|
30
|
-
job.last_error = nil
|
31
|
-
end
|
28
|
+
job.last_error = nil if cron?(job)
|
32
29
|
end
|
33
30
|
|
34
|
-
#
|
31
|
+
# Prevent destruction of cron jobs
|
35
32
|
lifecycle.after(:invoke_job) do |job|
|
36
|
-
if cron?(job)
|
37
|
-
job.cron = job.class.where(:id => job.id).pluck(:cron).first
|
38
|
-
end
|
33
|
+
job.schedule_instead_of_destroy = true if cron?(job)
|
39
34
|
end
|
40
35
|
|
41
36
|
# Schedule the next run based on the cron attribute.
|
42
37
|
lifecycle.after(:perform) do |worker, job|
|
43
|
-
if cron?(job)
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
38
|
+
if cron?(job) && !job.destroyed?
|
39
|
+
job.cron = job.class.where(:id => job.id).pluck(:cron).first
|
40
|
+
if job.cron.present?
|
41
|
+
job.schedule_next_run
|
42
|
+
else
|
43
|
+
job.schedule_instead_of_destroy = false
|
44
|
+
job.destroy
|
45
|
+
end
|
51
46
|
end
|
52
47
|
end
|
53
48
|
end
|
54
|
-
|
55
49
|
end
|
56
50
|
end
|
@@ -6,6 +6,16 @@ describe DelayedCronJob do
|
|
6
6
|
def perform; end
|
7
7
|
end
|
8
8
|
|
9
|
+
class DatabaseDisconnectPlugin < Delayed::Plugin
|
10
|
+
|
11
|
+
callbacks do |lifecycle|
|
12
|
+
lifecycle.after(:perform) do
|
13
|
+
ActiveRecord::Base.connection.disconnect!
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
9
19
|
before { Delayed::Job.delete_all }
|
10
20
|
|
11
21
|
let(:cron) { '5 1 * * *' }
|
@@ -25,8 +35,8 @@ describe DelayedCronJob do
|
|
25
35
|
end
|
26
36
|
|
27
37
|
it 'enqueue fails with invalid cron' do
|
28
|
-
expect { Delayed::Job.enqueue(handler, cron: 'no valid cron') }
|
29
|
-
to raise_error(ArgumentError)
|
38
|
+
expect { Delayed::Job.enqueue(handler, cron: 'no valid cron') }
|
39
|
+
.to raise_error(ArgumentError)
|
30
40
|
end
|
31
41
|
|
32
42
|
it 'schedules a new job after success' do
|
@@ -74,19 +84,16 @@ describe DelayedCronJob do
|
|
74
84
|
expect(j.cron).to eq(job.cron)
|
75
85
|
expect(j.run_at).to eq(next_run)
|
76
86
|
expect(j.attempts).to eq(1)
|
77
|
-
expect(j.last_error).to match(
|
87
|
+
expect(j.last_error).to match('execution expired')
|
78
88
|
end
|
79
89
|
|
80
|
-
it '
|
81
|
-
Delayed::Worker.max_run_time = 1.second
|
90
|
+
it 'does not schedule new job after deserialization error' do
|
82
91
|
job.update_column(:run_at, now)
|
83
92
|
allow_any_instance_of(TestJob).to receive(:perform).and_raise(Delayed::DeserializationError)
|
84
93
|
|
85
94
|
worker.work_off
|
86
95
|
|
87
|
-
expect(Delayed::Job.count).to eq(
|
88
|
-
j = Delayed::Job.first
|
89
|
-
expect(j.last_error).to match("Delayed::DeserializationError")
|
96
|
+
expect(Delayed::Job.count).to eq(0)
|
90
97
|
end
|
91
98
|
|
92
99
|
it 'has empty last_error after success' do
|
@@ -116,7 +123,7 @@ describe DelayedCronJob do
|
|
116
123
|
run_at = Time.utc(run.year, run.month, run.day, hour, (now.min + 1) % 60)
|
117
124
|
expect(job.run_at).to eq(run_at)
|
118
125
|
else
|
119
|
-
pending
|
126
|
+
pending 'This test only makes sense in non-UTC time zone'
|
120
127
|
end
|
121
128
|
end
|
122
129
|
|
@@ -168,6 +175,35 @@ describe DelayedCronJob do
|
|
168
175
|
expect { worker.work_off }.to change { Delayed::Job.count }.by(-1)
|
169
176
|
end
|
170
177
|
|
178
|
+
context 'when database connection is lost' do
|
179
|
+
around(:each) do |example|
|
180
|
+
Delayed::Worker.plugins.unshift DatabaseDisconnectPlugin
|
181
|
+
# hold onto a connection so the in-memory database isn't lost when disconnected
|
182
|
+
temp_connection = ActiveRecord::Base.connection_pool.checkout
|
183
|
+
example.run
|
184
|
+
ActiveRecord::Base.connection_pool.checkin temp_connection
|
185
|
+
Delayed::Worker.plugins.delete DatabaseDisconnectPlugin
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'does not lose the job if database connection is lost' do
|
189
|
+
job.update_column(:run_at, now)
|
190
|
+
job.reload # adjusts granularity of run_at datetime
|
191
|
+
|
192
|
+
begin
|
193
|
+
worker.work_off
|
194
|
+
rescue StandardError
|
195
|
+
# Attempting to save the clone delayed_job will raise an exception due to the database connection being closed
|
196
|
+
end
|
197
|
+
|
198
|
+
ActiveRecord::Base.connection.reconnect!
|
199
|
+
|
200
|
+
expect(Delayed::Job.count).to eq(1)
|
201
|
+
j = Delayed::Job.first
|
202
|
+
expect(j.id).to eq(job.id)
|
203
|
+
expect(j.cron).to eq(job.cron)
|
204
|
+
expect(j.attempts).to eq(0)
|
205
|
+
end
|
206
|
+
end
|
171
207
|
end
|
172
208
|
|
173
209
|
context 'without cron' do
|
data/spec/spec_helper.rb
CHANGED
@@ -23,7 +23,7 @@ ENV['RAILS_ENV'] = 'test'
|
|
23
23
|
|
24
24
|
ActiveJob::Base.queue_adapter = :delayed_job
|
25
25
|
|
26
|
-
ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => '
|
26
|
+
ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => 'file::memory:?cache=shared'
|
27
27
|
ActiveRecord::Base.logger = Delayed::Worker.logger
|
28
28
|
ActiveRecord::Migration.verbose = false
|
29
29
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: delayed_cron_job
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pascal Zumkehr
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-06-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: delayed_job
|
@@ -142,7 +142,7 @@ homepage: https://github.com/codez/delayed_cron_job
|
|
142
142
|
licenses:
|
143
143
|
- MIT
|
144
144
|
metadata: {}
|
145
|
-
post_install_message:
|
145
|
+
post_install_message:
|
146
146
|
rdoc_options: []
|
147
147
|
require_paths:
|
148
148
|
- lib
|
@@ -157,9 +157,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
157
157
|
- !ruby/object:Gem::Version
|
158
158
|
version: '0'
|
159
159
|
requirements: []
|
160
|
-
|
161
|
-
|
162
|
-
signing_key:
|
160
|
+
rubygems_version: 3.1.2
|
161
|
+
signing_key:
|
163
162
|
specification_version: 4
|
164
163
|
summary: An extension to Delayed::Job that allows you to set cron expressions for
|
165
164
|
your jobs to run regularly.
|