good_job 1.7.1 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 86268c6767ba44783707ed768fe726b7a53592e46468ed49bd38d427ff4919af
4
- data.tar.gz: 5fa72a4aaec59a31ba437eac31f9a6785cdb548c6694bea332434baf58aaad98
3
+ metadata.gz: ff879dc7996d0f02055d1b61cff803a6ba24a5e0ec5441b8c81e3b7b1730fe63
4
+ data.tar.gz: 988e726ed6b8ba20d202bc86d7a21645ac8d08d2cf32b86aad258cfacf92e7db
5
5
  SHA512:
6
- metadata.gz: c63baa745a861101b1a65c7a2dd7f4ec603a50714d5952a7ad942f30b41543643e6b7e1f19b1373f280668f212c6e173c4aa90d7d3a0770a92c1e159ae30215b
7
- data.tar.gz: 7bf564c4cfc0804f1a32995214195736e9cbd8d8f7a5b45d3ffe7efecf3d43126d412594855d50c9637c646e82838112a4869a4732fa0fe5ba1542b558facd0f
6
+ metadata.gz: f268aa3249af67cad76da907afaa698726ab8b0ead6f004af1f13cd5fcf6e93b5f39b34540b3207252eaaac107f5f5d59bf0277ad76417a4ad7bd08ee811aaaf
7
+ data.tar.gz: 464a66b3dee0bfd771a5b28527cff4dffd0e704a9a301effc40ebec8ebaf1702b8180c0b97b2d78939913b1439f22aa25f9c434f0ae9ae5c88b136d07b4dd094
data/CHANGELOG.md CHANGED
@@ -1,23 +1,48 @@
1
1
  # Changelog
2
2
 
