good_job 1.11.3 → 1.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +47 -3
- data/engine/app/views/good_job/active_jobs/show.html.erb +1 -1
- data/engine/app/views/good_job/dashboards/index.html.erb +2 -2
- data/engine/app/views/{shared → good_job/shared}/_chart.erb +2 -2
- data/engine/app/views/{shared → good_job/shared}/_jobs_table.erb +1 -1
- data/engine/app/views/{shared → good_job/shared}/icons/_check.html.erb +0 -0
- data/engine/app/views/{shared → good_job/shared}/icons/_exclamation.html.erb +0 -0
- data/engine/app/views/{shared → good_job/shared}/icons/_trash.html.erb +0 -0
- data/engine/app/views/layouts/good_job/base.html.erb +2 -2
- data/lib/good_job.rb +12 -7
- data/lib/good_job/adapter.rb +2 -0
- data/lib/good_job/cli.rb +7 -2
- data/lib/good_job/configuration.rb +24 -0
- data/lib/good_job/cron_manager.rb +115 -0
- data/lib/good_job/current_execution.rb +6 -0
- data/lib/good_job/job.rb +21 -0
- data/lib/good_job/log_subscriber.rb +10 -0
- data/lib/good_job/railtie.rb +2 -0
- data/lib/good_job/version.rb +1 -1
- metadata +22 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bdf4b1eebb1224acd53f07753d5cda9cb9fde7a44baf300824d7816da94980f2
|
4
|
+
data.tar.gz: 4b90c9cd8133f179f87094683218ed1a1aed3582512169dd914a00dd62a81628
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e95350cb8bd69198231d8030cc07adb9c87649499d30b6aa467ad4a06244920f2e950f46eae891e6963532fa879000959fcaa2eddf6093b8cd9d960eefef9abb
|
7
|
+
data.tar.gz: 1e09cf2851ca64fc34407e29f5afbac5bd7a72f072cef7c22798458b9ebc5678e287abbb03ee0d070cea8aa0665d85b017b94b68737e3e26ce7c0f67905c66e0
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v1.12.0](https://github.com/bensheldon/good_job/tree/v1.12.0) (2021-07-27)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.11.3...v1.12.0)
|
6
|
+
|
7
|
+
**Implemented enhancements:**
|
8
|
+
|
9
|
+
- Add the ability to schedule repeating / recurring / cron-like jobs [\#53](https://github.com/bensheldon/good_job/issues/53)
|
10
|
+
- Add cron-like support for recurring/repeating jobs [\#297](https://github.com/bensheldon/good_job/pull/297) ([bensheldon](https://github.com/bensheldon))
|
11
|
+
|
12
|
+
**Merged pull requests:**
|
13
|
+
|
14
|
+
- Place Dashboard shared view partials under `good_job` namespace [\#310](https://github.com/bensheldon/good_job/pull/310) ([bensheldon](https://github.com/bensheldon))
|
15
|
+
- Ensure Dashboard inline javascript has CSP nonce for strict Content-Security Policy [\#309](https://github.com/bensheldon/good_job/pull/309) ([bensheldon](https://github.com/bensheldon))
|
16
|
+
|
3
17
|
## [v1.11.3](https://github.com/bensheldon/good_job/tree/v1.11.3) (2021-07-25)
|
4
18
|
|
5
19
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.11.2...v1.11.3)
|
data/README.md
CHANGED
@@ -38,7 +38,8 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla
|
|
38
38
|
- [Configuration options](#configuration-options)
|
39
39
|
- [Global options](#global-options)
|
40
40
|
- [Dashboard](#dashboard)
|
41
|
-
- [ActiveJob
|
41
|
+
- [ActiveJob concurrency](#activejob-concurrency)
|
42
|
+
- [Cron-style repeating/recurring jobs](#cron-style-repeatingrecurring-jobs)
|
42
43
|
- [Updating](#updating)
|
43
44
|
- [Go deeper](#go-deeper)
|
44
45
|
- [Exceptions, retries, and reliability](#exceptions-retries-and-reliability)
|
@@ -156,6 +157,7 @@ Options:
|
|
156
157
|
[--poll-interval=SECONDS] # Interval between polls for available jobs in seconds (env var: GOOD_JOB_POLL_INTERVAL, default: 1)
|
157
158
|
[--max-cache=COUNT] # Maximum number of scheduled jobs to cache in memory (env var: GOOD_JOB_MAX_CACHE, default: 10000)
|
158
159
|
[--shutdown-timeout=SECONDS] # Number of seconds to wait for jobs to finish when shutting down before stopping the thread. (env var: GOOD_JOB_SHUTDOWN_TIMEOUT, default: -1 (forever))
|
160
|
+
[--enable-cron] # Whether to run cron process (default: false)
|
159
161
|
[--daemonize] # Run as a background daemon (default: false)
|
160
162
|
[--pidfile=PIDFILE] # Path to write daemonized Process ID (env var: GOOD_JOB_PIDFILE, default: tmp/pids/good_job.pid)
|
161
163
|
|
@@ -212,7 +214,8 @@ config.good_job.execution_mode = :async_server
|
|
212
214
|
config.good_job.max_threads = 5
|
213
215
|
config.good_job.poll_interval = 30 # seconds
|
214
216
|
config.good_job.shutdown_timeout = 25 # seconds
|
215
|
-
|
217
|
+
config.good_job.enable_cron = true
|
218
|
+
config.good_job.cron = { example: { cron: '0 * * * *', class: 'ExampleJob' } }
|
216
219
|
|
217
220
|
# ...or all at once.
|
218
221
|
config.good_job = {
|
@@ -220,6 +223,13 @@ config.good_job = {
|
|
220
223
|
max_threads: 5,
|
221
224
|
poll_interval: 30,
|
222
225
|
shutdown_timeout: 25,
|
226
|
+
enable_cron: true,
|
227
|
+
cron: {
|
228
|
+
example: {
|
229
|
+
cron: '0 * * * *',
|
230
|
+
class: 'ExampleJob'
|
231
|
+
},
|
232
|
+
},
|
223
233
|
}
|
224
234
|
```
|
225
235
|
|
@@ -235,6 +245,8 @@ Available configuration options are:
|
|
235
245
|
- `poll_interval` (integer) sets the number of seconds between polls for jobs when `execution_mode` is set to `:async` or `:async_server`. You can also set this with the environment variable `GOOD_JOB_POLL_INTERVAL`.
|
236
246
|
- `max_cache` (integer) sets the maximum number of scheduled jobs that will be stored in memory to reduce execution latency when also polling for scheduled jobs. Caching 10,000 scheduled jobs uses approximately 20MB of memory. You can also set this with the environment variable `GOOD_JOB_MAX_CACHE`.
|
237
247
|
- `shutdown_timeout` (float) number of seconds to wait for jobs to finish when shutting down before stopping the thread. Defaults to forever: `-1`. You can also set this with the environment variable `GOOD_JOB_SHUTDOWN_TIMEOUT`.
|
248
|
+
- `enable_cron` (boolean) whether to run cron process. Defaults to `false`. You can also set this with the environment variable `GOOD_JOB_ENABLE_CRON`.
|
249
|
+
- `cron` (hash) cron configuration. Defaults to `{}`. You can also set this as a JSON string with the environment variable `GOOD_JOB_CRON`
|
238
250
|
|
239
251
|
By default, GoodJob configures the following execution modes per environment:
|
240
252
|
|
@@ -320,7 +332,7 @@ GoodJob includes a Dashboard as a mountable `Rails::Engine`.
|
|
320
332
|
end
|
321
333
|
```
|
322
334
|
|
323
|
-
### ActiveJob
|
335
|
+
### ActiveJob concurrency
|
324
336
|
|
325
337
|
GoodJob can extend ActiveJob to provide limits on concurrently running jobs, either at time of _enqueue_ or at _perform_.
|
326
338
|
|
@@ -349,6 +361,38 @@ class MyJob < ApplicationJob
|
|
349
361
|
end
|
350
362
|
```
|
351
363
|
|
364
|
+
### Cron-style repeating/recurring jobs
|
365
|
+
|
366
|
+
GoodJob can enqueue jobs on a recurring basis that can be used as a replacement for cron.
|
367
|
+
|
368
|
+
Cron-style jobs are run on every GoodJob process (e.g. CLI or `async` execution mode) when `config.good_job.enable_cron = true`; use GoodJob's [ActiveJob concurrency](#activejob-concurrency) extension to limit the number of jobs that are enqueued.
|
369
|
+
|
370
|
+
Cron-format is parsed by the [`fugit`](https://github.com/floraison/fugit) gem, which has support for seconds-level resolution (e.g. `* * * * * *`).
|
371
|
+
|
372
|
+
```ruby
|
373
|
+
# config/environments/application.rb or a specific environment e.g. production.rb
|
374
|
+
|
375
|
+
# Enable cron in this process; e.g. only run on the first Heroku worker process
|
376
|
+
config.good_job.enable_cron = ENV['DYNO'] == 'worker.1' # or `true` or via $GOOD_JOB_ENABLE_CRON
|
377
|
+
|
378
|
+
# Configure cron with a hash that has a unique key for each recurring job
|
379
|
+
config.good_job.cron = {
|
380
|
+
# Every 15 minutes, enqueue `ExampleJob.set(priority: -10).perform_later(52, name: "Alice")`
|
381
|
+
frequent_task: { # each recurring job must have a unique key
|
382
|
+
cron: "*/15 * * * *", # cron-style scheduling format by fugit gem
|
383
|
+
class: "ExampleJob", # reference the Job class with a string
|
384
|
+
args: [42, { name: "Alice" }], # arguments to pass; can also be a proc e.g. `-> { { when: Time.now } }`
|
385
|
+
set: { priority: -10 }, # additional ActiveJob properties; can also be a lambda/proc e.g. `-> { { priority: [1,2].sample } }`
|
386
|
+
description: "Something helpful", # optional description that appears in Dashboard (coming soon!)
|
387
|
+
},
|
388
|
+
another_task: {
|
389
|
+
cron: "0 0,12 * * *",
|
390
|
+
class: "AnotherJob",
|
391
|
+
},
|
392
|
+
# etc.
|
393
|
+
}
|
394
|
+
```
|
395
|
+
|
352
396
|
### Updating
|
353
397
|
|
354
398
|
GoodJob follows semantic versioning, though updates may be encouraged through deprecation warnings in minor versions.
|
@@ -1 +1 @@
|
|
1
|
-
<%= render 'shared/jobs_table', jobs: @jobs %>
|
1
|
+
<%= render 'good_job/shared/jobs_table', jobs: @jobs %>
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<div class="card my-3 p-6">
|
2
|
-
<%= render 'shared/chart', chart_data: @chart %>
|
2
|
+
<%= render 'good_job/shared/chart', chart_data: @chart %>
|
3
3
|
</div>
|
4
4
|
|
5
5
|
<div class='card mb-2'>
|
@@ -38,7 +38,7 @@
|
|
38
38
|
</div>
|
39
39
|
|
40
40
|
<% if @filter.jobs.present? %>
|
41
|
-
<%= render 'shared/jobs_table', jobs: @filter.jobs %>
|
41
|
+
<%= render 'good_job/shared/jobs_table', jobs: @filter.jobs %>
|
42
42
|
|
43
43
|
<nav aria-label="Job pagination" class="mt-3">
|
44
44
|
<ul class="pagination">
|
@@ -1,6 +1,6 @@
|
|
1
1
|
<div id="chart"></div>
|
2
2
|
|
3
|
-
|
3
|
+
<%= javascript_tag nonce: true do %>
|
4
4
|
new Chartist.Line('#chart', <%== chart_data.to_json %>, {
|
5
5
|
height: '300px',
|
6
6
|
fullWidth: true,
|
@@ -49,4 +49,4 @@
|
|
49
49
|
tooltipEl.style.left = (event.offsetX || event.originalEvent.layerX) + tooltipEl.offsetWidth + 10 + 'px';
|
50
50
|
tooltipEl.style.top = (event.offsetY || event.originalEvent.layerY) + tooltipEl.offsetHeight - 20 + 'px';
|
51
51
|
}, true);
|
52
|
-
|
52
|
+
<% end %>
|
File without changes
|
File without changes
|
File without changes
|
@@ -53,13 +53,13 @@
|
|
53
53
|
|
54
54
|
<% if notice %>
|
55
55
|
<div class="alert alert-success alert-dismissible fade show d-flex align-items-center offset-md-3 col-6" role="alert">
|
56
|
-
<%= render "shared/icons/check", class: "flex-shrink-0 me-2" %>
|
56
|
+
<%= render "good_job/shared/icons/check", class: "flex-shrink-0 me-2" %>
|
57
57
|
<div><%= notice %></div>
|
58
58
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
59
59
|
</div>
|
60
60
|
<% elsif alert %>
|
61
61
|
<div class="alert alert-warning alert-dismissible fade show d-flex align-items-center offset-md-3 col-6" role="alert">
|
62
|
-
<%= render "shared/icons/check", class: "flex-shrink-0 me-2" %>
|
62
|
+
<%= render "good_job/shared/icons/check", class: "flex-shrink-0 me-2" %>
|
63
63
|
<div><%= alert %></div>
|
64
64
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
65
65
|
</div>
|
data/lib/good_job.rb
CHANGED
@@ -106,16 +106,13 @@ module GoodJob
|
|
106
106
|
wait ? -1 : nil
|
107
107
|
end
|
108
108
|
|
109
|
-
|
110
|
-
_shutdown_all(executables, timeout: timeout)
|
109
|
+
_shutdown_all(_executables, timeout: timeout)
|
111
110
|
end
|
112
111
|
|
113
112
|
# Tests whether jobs have stopped executing.
|
114
113
|
# @return [Boolean] whether background threads are shut down
|
115
114
|
def self.shutdown?
|
116
|
-
|
117
|
-
Poller.instances.all?(&:shutdown?) &&
|
118
|
-
Scheduler.instances.all?(&:shutdown?)
|
115
|
+
_executables.all?(&:shutdown?)
|
119
116
|
end
|
120
117
|
|
121
118
|
# Stops and restarts executing jobs.
|
@@ -126,8 +123,7 @@ module GoodJob
|
|
126
123
|
# @param timeout [Numeric, nil] Seconds to wait for active threads to finish.
|
127
124
|
# @return [void]
|
128
125
|
def self.restart(timeout: -1)
|
129
|
-
|
130
|
-
_shutdown_all(executables, :restart, timeout: timeout)
|
126
|
+
_shutdown_all(_executables, :restart, timeout: timeout)
|
131
127
|
end
|
132
128
|
|
133
129
|
# Sends +#shutdown+ or +#restart+ to executable objects ({GoodJob::Notifier}, {GoodJob::Poller}, {GoodJob::Scheduler})
|
@@ -146,5 +142,14 @@ module GoodJob
|
|
146
142
|
end
|
147
143
|
end
|
148
144
|
|
145
|
+
def self._executables
|
146
|
+
[].concat(
|
147
|
+
CronManager.instances,
|
148
|
+
Notifier.instances,
|
149
|
+
Poller.instances,
|
150
|
+
Scheduler.instances
|
151
|
+
)
|
152
|
+
end
|
153
|
+
|
149
154
|
ActiveSupport.run_load_hooks(:good_job, self)
|
150
155
|
end
|
data/lib/good_job/adapter.rb
CHANGED
@@ -57,6 +57,8 @@ module GoodJob
|
|
57
57
|
@scheduler = GoodJob::Scheduler.from_configuration(@configuration, warm_cache_on_initialize: Rails.application.initialized?)
|
58
58
|
@notifier.recipients << [@scheduler, :create_thread]
|
59
59
|
@poller.recipients << [@scheduler, :create_thread]
|
60
|
+
|
61
|
+
@cron_manager = GoodJob::CronManager.new(@configuration.cron, start_on_initialize: Rails.application.initialized?) if @configuration.enable_cron?
|
60
62
|
end
|
61
63
|
end
|
62
64
|
|
data/lib/good_job/cli.rb
CHANGED
@@ -70,12 +70,16 @@ module GoodJob
|
|
70
70
|
type: :numeric,
|
71
71
|
banner: 'SECONDS',
|
72
72
|
desc: "Number of seconds to wait for jobs to finish when shutting down before stopping the thread. (env var: GOOD_JOB_SHUTDOWN_TIMEOUT, default: -1 (forever))"
|
73
|
+
method_option :enable_cron,
|
74
|
+
type: :boolean,
|
75
|
+
desc: "Whether to run cron process (default: false)"
|
73
76
|
method_option :daemonize,
|
74
77
|
type: :boolean,
|
75
78
|
desc: "Run as a background daemon (default: false)"
|
76
79
|
method_option :pidfile,
|
77
80
|
type: :string,
|
78
81
|
desc: "Path to write daemonized Process ID (env var: GOOD_JOB_PIDFILE, default: tmp/pids/good_job.pid)"
|
82
|
+
|
79
83
|
def start
|
80
84
|
set_up_application!
|
81
85
|
configuration = GoodJob::Configuration.new(options)
|
@@ -87,7 +91,7 @@ module GoodJob
|
|
87
91
|
scheduler = GoodJob::Scheduler.from_configuration(configuration, warm_cache_on_initialize: true)
|
88
92
|
notifier.recipients << [scheduler, :create_thread]
|
89
93
|
poller.recipients << [scheduler, :create_thread]
|
90
|
-
|
94
|
+
cron_manager = GoodJob::CronManager.new(configuration.cron, start_on_initialize: true) if configuration.enable_cron?
|
91
95
|
@stop_good_job_executable = false
|
92
96
|
%w[INT TERM].each do |signal|
|
93
97
|
trap(signal) { @stop_good_job_executable = true }
|
@@ -98,7 +102,7 @@ module GoodJob
|
|
98
102
|
break if @stop_good_job_executable || scheduler.shutdown? || notifier.shutdown?
|
99
103
|
end
|
100
104
|
|
101
|
-
executors = [notifier, poller, scheduler]
|
105
|
+
executors = [notifier, poller, cron_manager, scheduler].compact
|
102
106
|
GoodJob._shutdown_all(executors, timeout: configuration.shutdown_timeout)
|
103
107
|
end
|
104
108
|
|
@@ -124,6 +128,7 @@ module GoodJob
|
|
124
128
|
type: :numeric,
|
125
129
|
banner: 'SECONDS',
|
126
130
|
desc: "Delete records finished more than this many seconds ago (env var: GOOD_JOB_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO, default: 86400)"
|
131
|
+
|
127
132
|
def cleanup_preserved_jobs
|
128
133
|
set_up_application!
|
129
134
|
|
@@ -18,6 +18,8 @@ module GoodJob
|
|
18
18
|
DEFAULT_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO = 24 * 60 * 60
|
19
19
|
# Default to always wait for jobs to finish for {Adapter#shutdown}
|
20
20
|
DEFAULT_SHUTDOWN_TIMEOUT = -1
|
21
|
+
# Default to not running cron
|
22
|
+
DEFAULT_ENABLE_CRON = false
|
21
23
|
|
22
24
|
# The options that were explicitly set when initializing +Configuration+.
|
23
25
|
# @return [Hash]
|
@@ -129,6 +131,28 @@ module GoodJob
|
|
129
131
|
).to_f
|
130
132
|
end
|
131
133
|
|
134
|
+
# Whether to run cron
|
135
|
+
# @return [Boolean]
|
136
|
+
def enable_cron
|
137
|
+
value = ActiveModel::Type::Boolean.new.cast(
|
138
|
+
options[:enable_cron] ||
|
139
|
+
rails_config[:enable_cron] ||
|
140
|
+
env['GOOD_JOB_ENABLE_CRON'] ||
|
141
|
+
false
|
142
|
+
)
|
143
|
+
value && cron.size.positive?
|
144
|
+
end
|
145
|
+
alias enable_cron? enable_cron
|
146
|
+
|
147
|
+
def cron
|
148
|
+
env_cron = JSON.parse(ENV['GOOD_JOB_CRON']) if ENV['GOOD_JOB_CRON'].present?
|
149
|
+
|
150
|
+
options[:cron] ||
|
151
|
+
rails_config[:cron] ||
|
152
|
+
env_cron ||
|
153
|
+
{}
|
154
|
+
end
|
155
|
+
|
132
156
|
# Number of seconds to preserve jobs when using the +good_job cleanup_preserved_jobs+ CLI command.
|
133
157
|
# This configuration is only used when {GoodJob.preserve_job_records} is +true+.
|
134
158
|
# @return [Integer]
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "concurrent/hash"
|
3
|
+
require "concurrent/scheduled_task"
|
4
|
+
require "fugit"
|
5
|
+
|
6
|
+
module GoodJob # :nodoc:
|
7
|
+
#
|
8
|
+
# CronManagers enqueue jobs on a repeating schedule.
|
9
|
+
#
|
10
|
+
class CronManager
|
11
|
+
# @!attribute [r] instances
|
12
|
+
# @!scope class
|
13
|
+
# List of all instantiated CronManagers in the current process.
|
14
|
+
# @return [Array<GoodJob::CronManagers>, nil]
|
15
|
+
cattr_reader :instances, default: [], instance_reader: false
|
16
|
+
|
17
|
+
# Task observer for cron task
|
18
|
+
# @param time [Time]
|
19
|
+
# @param output [Object]
|
20
|
+
# @param thread_error [Exception]
|
21
|
+
def self.task_observer(time, output, thread_error) # rubocop:disable Lint/UnusedMethodArgument
|
22
|
+
return if thread_error.is_a? Concurrent::CancelledOperationError
|
23
|
+
|
24
|
+
GoodJob.on_thread_error.call(thread_error) if thread_error && GoodJob.on_thread_error.respond_to?(:call)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Job configuration to be scheduled
|
28
|
+
# @return [Hash]
|
29
|
+
attr_reader :schedules
|
30
|
+
|
31
|
+
# @param schedules [Hash]
|
32
|
+
# @param start_on_initialize [Boolean]
|
33
|
+
def initialize(schedules = {}, start_on_initialize: false)
|
34
|
+
@running = false
|
35
|
+
@schedules = schedules
|
36
|
+
@tasks = Concurrent::Hash.new
|
37
|
+
|
38
|
+
self.class.instances << self
|
39
|
+
|
40
|
+
start if start_on_initialize
|
41
|
+
end
|
42
|
+
|
43
|
+
# Schedule tasks that will enqueue jobs based on their schedule
|
44
|
+
def start
|
45
|
+
ActiveSupport::Notifications.instrument("cron_manager_start.good_job", cron_jobs: @schedules) do
|
46
|
+
@running = true
|
47
|
+
schedules.each_key { |cron_key| create_task(cron_key) }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Stop/cancel any scheduled tasks
|
52
|
+
# @param timeout [Numeric, nil] Unused but retained for compatibility
|
53
|
+
def shutdown(timeout: nil) # rubocop:disable Lint/UnusedMethodArgument
|
54
|
+
@running = false
|
55
|
+
@tasks.each do |_cron_key, task|
|
56
|
+
task.cancel
|
57
|
+
end
|
58
|
+
@tasks.clear
|
59
|
+
end
|
60
|
+
|
61
|
+
# Stop and restart
|
62
|
+
# @param timeout [Numeric, nil] Unused but retained for compatibility
|
63
|
+
def restart(timeout: nil) # rubocop:disable Lint/UnusedMethodArgument
|
64
|
+
shutdown
|
65
|
+
start
|
66
|
+
end
|
67
|
+
|
68
|
+
# Tests whether the manager is running.
|
69
|
+
# @return [Boolean, nil]
|
70
|
+
def running?
|
71
|
+
@running
|
72
|
+
end
|
73
|
+
|
74
|
+
# Tests whether the manager is shutdown.
|
75
|
+
# @return [Boolean, nil]
|
76
|
+
def shutdown?
|
77
|
+
!running?
|
78
|
+
end
|
79
|
+
|
80
|
+
# Enqueues a scheduled task
|
81
|
+
# @param cron_key [Symbol, String] the key within the schedule to use
|
82
|
+
def create_task(cron_key)
|
83
|
+
schedule = @schedules[cron_key]
|
84
|
+
return false if schedule.blank?
|
85
|
+
|
86
|
+
fugit = Fugit::Cron.parse(schedule.fetch(:cron))
|
87
|
+
delay = [(fugit.next_time - Time.current).to_f, 0].max
|
88
|
+
|
89
|
+
future = Concurrent::ScheduledTask.new(delay, args: [self, cron_key]) do |thr_scheduler, thr_cron_key|
|
90
|
+
# Re-schedule the next cron task before executing the current task
|
91
|
+
thr_scheduler.create_task(thr_cron_key)
|
92
|
+
|
93
|
+
CurrentExecution.reset
|
94
|
+
CurrentExecution.cron_key = thr_cron_key
|
95
|
+
|
96
|
+
Rails.application.executor.wrap do
|
97
|
+
schedule = thr_scheduler.schedules.fetch(thr_cron_key).with_indifferent_access
|
98
|
+
job_class = schedule.fetch(:class).constantize
|
99
|
+
|
100
|
+
job_set_value = schedule.fetch(:set, {})
|
101
|
+
job_set = job_set_value.respond_to?(:call) ? job_set_value.call : job_set_value
|
102
|
+
|
103
|
+
job_args_value = schedule.fetch(:args, [])
|
104
|
+
job_args = job_args_value.respond_to?(:call) ? job_args_value.call : job_args_value
|
105
|
+
|
106
|
+
job_class.set(job_set).perform_later(*job_args)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
@tasks[cron_key] = future
|
111
|
+
future.add_observer(self.class, :task_observer)
|
112
|
+
future.execute
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -11,6 +11,12 @@ module GoodJob
|
|
11
11
|
# @return [String, nil]
|
12
12
|
thread_mattr_accessor :active_job_id
|
13
13
|
|
14
|
+
# @!attribute [rw] cron_key
|
15
|
+
# @!scope class
|
16
|
+
# Cron Key
|
17
|
+
# @return [String, nil]
|
18
|
+
thread_mattr_accessor :cron_key
|
19
|
+
|
14
20
|
# @!attribute [rw] error_on_discard
|
15
21
|
# @!scope class
|
16
22
|
# Error captured by discard_on
|
data/lib/good_job/job.rb
CHANGED
@@ -199,6 +199,7 @@ module GoodJob
|
|
199
199
|
def self.enqueue(active_job, scheduled_at: nil, create_with_advisory_lock: false)
|
200
200
|
ActiveSupport::Notifications.instrument("enqueue_job.good_job", { active_job: active_job, scheduled_at: scheduled_at, create_with_advisory_lock: create_with_advisory_lock }) do |instrument_payload|
|
201
201
|
good_job_args = {
|
202
|
+
cron_key: CurrentExecution.cron_key,
|
202
203
|
queue_name: active_job.queue_name.presence || DEFAULT_QUEUE_NAME,
|
203
204
|
priority: active_job.priority || DEFAULT_PRIORITY,
|
204
205
|
serialized_params: active_job.serialize,
|
@@ -285,6 +286,25 @@ module GoodJob
|
|
285
286
|
super || serialized_params['job_id']
|
286
287
|
end
|
287
288
|
|
289
|
+
def cron_key
|
290
|
+
if self.class.column_names.include?('cron_key')
|
291
|
+
super
|
292
|
+
else
|
293
|
+
ActiveSupport::Deprecation.warn(<<~DEPRECATION)
|
294
|
+
GoodJob has pending database migrations. To create the migration files, run:
|
295
|
+
|
296
|
+
rails generate good_job:update
|
297
|
+
|
298
|
+
To apply the migration files, run:
|
299
|
+
|
300
|
+
rails db:migrate
|
301
|
+
|
302
|
+
DEPRECATION
|
303
|
+
|
304
|
+
nil
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
288
308
|
private
|
289
309
|
|
290
310
|
# @return [ExecutionResult]
|
@@ -295,6 +315,7 @@ module GoodJob
|
|
295
315
|
|
296
316
|
GoodJob::CurrentExecution.reset
|
297
317
|
GoodJob::CurrentExecution.active_job_id = active_job_id
|
318
|
+
GoodJob::CurrentExecution.cron_key = cron_key
|
298
319
|
ActiveSupport::Notifications.instrument("perform_job.good_job", { good_job: self, process_id: GoodJob::CurrentExecution.process_id, thread_name: GoodJob::CurrentExecution.thread_name }) do
|
299
320
|
value = ActiveJob::Base.execute(params)
|
300
321
|
|
@@ -57,6 +57,16 @@ module GoodJob
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
+
# @!macro notification_responder
|
61
|
+
def cron_manager_start(event)
|
62
|
+
cron_jobs = event.payload[:cron_jobs]
|
63
|
+
cron_jobs_count = cron_jobs.size
|
64
|
+
|
65
|
+
info do
|
66
|
+
"GoodJob started cron with #{cron_jobs_count} #{'jobs'.pluralize(cron_jobs_count)}."
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
60
70
|
# @!macro notification_responder
|
61
71
|
def scheduler_shutdown_start(event)
|
62
72
|
process_id = event.payload[:process_id]
|
data/lib/good_job/railtie.rb
CHANGED
@@ -3,6 +3,7 @@ module GoodJob
|
|
3
3
|
# Ruby on Rails integration.
|
4
4
|
class Railtie < ::Rails::Railtie
|
5
5
|
config.good_job = ActiveSupport::OrderedOptions.new
|
6
|
+
config.good_job.cron = {}
|
6
7
|
|
7
8
|
initializer "good_job.logger" do |_app|
|
8
9
|
ActiveSupport.on_load(:good_job) do
|
@@ -23,6 +24,7 @@ module GoodJob
|
|
23
24
|
|
24
25
|
config.after_initialize do
|
25
26
|
GoodJob::Scheduler.instances.each(&:warm_cache)
|
27
|
+
GoodJob::CronManager.instances.each(&:start)
|
26
28
|
end
|
27
29
|
end
|
28
30
|
end
|
data/lib/good_job/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: good_job
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.12.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Sheldon
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-07-
|
11
|
+
date: 2021-07-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: 1.0.2
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: fugit
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.1'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.1'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: railties
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -345,12 +359,12 @@ files:
|
|
345
359
|
- engine/app/helpers/good_job/application_helper.rb
|
346
360
|
- engine/app/views/good_job/active_jobs/show.html.erb
|
347
361
|
- engine/app/views/good_job/dashboards/index.html.erb
|
362
|
+
- engine/app/views/good_job/shared/_chart.erb
|
363
|
+
- engine/app/views/good_job/shared/_jobs_table.erb
|
364
|
+
- engine/app/views/good_job/shared/icons/_check.html.erb
|
365
|
+
- engine/app/views/good_job/shared/icons/_exclamation.html.erb
|
366
|
+
- engine/app/views/good_job/shared/icons/_trash.html.erb
|
348
367
|
- engine/app/views/layouts/good_job/base.html.erb
|
349
|
-
- engine/app/views/shared/_chart.erb
|
350
|
-
- engine/app/views/shared/_jobs_table.erb
|
351
|
-
- engine/app/views/shared/icons/_check.html.erb
|
352
|
-
- engine/app/views/shared/icons/_exclamation.html.erb
|
353
|
-
- engine/app/views/shared/icons/_trash.html.erb
|
354
368
|
- engine/config/routes.rb
|
355
369
|
- engine/lib/good_job/engine.rb
|
356
370
|
- exe/good_job
|
@@ -367,6 +381,7 @@ files:
|
|
367
381
|
- lib/good_job/adapter.rb
|
368
382
|
- lib/good_job/cli.rb
|
369
383
|
- lib/good_job/configuration.rb
|
384
|
+
- lib/good_job/cron_manager.rb
|
370
385
|
- lib/good_job/current_execution.rb
|
371
386
|
- lib/good_job/daemon.rb
|
372
387
|
- lib/good_job/execution_result.rb
|