3
+ ## [v1.8.0](https://github.com/bensheldon/good_job/tree/v1.8.0) (2021-03-03)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.7.1...v1.8.0)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Wait then stop on shutdown [\#126](https://github.com/bensheldon/good_job/issues/126)
10
+ - Add shutdown-timeout option to configure the wait for jobs to gracefully finish before stopping them [\#213](https://github.com/bensheldon/good_job/pull/213) ([bensheldon](https://github.com/bensheldon))
11
+
12
+ **Fixed bugs:**
13
+
14
+ - Ensure Job\#serialized\_params are immutable [\#218](https://github.com/bensheldon/good_job/pull/218) ([bensheldon](https://github.com/bensheldon))
15
+
16
+ **Closed issues:**
17
+
18
+ - Run GoodJob on puma boot [\#91](https://github.com/bensheldon/good_job/issues/91)
19
+ - ActiveRecord::ConnectionNotEstablished when using async mode [\#89](https://github.com/bensheldon/good_job/issues/89)
20
+
21
+ **Merged pull requests:**
22
+
23
+ - Update bundler and Appraisals so Rails HEAD is locked to Ruby version \>= 2.7 [\#219](https://github.com/bensheldon/good_job/pull/219) ([bensheldon](https://github.com/bensheldon))
24
+
3
25
  ## [v1.7.1](https://github.com/bensheldon/good_job/tree/v1.7.1) (2021-01-27)
4
26
 
5
27
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.7.0...v1.7.1)
6
28
 
29
+ **Fixed bugs:**
30
+
31
+ - Scheduler should always push a new task on completion of previous task, regardless of available thread calculation [\#209](https://github.com/bensheldon/good_job/pull/209) ([bensheldon](https://github.com/bensheldon))
32
+
7
33
  **Closed issues:**
8
34
 
9
35
  - Unexpected behavior with max\_threads = 1 [\#208](https://github.com/bensheldon/good_job/issues/208)
10
36
 
11
37
  **Merged pull requests:**
12
38
 
13
- - Scheduler should always push a new task on completion of previous task, regardless of available thread calculation [\#209](https://github.com/bensheldon/good_job/pull/209) ([bensheldon](https://github.com/bensheldon))
14
39
  - Fix equality typo in development.rb of test\_app [\#207](https://github.com/bensheldon/good_job/pull/207) ([reczy](https://github.com/reczy))
15
40
 
16
41
  ## [v1.7.0](https://github.com/bensheldon/good_job/tree/v1.7.0) (2021-01-25)
17
42
 
18
43
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.6.0...v1.7.0)
19
44
 
20
- **Merged pull requests:**
45
+ **Implemented enhancements:**
21
46
 
22
47
  - Cache scheduled jobs in memory so they can be executed without polling [\#205](https://github.com/bensheldon/good_job/pull/205) ([bensheldon](https://github.com/bensheldon))
23
48
 
@@ -47,6 +72,7 @@
47
72
  **Implemented enhancements:**
48
73
 
49
74
  - Create Web UI Dashboard [\#50](https://github.com/bensheldon/good_job/issues/50)
75
+ - Configure GoodJob via `Rails.application.config` instead of recommending `GoodJob::Adapter.new` [\#199](https://github.com/bensheldon/good_job/pull/199) ([bensheldon](https://github.com/bensheldon))
50
76
 
51
77
  **Closed issues:**
52
78
 
@@ -55,7 +81,6 @@
55
81
  **Merged pull requests:**
56
82
 
57
83
  - Update bundler version to 2.2.5 [\#200](https://github.com/bensheldon/good_job/pull/200) ([bensheldon](https://github.com/bensheldon))
58
- - Configure GoodJob via `Rails.application.config` instead of recommending `GoodJob::Adapter.new` [\#199](https://github.com/bensheldon/good_job/pull/199) ([bensheldon](https://github.com/bensheldon))
59
84
  - Update GH Test Matrix with minimum & latest JRuby version [\#197](https://github.com/bensheldon/good_job/pull/197) ([tedhexaflow](https://github.com/tedhexaflow))
60
85
  - Fix JRuby version number [\#193](https://github.com/bensheldon/good_job/pull/193) ([tedhexaflow](https://github.com/tedhexaflow))
61
86
 
@@ -74,6 +99,7 @@
74
99
  **Merged pull requests:**
75
100
 
76
101
  - Add missing YARD docs and Dashboard screenshot [\#191](https://github.com/bensheldon/good_job/pull/191) ([bensheldon](https://github.com/bensheldon))
102
+ - Update all Lockable queries to use exec\_query instead of execute; clear async\_exec results [\#189](https://github.com/bensheldon/good_job/pull/189) ([bensheldon](https://github.com/bensheldon))
77
103
 
78
104
  ## [v1.4.0](https://github.com/bensheldon/good_job/tree/v1.4.0) (2020-12-31)
79
105
 
@@ -100,7 +126,6 @@
100
126
  **Merged pull requests:**
101
127
 
102
128
  - Run tests with Rails default configuration to enable Zeitwerk [\#190](https://github.com/bensheldon/good_job/pull/190) ([bensheldon](https://github.com/bensheldon))
103
- - Update all Lockable queries to use exec\_query instead of execute; clear async\_exec results [\#189](https://github.com/bensheldon/good_job/pull/189) ([bensheldon](https://github.com/bensheldon))
104
129
  - Have Lockable\#advisory\_locked? directly query pg\_locks table [\#188](https://github.com/bensheldon/good_job/pull/188) ([bensheldon](https://github.com/bensheldon))
105
130
  - Update development gems, including Rails v6.1 and Rails HEAD [\#186](https://github.com/bensheldon/good_job/pull/186) ([bensheldon](https://github.com/bensheldon))
106
131
  - Update Appraisals for Rails 6.1 [\#183](https://github.com/bensheldon/good_job/pull/183) ([bensheldon](https://github.com/bensheldon))
@@ -114,7 +139,6 @@
114
139
 
115
140
  - Ensure advisory lock CTE is MATERIALIZED on Postgres v12+ [\#179](https://github.com/bensheldon/good_job/pull/179) ([bensheldon](https://github.com/bensheldon))
116
141
  - Ensure that deleted jobs are unlocked [\#178](https://github.com/bensheldon/good_job/pull/178) ([bensheldon](https://github.com/bensheldon))
117
- - Fix job ordering for Rails 6.1 [\#174](https://github.com/bensheldon/good_job/pull/174) ([morgoth](https://github.com/morgoth))
118
142
 
119
143
  **Closed issues:**
120
144
 
@@ -129,6 +153,10 @@
129
153
 
130
154
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.3...v1.3.4)
131
155
 
156
+ **Fixed bugs:**
157
+
158
+ - Fix job ordering for Rails 6.1 [\#174](https://github.com/bensheldon/good_job/pull/174) ([morgoth](https://github.com/morgoth))
159
+
132
160
  ## [v1.3.3](https://github.com/bensheldon/good_job/tree/v1.3.3) (2020-12-01)
133
161
 
134
162
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.2...v1.3.3)
@@ -268,7 +296,6 @@
268
296
  - Have YARD render markdown files with GFM \(Github Flavored Markdown\) [\#113](https://github.com/bensheldon/good_job/pull/113) ([bensheldon](https://github.com/bensheldon))
269
297
  - Add markdownlint to lint readme [\#109](https://github.com/bensheldon/good_job/pull/109) ([bensheldon](https://github.com/bensheldon))
270
298
  - Remove unused method in PgLocks [\#107](https://github.com/bensheldon/good_job/pull/107) ([gadimbaylisahil](https://github.com/gadimbaylisahil))
271
- - Re-organize Readme: frontload configuration, add Table of Contents [\#106](https://github.com/bensheldon/good_job/pull/106) ([bensheldon](https://github.com/bensheldon))
272
299
 
273
300
  ## [v1.2.3](https://github.com/bensheldon/good_job/tree/v1.2.3) (2020-08-27)
274
301
 
@@ -303,6 +330,7 @@
303
330
 
304
331
  **Merged pull requests:**
305
332
 
333
+ - Re-organize Readme: frontload configuration, add Table of Contents [\#106](https://github.com/bensheldon/good_job/pull/106) ([bensheldon](https://github.com/bensheldon))
306
334
  - Use more ActiveRecord in Lockable and not connection.execute [\#102](https://github.com/bensheldon/good_job/pull/102) ([bensheldon](https://github.com/bensheldon))
307
335
  - Run CI tests on Ruby 2.5, 2.6, and 2.7 [\#101](https://github.com/bensheldon/good_job/pull/101) ([arku](https://github.com/arku))
308
336
  - Fix Ruby 2.7 keyword arguments warning [\#98](https://github.com/bensheldon/good_job/pull/98) ([arku](https://github.com/arku))
@@ -435,6 +463,7 @@
435
463
 
436
464
  - Add migration generator [\#56](https://github.com/bensheldon/good_job/pull/56) ([thedanbob](https://github.com/thedanbob))
437
465
  - Fix migration script in readme [\#55](https://github.com/bensheldon/good_job/pull/55) ([thedanbob](https://github.com/thedanbob))
466
+ - Move where\(scheduled\_at: Time.current\) into dynamic part of GoodJob::Job::Performer [\#42](https://github.com/bensheldon/good_job/pull/42) ([bensheldon](https://github.com/bensheldon))
438
467
 
439
468
  ## [v1.0.1](https://github.com/bensheldon/good_job/tree/v1.0.1) (2020-07-22)
440
469
 
@@ -473,10 +502,6 @@
473
502
 
474
503
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v0.8.0...v0.8.1)
475
504
 
476
- **Merged pull requests:**
477
-
478
- - Move where\(scheduled\_at: Time.current\) into dynamic part of GoodJob::Job::Performer [\#42](https://github.com/bensheldon/good_job/pull/42) ([bensheldon](https://github.com/bensheldon))
479
-
480
505
  ## [v0.8.0](https://github.com/bensheldon/good_job/tree/v0.8.0) (2020-07-17)
481
506
 
482
507
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v0.7.0...v0.8.0)
data/README.md CHANGED
@@ -149,12 +149,13 @@ Usage:
149
149
  good_job start
150
150
 
151
151
  Options:
152
- [--max-threads=COUNT] # Maximum number of threads to use for working jobs. (env var: GOOD_JOB_MAX_THREADS, default: 5)
153
- [--queues=QUEUE_LIST] # Queues to work from. (env var: GOOD_JOB_QUEUES, default: *)
154
- [--poll-interval=SECONDS] # Interval between polls for available jobs in seconds (env var: GOOD_JOB_POLL_INTERVAL, default: 1)
155
- [--max-cache=COUNT] # Maximum number of scheduled jobs to cache in memory (env var: GOOD_JOB_MAX_CACHE, default: 10000)
156
- [--daemonize] # Run as a background daemon (default: false)
157
- [--pidfile=PIDFILE] # Path to write daemonized Process ID (env var: GOOD_JOB_PIDFILE, default: tmp/pids/good_job.pid)
152
+ [--max-threads=COUNT] # Maximum number of threads to use for working jobs. (env var: GOOD_JOB_MAX_THREADS, default: 5)
153
+ [--queues=QUEUE_LIST] # Queues to work from. (env var: GOOD_JOB_QUEUES, default: *)
154
+ [--poll-interval=SECONDS] # Interval between polls for available jobs in seconds (env var: GOOD_JOB_POLL_INTERVAL, default: 1)
155
+ [--max-cache=COUNT] # Maximum number of scheduled jobs to cache in memory (env var: GOOD_JOB_MAX_CACHE, default: 10000)
156
+ [--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))
157
+ [--daemonize] # Run as a background daemon (default: false)
158
+ [--pidfile=PIDFILE] # Path to write daemonized Process ID (env var: GOOD_JOB_PIDFILE, default: tmp/pids/good_job.pid)
158
159
 
159
160
  Executes queued jobs.
160
161
 
@@ -208,12 +209,15 @@ config.active_job.queue_adapter = :good_job
208
209
  config.good_job.execution_mode = :async
209
210
  config.good_job.max_threads = 5
210
211
  config.good_job.poll_interval = 30 # seconds
212
+ config.good_job.shutdown_timeout = 25 # seconds
213
+
211
214
 
212
215
  # ...or all at once.
213
216
  config.good_job = {
214
217
  execution_mode: :async,
215
218
  max_threads: 5,
216
219
  poll_interval: 30,
220
+ shutdown_timeout: 25,
217
221
  }
218
222
  ```
219
223
 
@@ -227,6 +231,7 @@ Available configuration options are:
227
231
  - `queues` (string) determines which queues to execute jobs from when `execution_mode` is set to `:async`. See the description of `good_job start` for more details on the format of this string. You can also set this with the environment variable `GOOD_JOB_QUEUES`.
228
232
  - `poll_interval` (integer) sets the number of seconds between polls for jobs when `execution_mode` is set to `:async`. You can also set this with the environment variable `GOOD_JOB_POLL_INTERVAL`.
229
233
  - `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`.
234
+ - `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`.
230
235
 
231
236
  By default, GoodJob configures the following execution modes per environment:
232
237
 
data/lib/good_job.rb CHANGED
@@ -78,15 +78,26 @@ module GoodJob
78
78
  # See the {file:README.md#executing-jobs-async--in-process} for more explanation and examples.
79
79
  # @param wait [Boolean] whether to wait for shutdown
80
80
  # @return [void]
81
- def self.shutdown(wait: true)
82
- Notifier.instances.each { |notifier| notifier.shutdown(wait: wait) }
83
- Scheduler.instances.each { |scheduler| scheduler.shutdown(wait: wait) }
81
+ def self.shutdown(timeout: -1, wait: nil)
82
+ timeout = if wait.present?
83
+ ActiveSupport::Deprecation.warn(
84
+ "Using `GoodJob.shutdown` with `wait:` kwarg is deprecated; use `timeout:` kwarg instead e.g. GoodJob.shutdown(timeout: #{wait ? '-1' : 'nil'})"
85
+ )
86
+ wait ? -1 : nil
87
+ else
88
+ timeout
89
+ end
90
+
91
+ executables = Array(Notifier.instances) + Array(Poller.instances) + Array(Scheduler.instances)
92
+ _shutdown_all(executables, timeout: timeout)
84
93
  end
85
94
 
86
95
  # Tests whether jobs have stopped executing.
87
96
  # @return [Boolean] whether background threads are shut down
88
97
  def self.shutdown?
89
- Notifier.instances.all?(&:shutdown?) && Scheduler.instances.all?(&:shutdown?)
98
+ Notifier.instances.all?(&:shutdown?) &&
99
+ Poller.instances.all?(&:shutdown?) &&
100
+ Scheduler.instances.all?(&:shutdown?)
90
101
  end
91
102
 
92
103
  # Stops and restarts executing jobs.
@@ -95,9 +106,25 @@ module GoodJob
95
106
  # For example, you should use +shutdown+ and +restart+ when using async execution mode with Puma.
96
107
  # See the {file:README.md#executing-jobs-async--in-process} for more explanation and examples.
97
108
  # @return [void]
98
- def self.restart
99
- Notifier.instances.each(&:restart)
100
- Scheduler.instances.each(&:restart)
109
+ def self.restart(timeout: -1)
110
+ executables = Array(Notifier.instances) + Array(Poller.instances) + Array(Scheduler.instances)
111
+ _shutdown_all(executables, :restart, timeout: timeout)
112
+ end
113
+
114
+ # Sends +#shutdown+ or +#restart+ to executable objects ({GoodJob::Notifier}, {GoodJob::Poller}, {GoodJob::Scheduler})
115
+ # @param executables [Array<(Notifier, Poller, Scheduler)>] Objects to shut down.
116
+ # @param method_name [:symbol] Method to call, e.g. +:shutdown+ or +:restart+.
117
+ # @param timeout [nil,Numeric]
118
+ # @return [void]
119
+ def self._shutdown_all(executables, method_name = :shutdown, timeout: -1)
120
+ if timeout.positive?
121
+ executables.each { |executable| executable.send(method_name, timeout: nil) }
122
+
123
+ stop_at = Time.current + timeout
124
+ executables.each { |executable| executable.send(method_name, timeout: [stop_at - Time.current, 0].max) }
125
+ else
126
+ executables.each { |executable| executable.send(method_name, timeout: timeout) }
127
+ end
101
128
  end
102
129
 
103
130
  ActiveSupport.run_load_hooks(:good_job, self)
@@ -38,7 +38,7 @@ module GoodJob
38
38
  DEPRECATION
39
39
  end
40
40
 
41
- configuration = GoodJob::Configuration.new(
41
+ @configuration = GoodJob::Configuration.new(
42
42
  {
43
43
  execution_mode: execution_mode,
44
44
  queues: queues,
@@ -47,13 +47,12 @@ module GoodJob
47
47
  }
48
48
  )
49
49
 
50
- @execution_mode = configuration.execution_mode
51
- raise ArgumentError, "execution_mode: must be one of #{EXECUTION_MODES.join(', ')}." unless EXECUTION_MODES.include?(@execution_mode)
50
+ raise ArgumentError, "execution_mode: must be one of #{EXECUTION_MODES.join(', ')}." unless EXECUTION_MODES.include?(@configuration.execution_mode)
52
51
 
53
52
  if execute_async? # rubocop:disable Style/GuardClause
54
53
  @notifier = GoodJob::Notifier.new
55
- @poller = GoodJob::Poller.new(poll_interval: configuration.poll_interval)
56
- @scheduler = GoodJob::Scheduler.from_configuration(configuration, warm_cache_on_initialize: Rails.application.initialized?)
54
+ @poller = GoodJob::Poller.new(poll_interval: @configuration.poll_interval)
55
+ @scheduler = GoodJob::Scheduler.from_configuration(@configuration, warm_cache_on_initialize: Rails.application.initialized?)
57
56
  @notifier.recipients << [@scheduler, :create_thread]
58
57
  @poller.recipients << [@scheduler, :create_thread]
59
58
  end
@@ -96,29 +95,48 @@ module GoodJob
96
95
  good_job
97
96
  end
98
97
 
99
- # Gracefully stop processing jobs.
100
- # Waits for termination by default.
101
- # @param wait [Boolean] Whether to wait for shut down.
98
+ # Shut down the thread pool executors.
99
+ # @param timeout [nil, Numeric] Seconds to wait for active threads.
100
+ #
101
+ # * +nil+, the scheduler will trigger a shutdown but not wait for it to complete.
102
+ # * +-1+, the scheduler will wait until the shutdown is complete.
103
+ # * +0+, the scheduler will immediately shutdown and stop any threads.
104
+ # * A positive number will wait that many seconds before stopping any remaining active threads.
105
+ # @param wait [Boolean] Deprecated. Use +timeout:+ instead.
102
106
  # @return [void]
103
- def shutdown(wait: true)
104
- @notifier&.shutdown(wait: wait)
105
- @poller&.shutdown(wait: wait)
106
- @scheduler&.shutdown(wait: wait)
107
+ def shutdown(timeout: :default, wait: nil)
108
+ timeout = if wait.present?
109
+ ActiveSupport::Deprecation.warn(
110
+ "Using `GoodJob::Adapter.shutdown` with `wait:` kwarg is deprecated; use `timeout:` kwarg instead e.g. GoodJob::Adapter.shutdown(timeout: #{wait ? '-1' : 'nil'})"
111
+ )
112
+ wait ? -1 : nil
113
+ else
114
+ timeout
115
+ end
116
+
117
+ timeout = if timeout == :default
118
+ @configuration.shutdown_timeout
119
+ else
120
+ timeout
121
+ end
122
+
123
+ executables = [@notifier, @poller, @scheduler].compact
124
+ GoodJob._shutdown_all(executables, timeout: timeout)
107
125
  end
108
126
 
109
127
  # Whether in +:async+ execution mode.
110
128
  def execute_async?
111
- @execution_mode == :async
129
+ @configuration.execution_mode == :async
112
130
  end
113
131
 
114
132
  # Whether in +:external+ execution mode.
115
133
  def execute_externally?
116
- @execution_mode == :external
134
+ @configuration.execution_mode == :external
117
135
  end
118
136
 
119
137
  # Whether in +:inline+ execution mode.
120
138
  def execute_inline?
121
- @execution_mode == :inline
139
+ @configuration.execution_mode == :inline
122
140
  end
123
141
  end
124
142
  end
data/lib/good_job/cli.rb CHANGED
@@ -53,6 +53,10 @@ module GoodJob
53
53
  type: :numeric,
54
54
  banner: 'COUNT',
55
55
  desc: "Maximum number of scheduled jobs to cache in memory (env var: GOOD_JOB_MAX_CACHE, default: 10000)"
56
+ method_option :shutdown_timeout,
57
+ type: :numeric,
58
+ banner: 'SECONDS',
59
+ 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))"
56
60
  method_option :daemonize,
57
61
  type: :boolean,
58
62
  desc: "Run as a background daemon (default: false)"
@@ -81,9 +85,8 @@ module GoodJob
81
85
  break if @stop_good_job_executable || scheduler.shutdown? || notifier.shutdown?
82
86
  end
83
87
 
84
- notifier.shutdown
85
- poller.shutdown
86
- scheduler.shutdown
88
+ executors = [notifier, poller, scheduler]
89
+ GoodJob._shutdown_all(executors, timeout: configuration.shutdown_timeout)
87
90
  end
88
91
 
89
92
  default_task :start
@@ -13,6 +13,8 @@ module GoodJob
13
13
  DEFAULT_MAX_CACHE = 10000
14
14
  # Default number of seconds to preserve jobs for {CLI#cleanup_preserved_jobs}
15
15
  DEFAULT_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO = 24 * 60 * 60
16
+ # Default to always wait for jobs to finish for {#shutdown}
17
+ DEFAULT_SHUTDOWN_TIMEOUT = -1
16
18
 
17
19
  # The options that were explicitly set when initializing +Configuration+.
18
20
  # @return [Hash]
@@ -77,10 +79,10 @@ module GoodJob
77
79
  def max_threads
78
80
  (
79
81
  options[:max_threads] ||
80
- rails_config[:max_threads] ||
81
- env['GOOD_JOB_MAX_THREADS'] ||
82
- env['RAILS_MAX_THREADS'] ||
83
- DEFAULT_MAX_THREADS
82
+ rails_config[:max_threads] ||
83
+ env['GOOD_JOB_MAX_THREADS'] ||
84
+ env['RAILS_MAX_THREADS'] ||
85
+ DEFAULT_MAX_THREADS
84
86
  ).to_i
85
87
  end
86
88
 
@@ -103,9 +105,9 @@ module GoodJob
103
105
  def poll_interval
104
106
  (
105
107
  options[:poll_interval] ||
106
- rails_config[:poll_interval] ||
107
- env['GOOD_JOB_POLL_INTERVAL'] ||
108
- DEFAULT_POLL_INTERVAL
108
+ rails_config[:poll_interval] ||
109
+ env['GOOD_JOB_POLL_INTERVAL'] ||
110
+ DEFAULT_POLL_INTERVAL
109
111
  ).to_i
110
112
  end
111
113
 
@@ -122,15 +124,27 @@ module GoodJob
122
124
  ).to_i
123
125
  end
124
126
 
127
+ # The number of seconds to wait for jobs to finish when shutting down
128
+ # before stopping the thread. +-1+ is forever.
129
+ # @return [Numeric]
130
+ def shutdown_timeout
131
+ (
132
+ options[:shutdown_timeout] ||
133
+ rails_config[:shutdown_timeout] ||
134
+ env['GOOD_JOB_SHUTDOWN_TIMEOUT'] ||
135
+ DEFAULT_SHUTDOWN_TIMEOUT
136
+ ).to_f
137
+ end
138
+
125
139
  # Number of seconds to preserve jobs when using the +good_job cleanup_preserved_jobs+ CLI command.
126
140
  # This configuration is only used when {GoodJob.preserve_job_records} is +true+.
127
141
  # @return [Integer]
128
142
  def cleanup_preserved_jobs_before_seconds_ago
129
143
  (
130
144
  options[:before_seconds_ago] ||
131
- rails_config[:cleanup_preserved_jobs_before_seconds_ago] ||
132
- env['GOOD_JOB_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO'] ||
133
- DEFAULT_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO
145
+ rails_config[:cleanup_preserved_jobs_before_seconds_ago] ||
146
+ env['GOOD_JOB_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO'] ||
147
+ DEFAULT_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO
134
148
  ).to_i
135
149
  end
136
150
 
data/lib/good_job/job.rb CHANGED
@@ -15,6 +15,8 @@ module GoodJob
15
15
 
16
16
  self.table_name = 'good_jobs'.freeze
17
17
 
18
+ attr_readonly :serialized_params
19
+
18
20
  # Parse a string representing a group of queues into a more readable data
19
21
  # structure.
20
22
  # @return [Hash]
@@ -51,8 +51,9 @@ module GoodJob
51
51
  end
52
52
 
53
53
  # The Returns timestamps of when next tasks may be available.
54
- # @param count [Integer] number of timestamps to return
55
- # @param count [DateTime, Time, nil] jobs scheduled after this time
54
+ # @param after [DateTime, Time, nil] future jobs scheduled after this time
55
+ # @param limit [Integer] number of future timestamps to return
56
+ # @param now_limit [Integer] number of past timestamps to return
56
57
  # @return [Array<(Time, Timestamp)>, nil]
57
58
  def next_at(after: nil, limit: nil, now_limit: nil)
58
59
  job_query.next_scheduled_at(after: after, limit: limit, now_limit: now_limit)
@@ -1,16 +1,16 @@
1
1
  module GoodJob
2
2
  # Delegates the interface of a single {Scheduler} to multiple Schedulers.
3
3
  class MultiScheduler
4
- # @return [array<Scheduler>] List of the scheduler delegates
4
+ # @return [Array<Scheduler>] List of the scheduler delegates
5
5
  attr_reader :schedulers
6
6
 
7
7
  def initialize(schedulers)
8
8
  @schedulers = schedulers
9
9
  end
10
10
 
11
- # Delegates to {Scheduler#shutdown}.
12
- def shutdown(wait: true)
13
- schedulers.each { |s| s.shutdown(wait: wait) }
11
+ # Delegates to {Scheduler#running?}.
12
+ def running?
13
+ schedulers.all?(&:running?)
14
14
  end
15
15
 
16
16
  # Delegates to {Scheduler#shutdown?}.
@@ -18,9 +18,14 @@ module GoodJob
18
18
  schedulers.all?(&:shutdown?)
19
19
  end
20
20
 
21
+ # Delegates to {Scheduler#shutdown}.
22
+ def shutdown(timeout: -1)
23
+ GoodJob._shutdown_all(schedulers, timeout: timeout)
24
+ end
25
+
21
26
  # Delegates to {Scheduler#restart}.
22
- def restart(wait: true)
23
- schedulers.each { |s| s.restart(wait: wait) }
27
+ def restart(timeout: -1)
28
+ GoodJob._shutdown_all(schedulers, :restart, timeout: timeout)
24
29
  end
25
30
 
26
31
  # Delegates to {Scheduler#create_thread}.
@@ -15,7 +15,7 @@ module GoodJob # :nodoc:
15
15
  # Default Postgres channel for LISTEN/NOTIFY
16
16
  CHANNEL = 'good_job'.freeze
17
17
  # Defaults for instance of Concurrent::ThreadPoolExecutor
18
- POOL_OPTIONS = {
18
+ EXECUTOR_OPTIONS = {
19
19
  name: name,
20
20
  min_threads: 0,
21
21
  max_threads: 1,
@@ -30,7 +30,7 @@ module GoodJob # :nodoc:
30
30
  # @!attribute [r] instances
31
31
  # @!scope class
32
32
  # List of all instantiated Notifiers in the current process.
33
- # @return [array<GoodJob:Adapter>]
33
+ # @return [Array<GoodJob:Adapter>]
34
34
  cattr_reader :instances, default: [], instance_reader: false
35
35
 
36
36
  # Send a message via Postgres NOTIFY
@@ -53,7 +53,7 @@ module GoodJob # :nodoc:
53
53
 
54
54
  self.class.instances << self
55
55
 
56
- create_pool
56
+ create_executor
57
57
  listen
58
58
  end
59
59
 
@@ -63,34 +63,43 @@ module GoodJob # :nodoc:
63
63
  @listening.true?
64
64
  end
65
65
 
66
- # Restart the notifier.
67
- # When shutdown, start; or shutdown and start.
68
- # @param wait [Boolean] Wait for background thread to finish
69
- # @return [void]
70
- def restart(wait: true)
71
- shutdown(wait: wait)
72
- create_pool
73
- listen
74
- end
66
+ # Tests whether the notifier is running.
67
+ # @return [true, false, nil]
68
+ delegate :running?, to: :executor, allow_nil: true
69
+
70
+ # Tests whether the scheduler is shutdown.
71
+ # @return [true, false, nil]
72
+ delegate :shutdown?, to: :executor, allow_nil: true
75
73
 
76
74
  # Shut down the notifier.
77
75
  # This stops the background LISTENing thread.
78
- # If +wait+ is +true+, the notifier will wait for background thread to shutdown.
79
- # If +wait+ is +false+, this method will return immediately even though threads may still be running.
80
76
  # Use {#shutdown?} to determine whether threads have stopped.
81
- # @param wait [Boolean] Wait for actively executing threads to finish
77
+ # @param timeout [nil, Numeric] Seconds to wait for active threads.
78
+ #
79
+ # * +nil+, the scheduler will trigger a shutdown but not wait for it to complete.
80
+ # * +-1+, the scheduler will wait until the shutdown is complete.
81
+ # * +0+, the scheduler will immediately shutdown and stop any threads.
82
+ # * A positive number will wait that many seconds before stopping any remaining active threads.
82
83
  # @return [void]
83
- def shutdown(wait: true)
84
- return unless @pool.running?
84
+ def shutdown(timeout: -1)
85
+ return if executor.nil? || executor.shutdown?
85
86
 
86
- @pool.shutdown
87
- @pool.wait_for_termination if wait
87
+ executor.shutdown if executor.running?
88
+
89
+ if executor.shuttingdown? && timeout # rubocop:disable Style/GuardClause
90
+ executor_wait = timeout.negative? ? nil : timeout
91
+ executor.kill unless executor.wait_for_termination(executor_wait)
92
+ end
88
93
  end
89
94
 
90
- # Tests whether the notifier is shutdown.
91
- # @return [true, false, nil]
92
- def shutdown?
93
- !@pool.running?
95
+ # Restart the notifier.
96
+ # When shutdown, start; or shutdown and start.
97
+ # @param timeout [nil, Numeric] Seconds to wait; shares same values as {#shutdown}.
98
+ # @return [void]
99
+ def restart(timeout: -1)
100
+ shutdown(timeout: timeout) if running?
101
+ create_executor
102
+ listen
94
103
  end
95
104
 
96
105
  # Invoked on completion of ThreadPoolExecutor task
@@ -109,36 +118,36 @@ module GoodJob # :nodoc:
109
118
 
110
119
  private
111
120
 
112
- def create_pool
113
- @pool = Concurrent::ThreadPoolExecutor.new(POOL_OPTIONS)
121
+ attr_reader :executor
122
+
123
+ def create_executor
124
+ @executor = Concurrent::ThreadPoolExecutor.new(EXECUTOR_OPTIONS)
114
125
  end
115
126
 
116
127
  def listen
117
- future = Concurrent::Future.new(args: [@recipients, @pool, @listening], executor: @pool) do |recipients, pool, listening|
128
+ future = Concurrent::Future.new(args: [@recipients, executor, @listening], executor: @executor) do |thr_recipients, thr_executor, thr_listening|
118
129
  with_listen_connection do |conn|
119
130
  ActiveSupport::Notifications.instrument("notifier_listen.good_job") do
120
131
  conn.async_exec("LISTEN #{CHANNEL}").clear
121
132
  end
122
133
 
123
134
  ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
124
- while pool.running?
125
- listening.make_true
135
+ thr_listening.make_true
136
+ while thr_executor.running?
126
137
  conn.wait_for_notify(WAIT_INTERVAL) do |channel, _pid, payload|
127
- listening.make_false
128
138
  next unless channel == CHANNEL
129
139
 
130
140
  ActiveSupport::Notifications.instrument("notifier_notified.good_job", { payload: payload })
131
141
  parsed_payload = JSON.parse(payload, symbolize_names: true)
132
- recipients.each do |recipient|
142
+ thr_recipients.each do |recipient|
133
143
  target, method_name = recipient.is_a?(Array) ? recipient : [recipient, :call]
134
144
  target.send(method_name, parsed_payload)
135
145
  end
136
146
  end
137
- listening.make_false
138
147
  end
139
148
  end
140
149
  ensure
141
- listening.make_false
150
+ thr_listening.make_false
142
151
  ActiveSupport::Notifications.instrument("notifier_unlisten.good_job") do
143
152
  conn.async_exec("UNLISTEN *").clear
144
153
  end
@@ -16,7 +16,7 @@ module GoodJob # :nodoc:
16
16
  # @!attribute [r] instances
17
17
  # @!scope class
18
18
  # List of all instantiated Pollers in the current process.
19
- # @return [array<GoodJob:Poller>]
19
+ # @return [Array<GoodJob:Poller>]
20
20
  cattr_reader :instances, default: [], instance_reader: false
21
21
 
22
22
  # Creates GoodJob::Poller from a GoodJob::Configuration instance.
@@ -40,35 +40,44 @@ module GoodJob # :nodoc:
40
40
 
41
41
  self.class.instances << self
42
42
 
43
- create_pool
43
+ create_timer
44
44
  end
45
45
 
46
- # Shut down the poller.
47
- # If +wait+ is +true+, the poller will wait for background thread to shutdown.
48
- # If +wait+ is +false+, this method will return immediately even though threads may still be running.
46
+ # Tests whether the timer is running.
47
+ # @return [true, false, nil]
48
+ delegate :running?, to: :timer, allow_nil: true
49
+
50
+ # Tests whether the timer is shutdown.
51
+ # @return [true, false, nil]
52
+ delegate :shutdown?, to: :timer, allow_nil: true
53
+
54
+ # Shut down the notifier.
49
55
  # Use {#shutdown?} to determine whether threads have stopped.
50
- # @param wait [Boolean] Wait for actively executing threads to finish
56
+ # @param timeout [nil, Numeric] Seconds to wait for active threads.
57
+ #
58
+ # * +nil+, the scheduler will trigger a shutdown but not wait for it to complete.
59
+ # * +-1+, the scheduler will wait until the shutdown is complete.
60
+ # * +0+, the scheduler will immediately shutdown and stop any threads.
61
+ # * A positive number will wait that many seconds before stopping any remaining active threads.
51
62
  # @return [void]
52
- def shutdown(wait: true)
53
- return unless @timer&.running?
63
+ def shutdown(timeout: -1)
64
+ return if timer.nil? || timer.shutdown?
54
65
 
55
- @timer.shutdown
56
- @timer.wait_for_termination if wait
57
- end
66
+ timer.shutdown if timer.running?
58
67
 
59
- # Tests whether the poller is shutdown.
60
- # @return [true, false, nil]
61
- def shutdown?
62
- !@timer&.running?
68
+ if timer.shuttingdown? && timeout # rubocop:disable Style/GuardClause
69
+ timer_wait = timeout.negative? ? nil : timeout
70
+ timer.kill unless timer.wait_for_termination(timer_wait)
71
+ end
63
72
  end
64
73
 
65
74
  # Restart the poller.
66
75
  # When shutdown, start; or shutdown and start.
67
- # @param wait [Boolean] Wait for background thread to finish
76
+ # @param timeout [nil, Numeric] Seconds to wait; shares same values as {#shutdown}.
68
77
  # @return [void]
69
- def restart(wait: true)
70
- shutdown(wait: wait)
71
- create_pool
78
+ def restart(timeout: -1)
79
+ shutdown(timeout: timeout) if running?
80
+ create_timer
72
81
  end
73
82
 
74
83
  # Invoked on completion of TimerTask task.
@@ -81,7 +90,9 @@ module GoodJob # :nodoc:
81
90
 
82
91
  private
83
92
 
84
- def create_pool
93
+ attr_reader :timer
94
+
95
+ def create_timer
85
96
  return if @timer_options[:execution_interval] <= 0
86
97
 
87
98
  @timer = Concurrent::TimerTask.new(@timer_options) do
@@ -16,8 +16,8 @@ module GoodJob # :nodoc:
16
16
  #
17
17
  class Scheduler
18
18
  # Defaults for instance of Concurrent::ThreadPoolExecutor
19
- # The thread pool is where work is performed.
20
- DEFAULT_POOL_OPTIONS = {
19
+ # The thread pool executor is where work is performed.
20
+ DEFAULT_EXECUTOR_OPTIONS = {
21
21
  name: name,
22
22
  min_threads: 0,
23
23
  max_threads: Configuration::DEFAULT_MAX_THREADS,
@@ -30,7 +30,7 @@ module GoodJob # :nodoc:
30
30
  # @!attribute [r] instances
31
31
  # @!scope class
32
32
  # List of all instantiated Schedulers in the current process.
33
- # @return [array<GoodJob:Scheduler>]
33
+ # @return [Array<GoodJob:Scheduler>]
34
34
  cattr_reader :instances, default: [], instance_reader: false
35
35
 
36
36
  # Creates GoodJob::Scheduler(s) and Performers from a GoodJob::Configuration instance.
@@ -70,66 +70,76 @@ module GoodJob # :nodoc:
70
70
  @performer = performer
71
71
 
72
72
  @max_cache = max_cache || 0
73
- @pool_options = DEFAULT_POOL_OPTIONS.dup
73
+ @executor_options = DEFAULT_EXECUTOR_OPTIONS.dup
74
74
  if max_threads.present?
75
- @pool_options[:max_threads] = max_threads
76
- @pool_options[:max_queue] = max_threads
75
+ @executor_options[:max_threads] = max_threads
76
+ @executor_options[:max_queue] = max_threads
77
77
  end
78
- @pool_options[:name] = "GoodJob::Scheduler(queues=#{@performer.name} max_threads=#{@pool_options[:max_threads]})"
78
+ @executor_options[:name] = "GoodJob::Scheduler(queues=#{@performer.name} max_threads=#{@executor_options[:max_threads]})"
79
79
 
80
- create_pool
80
+ create_executor
81
81
  warm_cache if warm_cache_on_initialize
82
82
  end
83
83
 
84
+ # Tests whether the scheduler is running.
85
+ # @return [true, false, nil]
86
+ delegate :running?, to: :executor, allow_nil: true
87
+
88
+ # Tests whether the scheduler is shutdown.
89
+ # @return [true, false, nil]
90
+ delegate :shutdown?, to: :executor, allow_nil: true
91
+
84
92
  # Shut down the scheduler.
85
- # This stops all threads in the pool.
86
- # If +wait+ is +true+, the scheduler will wait for any active tasks to finish.
87
- # If +wait+ is +false+, this method will return immediately even though threads may still be running.
93
+ # This stops all threads in the thread pool.
88
94
  # Use {#shutdown?} to determine whether threads have stopped.
89
- # @param wait [Boolean] Wait for actively executing jobs to finish
95
+ # @param timeout [nil, Numeric] Seconds to wait for actively executing jobs to finish
96
+ #
97
+ # * +nil+, the scheduler will trigger a shutdown but not wait for it to complete.
98
+ # * +-1+, the scheduler will wait until the shutdown is complete.
99
+ # * +0+, the scheduler will immediately shutdown and stop any active tasks.
100
+ # * A positive number will wait that many seconds before stopping any remaining active tasks.
90
101
  # @return [void]
91
- def shutdown(wait: true)
92
- return unless @pool&.running?
93
-
94
- instrument("scheduler_shutdown_start", { wait: wait })
95
- instrument("scheduler_shutdown", { wait: wait }) do
96
- @timer_set.shutdown
102
+ def shutdown(timeout: -1)
103
+ return if executor.nil? || executor.shutdown?
104
+
105
+ instrument("scheduler_shutdown_start", { timeout: timeout })
106
+ instrument("scheduler_shutdown", { timeout: timeout }) do
107
+ if executor.running?
108
+ @timer_set.shutdown
109
+ executor.shutdown
110
+ end
97
111
 
98
- @pool.shutdown
99
- @pool.wait_for_termination if wait
100
- # TODO: Should be killed if wait is not true
112
+ if executor.shuttingdown? && timeout
113
+ executor_wait = timeout.negative? ? nil : timeout
114
+ executor.kill unless executor.wait_for_termination(executor_wait)
115
+ end
101
116
  end
102
117
  end
103
118
 
104
- # Tests whether the scheduler is shutdown.
105
- # @return [true, false, nil]
106
- def shutdown?
107
- !@pool&.running?
108
- end
109
-
110
119
  # Restart the Scheduler.
111
120
  # When shutdown, start; or shutdown and start.
112
- # @param wait [Boolean] Wait for actively executing jobs to finish
121
+ # @param timeout [nil, Numeric] Seconds to wait for actively executing jobs to finish; shares same values as {#shutdown}.
113
122
  # @return [void]
114
- def restart(wait: true)
123
+ def restart(timeout: -1)
115
124
  instrument("scheduler_restart_pools") do
116
- shutdown(wait: wait) unless shutdown?
117
- create_pool
125
+ shutdown(timeout: timeout) if running?
126
+ create_executor
118
127
  warm_cache
119
128
  end
120
129
  end
121
130
 
122
131
  # Wakes a thread to allow the performer to execute a task.
123
- # @param state [nil, Object] Contextual information for the performer. See {Performer#next?}.
132
+ # @param state [nil, Object] Contextual information for the performer. See {JobPerformer#next?}.
124
133
  # @return [nil, Boolean] Whether work was started.
125
- # Returns +nil+ if the scheduler is unable to take new work, for example if the thread pool is shut down or at capacity.
126
- # Returns +true+ if the performer started executing work.
127
- # Returns +false+ if the performer decides not to attempt to execute a task based on the +state+ that is passed to it.
134
+ #
135
+ # * +nil+ if the scheduler is unable to take new work, for example if the thread pool is shut down or at capacity.
136
+ # * +true+ if the performer started executing work.
137
+ # * +false+ if the performer decides not to attempt to execute a task based on the +state+ that is passed to it.
128
138
  def create_thread(state = nil)
129
- return nil unless @pool.running?
139
+ return nil unless executor.running?
130
140
 
131
141
  if state
132
- return false unless @performer.next?(state)
142
+ return false unless performer.next?(state)
133
143
 
134
144
  if state[:scheduled_at]
135
145
  scheduled_at = if state[:scheduled_at].is_a? String
@@ -144,18 +154,12 @@ module GoodJob # :nodoc:
144
154
  delay ||= 0
145
155
  run_now = delay <= 0.01
146
156
  if run_now
147
- return nil unless @pool.ready_worker_count.positive?
157
+ return nil unless executor.ready_worker_count.positive?
148
158
  elsif @max_cache.positive?
149
159
  return nil unless remaining_cache_count.positive?
150
160
  end
151
161
 
152
- future = Concurrent::ScheduledTask.new(delay, args: [@performer], executor: @pool, timer_set: timer_set) do |performer|
153
- output = nil
154
- Rails.application.executor.wrap { output = performer.next }
155
- output
156
- end
157
- future.add_observer(self, :task_observer)
158
- future.execute
162
+ create_task(delay)
159
163
 
160
164
  run_now ? true : nil
161
165
  end
@@ -169,44 +173,46 @@ module GoodJob # :nodoc:
169
173
  create_task if output
170
174
  end
171
175
 
176
+ # Information about the Scheduler
177
+ # @return [Hash]
178
+ def stats
179
+ {
180
+ name: performer.name,
181
+ max_threads: @executor_options[:max_threads],
182
+ active_threads: @executor_options[:max_threads] - executor.ready_worker_count,
183
+ available_threads: executor.ready_worker_count,
184
+ max_cache: @max_cache,
185
+ active_cache: cache_count,
186
+ available_cache: remaining_cache_count,
187
+ }
188
+ end
189
+
172
190
  def warm_cache
173
191
  return if @max_cache.zero?
174
192
 
175
- @performer.next_at(
193
+ performer.next_at(
176
194
  limit: @max_cache,
177
- now_limit: @pool_options[:max_threads]
195
+ now_limit: @executor_options[:max_threads]
178
196
  ).each do |scheduled_at|
179
197
  create_thread({ scheduled_at: scheduled_at })
180
198
  end
181
199
  end
182
200
 
183
- def stats
184
- {
185
- name: @performer.name,
186
- max_threads: @pool_options[:max_threads],
187
- active_threads: @pool_options[:max_threads] - @pool.ready_worker_count,
188
- available_threads: @pool.ready_worker_count,
189
- max_cache: @max_cache,
190
- active_cache: cache_count,
191
- available_cache: remaining_cache_count,
192
- }
193
- end
194
-
195
201
  private
196
202
 
197
- attr_reader :timer_set
203
+ attr_reader :performer, :executor, :timer_set
198
204
 
199
- def create_pool
200
- instrument("scheduler_create_pool", { performer_name: @performer.name, max_threads: @pool_options[:max_threads] }) do
205
+ def create_executor
206
+ instrument("scheduler_create_pool", { performer_name: performer.name, max_threads: @executor_options[:max_threads] }) do
201
207
  @timer_set = Concurrent::TimerSet.new
202
- @pool = ThreadPoolExecutor.new(@pool_options)
208
+ @executor = ThreadPoolExecutor.new(@executor_options)
203
209
  end
204
210
  end
205
211
 
206
212
  def create_task(delay = 0)
207
- future = Concurrent::ScheduledTask.new(delay, args: [@performer], executor: @pool, timer_set: timer_set) do |performer|
213
+ future = Concurrent::ScheduledTask.new(delay, args: [performer], executor: executor, timer_set: timer_set) do |thr_performer|
208
214
  output = nil
209
- Rails.application.executor.wrap { output = performer.next }
215
+ Rails.application.executor.wrap { output = thr_performer.next }
210
216
  output
211
217
  end
212
218
  future.add_observer(self, :task_observer)
@@ -1,4 +1,4 @@
1
1
  module GoodJob
2
2
  # GoodJob gem version.
3
- VERSION = '1.7.1'.freeze
3
+ VERSION = '1.8.0'.freeze
4
4
  end
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.7.1
4
+ version: 1.8.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-01-27 00:00:00.000000000 Z
11
+ date: 2021-03-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -400,7 +400,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
400
400
  - !ruby/object:Gem::Version
401
401
  version: '0'
402
402
  requirements: []
403
- rubygems_version: 3.2.4
403
+ rubygems_version: 3.1.4
404
404
  signing_key:
405
405
  specification_version: 4
406
406
  summary: A multithreaded, Postgres-based ActiveJob backend for Ruby on Rails