good_job 0.9.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bddc60fa297293af01b1334d4adf51981bfb8b314ba4577a7b3bff1d7385bd5a
4
- data.tar.gz: 3533f21ffee4dba606558fa0fadd1f37a943aa285b4f6bebed4bb3b89045b61e
3
+ metadata.gz: 7c6d495a4455453f6f4af736d3fd31685fc2026c98e0cd564ea0f47b188cc473
4
+ data.tar.gz: 794137e732ed3fcebec859daa8e58a2c2c8fd283cc43c5cfc7824263a1ca93fd
5
5
  SHA512:
6
- metadata.gz: 74c0d3df2df08e78d06baa8982401d633af536277231a17d87687ff3c7ba1553d4123f7c1ce49ae23094bd781db5ee23eb9584eeba94aae49fb8cc83da0c68e0
7
- data.tar.gz: 7df20f03621cb66b864a2c2950e4a11f60199cd4954ad5a622a1631254fa04bab1243ee376567600a2aed294a8c14fa9802fd1024855dc7d473a7ff8f7166515
6
+ metadata.gz: 258757117262f25f5507ceb47c20f0f43a1f40427c7a0f9c3d0dc7803f25019062a160de08e96fd5ba74ceeeec5666d1084f6dff7ca9d381b2bbb198fabb10fd
7
+ data.tar.gz: e2a5d09cfb57a7f2a08d59f84c3b39e3582cb5b0b76a7e6ea2da421542d5e0ebee2e25a79298c1f5d3351d58e156e43ac0770e1d9e26e0f26169620f0bfcc493
@@ -1,21 +1,117 @@
1
1
  # Changelog
2
2
 
3
+ ## [v1.1.0](https://github.com/bensheldon/good_job/tree/v1.1.0) (2020-08-09)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.0.3...v1.1.0)
6
+
7
+ **Closed issues:**
8
+
9
+ - Document reliability guarantees [\#59](https://github.com/bensheldon/good_job/issues/59)
10
+ - Document how to hook in exception monitor \(Sentry, Rollbar, etc\) [\#47](https://github.com/bensheldon/good_job/issues/47)
11
+ - Allow an Async mode [\#27](https://github.com/bensheldon/good_job/issues/27)
12
+
13
+ **Merged pull requests:**
14
+
15
+ - Add a callable hook on thread errors [\#71](https://github.com/bensheldon/good_job/pull/71) ([bensheldon](https://github.com/bensheldon))
16
+ - Clarify reliability guarantees [\#70](https://github.com/bensheldon/good_job/pull/70) ([bensheldon](https://github.com/bensheldon))
17
+ - Clean up Readme formatting; re-arrange tests for clarity and values [\#69](https://github.com/bensheldon/good_job/pull/69) ([bensheldon](https://github.com/bensheldon))
18
+ - Create an Async execution mode [\#68](https://github.com/bensheldon/good_job/pull/68) ([bensheldon](https://github.com/bensheldon))
19
+ - Move all stdout to LogSubscriber [\#67](https://github.com/bensheldon/good_job/pull/67) ([bensheldon](https://github.com/bensheldon))
20
+ - Allow schedulers to be restarted; separate unit tests from integration tests [\#66](https://github.com/bensheldon/good_job/pull/66) ([bensheldon](https://github.com/bensheldon))
21
+
22
+ ## [v1.0.3](https://github.com/bensheldon/good_job/tree/v1.0.3) (2020-07-26)
23
+
24
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.0.2...v1.0.3)
25
+
26
+ **Fixed bugs:**
27
+
28
+ - Preserve GoodJob::Jobs when a StandardError is raised [\#60](https://github.com/bensheldon/good_job/issues/60)
29
+
30
+ **Closed issues:**
31
+
32
+ - Have an initial setup generator [\#6](https://github.com/bensheldon/good_job/issues/6)
33
+
34
+ **Merged pull requests:**
35
+
36
+ - Re-perform a job if a StandardError bubbles up; better document job reliability [\#62](https://github.com/bensheldon/good_job/pull/62) ([bensheldon](https://github.com/bensheldon))
37
+ - Update the setup documentation to use correct bin setup command [\#61](https://github.com/bensheldon/good_job/pull/61) ([jm96441n](https://github.com/jm96441n))
38
+
39
+ ## [v1.0.2](https://github.com/bensheldon/good_job/tree/v1.0.2) (2020-07-25)
40
+
41
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.0.1...v1.0.2)
42
+
43
+ **Fixed bugs:**
44
+
45
+ - Fix counting of available execution threads [\#58](https://github.com/bensheldon/good_job/pull/58) ([bensheldon](https://github.com/bensheldon))
46
+
47
+ **Closed issues:**
48
+
49
+ - repeating/recurring jobs [\#53](https://github.com/bensheldon/good_job/issues/53)
50
+
51
+ **Merged pull requests:**
52
+
53
+ - Add migration generator [\#56](https://github.com/bensheldon/good_job/pull/56) ([thedanbob](https://github.com/thedanbob))
54
+ - Fix migration script in readme [\#55](https://github.com/bensheldon/good_job/pull/55) ([thedanbob](https://github.com/thedanbob))
55
+
56
+ ## [v1.0.1](https://github.com/bensheldon/good_job/tree/v1.0.1) (2020-07-22)
57
+
58
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.0.0...v1.0.1)
59
+
60
+ **Merged pull requests:**
61
+
62
+ - Change threadpool idletime default to 60 seconds from 0 [\#49](https://github.com/bensheldon/good_job/pull/49) ([bensheldon](https://github.com/bensheldon))
63
+
64
+ ## [v1.0.0](https://github.com/bensheldon/good_job/tree/v1.0.0) (2020-07-20)
65
+
66
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v0.9.0...v1.0.0)
67
+
3
68
  ## [v0.9.0](https://github.com/bensheldon/good_job/tree/v0.9.0) (2020-07-20)
4
69
 
5
- [Full Changelog](https://github.com/bensheldon/good_job/compare/v0.6.0...v0.9.0)
70
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v0.8.2...v0.9.0)
71
+
72
+ **Merged pull requests:**
73
+
74
+ - Allow preservation of finished job records [\#46](https://github.com/bensheldon/good_job/pull/46) ([bensheldon](https://github.com/bensheldon))
75
+
76
+ ## [v0.8.2](https://github.com/bensheldon/good_job/tree/v0.8.2) (2020-07-18)
77
+
78
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v0.8.1...v0.8.2)
6
79
 
7
80
  **Closed issues:**
8
81
 
9
- - Always store a default priority \(0\) and scheduled\_at\(Time.current\) [\#30](https://github.com/bensheldon/good_job/issues/30)
10
82
  - Add a job timeout configuration to time out jobs that have run too long [\#19](https://github.com/bensheldon/good_job/issues/19)
11
83
 
12
84
  **Merged pull requests:**
13
85
 
14
- - Allow preservation of finished job records [\#46](https://github.com/bensheldon/good_job/pull/46) ([bensheldon](https://github.com/bensheldon))
15
86
  - Run Github Action tests on PRs from forks [\#44](https://github.com/bensheldon/good_job/pull/44) ([bensheldon](https://github.com/bensheldon))
16
87
  - Fix Rubygems homepage URL [\#43](https://github.com/bensheldon/good_job/pull/43) ([joshmn](https://github.com/joshmn))
88
+
89
+ ## [v0.8.1](https://github.com/bensheldon/good_job/tree/v0.8.1) (2020-07-18)
90
+
91
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v0.8.0...v0.8.1)
92
+
93
+ **Merged pull requests:**
94
+
17
95
  - 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))
96
+
97
+ ## [v0.8.0](https://github.com/bensheldon/good_job/tree/v0.8.0) (2020-07-17)
98
+
99
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v0.7.0...v0.8.0)
100
+
101
+ **Merged pull requests:**
102
+
18
103
  - Replace Adapter inline boolean kwarg with execution\_mode instead [\#41](https://github.com/bensheldon/good_job/pull/41) ([bensheldon](https://github.com/bensheldon))
104
+
105
+ ## [v0.7.0](https://github.com/bensheldon/good_job/tree/v0.7.0) (2020-07-16)
106
+
107
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v0.6.0...v0.7.0)
108
+
109
+ **Closed issues:**
110
+
111
+ - Always store a default priority \(0\) and scheduled\_at\(Time.current\) [\#30](https://github.com/bensheldon/good_job/issues/30)
112
+
113
+ **Merged pull requests:**
114
+
19
115
  - Add more examples to Readme [\#39](https://github.com/bensheldon/good_job/pull/39) ([bensheldon](https://github.com/bensheldon))
20
116
  - Add additional Rubocops and lint [\#38](https://github.com/bensheldon/good_job/pull/38) ([bensheldon](https://github.com/bensheldon))
21
117
  - Always store a default queue\_name, priority and scheduled\_at; index by queue\_name and scheduled\_at [\#37](https://github.com/bensheldon/good_job/pull/37) ([bensheldon](https://github.com/bensheldon))
data/README.md CHANGED
@@ -9,6 +9,8 @@ GoodJob is a multithreaded, Postgres-based, ActiveJob backend for Ruby on Rails.
9
9
  - **Backed by Postgres.** Relies upon Postgres integrity and session-level Advisory Locks to provide run-once safety and stay within the limits of `schema.rb`.
10
10
  - **For most workloads.** Targets full-stack teams, economy-minded solo developers, and applications that enqueue less than 1-million jobs/day.
11
11
 
12
+ For more of the story of GoodJob, read the [introductory blog post](https://island94.org/2020/07/introducing-goodjob-1-0).
13
+
12
14
  ## Installation
13
15
 
14
16
  Add this line to your application's Gemfile:
@@ -25,35 +27,11 @@ $ bundle install
25
27
  ## Usage
26
28
 
27
29
  1. Create a database migration:
28
- ```bash
29
- $ bin/rails g migration CreateGoodJobs
30
+
31
+ ```bash
32
+ $ bin/rails g good_job:install
30
33
  ```
31
34
 
32
- Add to the newly created migration file:
33
-
34
- ```ruby
35
- class CreateGoodJobs < ActiveRecord::Migration[6.0]
36
- def change
37
- enable_extension 'pgcrypto'
38
-
39
- create_table :good_jobs, id: :uuid do |t|
40
- t.timestamps
41
-
42
- t.text :queue_name
43
- t.integer :priority
44
- t.jsonb :serialized_params
45
- t.timestamp :scheduled_at
46
- t.timestamp :performed_at
47
- t.timestamp :finished_at
48
- t.text :error
49
-
50
- add_index :good_jobs, :scheduled_at, where: "(finished_at IS NULL)"
51
- add_index :good_jobs, [:queue_name, :scheduled_at], where: "(finished_at IS NULL)"
52
- end
53
- end
54
- end
55
- ```
56
-
57
35
  Run the migration:
58
36
 
59
37
  ```bash
@@ -61,7 +39,8 @@ $ bundle install
61
39
  ```
62
40
 
63
41
  1. Configure the ActiveJob adapter:
64
- ```ruby
42
+
43
+ ```ruby
65
44
  # config/application.rb
66
45
  config.active_job.queue_adapter = :good_job
67
46
  ```
@@ -80,16 +59,19 @@ $ bundle install
80
59
  ```
81
60
 
82
61
  1. Queue your job 🎉:
62
+
83
63
  ```ruby
84
64
  YourJob.set(queue: :some_queue, wait: 5.minutes, priority: 10).perform_later
85
65
  ```
86
66
 
87
67
  1. In production, the scheduler is designed to run in its own process:
68
+
88
69
  ```bash
89
70
  $ bundle exec good_job
90
71
  ```
91
72
 
92
73
  Configuration options available with `help`:
74
+
93
75
  ```bash
94
76
  $ bundle exec good_job help start
95
77
 
@@ -102,26 +84,120 @@ $ bundle install
102
84
  # [--poll-interval=N] # Interval between polls for available jobs in seconds (default: 1)
103
85
  ```
104
86
 
105
- ### Taking advantage of ActiveJob
87
+ ### Error handling, retries, and reliability
88
+
89
+ GoodJob guarantees that a completely-performed job will run once and only once. GoodJob fully supports ActiveJob's built-in functionality for error handling, retries and timeouts. Writing reliable, transactional, and idempotent `ActiveJob#perform` methods is outside the scope of GoodJob.
90
+
91
+ #### Error handling
92
+
93
+ By default, if a job raises an error while it is being performed, _and it bubbles up to the GoodJob backend_, GoodJob will be immediately re-perform the job until it finishes successfully.
94
+
95
+ - `Exception`-type errors, such as a SIGINT, will always cause a job to be re-performed.
96
+ - `StandardError`-type errors, by default, will cause a job to be re-performed, though this is configurable:
97
+
98
+ ```ruby
99
+ # config/initializers/good_job.rb
100
+ GoodJob.reperform_jobs_on_standard_error = true # => default
101
+ ```
102
+
103
+ To report errors that _do_ bubble up to the GoodJob backend, assign a callable to `GoodJob.on_thread_error`. For example:
104
+
105
+ ```ruby
106
+ # config/initializers/good_job.rb
107
+
108
+ # With Sentry (or Bugsnag, Airbrake, Honeybadger, etc.)
109
+ GoodJob.on_thread_error = -> (exception) { Raven.capture_exception(exception) }
110
+ ```
111
+
112
+ ### Retrying jobs
113
+
114
+ ActiveJob can be configured to retry an infinite number of times, with an exponential backoff. Using ActiveJob's `retry_on` will ensure that errors do not bubble up to the GoodJob backend:
115
+
116
+ ```ruby
117
+ class ApplicationJob < ActiveJob::Base
118
+ retry_on StandardError, wait: :exponentially_longer, attempts: Float::INFINITY
119
+ # ...
120
+ end
121
+ ```
122
+
123
+ When specifying a limited number of retries, care must be taken to ensure that an error does not bubble up to the GoodJob backend because that will result in the job being re-performed:
124
+
125
+ ```ruby
126
+ class ApplicationJob < ActiveJob::Base
127
+ retry_on StandardError, attempts: 5 do |_job, _exception|
128
+ # Log error, etc.
129
+ # You must implement this block, otherwise,
130
+ # Active Job will re-raise the error.
131
+ # Do not re-raise the error, otherwise
132
+ # GoodJob will immediately re-perform the job.
133
+ end
134
+ # ...
135
+ end
136
+ ```
106
137
 
107
- ActiveJob has a rich set of built-in functionality for timeouts, error handling, and retrying. For example:
138
+ GoodJob can be configured to allow omitting `retry_on`'s block argument and implicitly discard un-handled errors:
139
+
140
+ ```ruby
141
+ # config/initializers/good_job.rb
142
+
143
+ # Do NOT re-perform a job if a StandardError bubbles up to the GoodJob backend
144
+ GoodJob.reperform_jobs_on_standard_error = false
145
+ ```
146
+
147
+ When using an exception monitoring service (e.g. Sentry, Bugsnag, Airbrake, Honeybadger, etc), the use of `rescue_on` may be incompatible with their ActiveJob integration. It's safest to explicitly wrap jobs with an exception reporter. For example:
108
148
 
109
149
  ```ruby
110
150
  class ApplicationJob < ActiveJob::Base
111
- # Retry errors an infinite number of times with exponential back-off
112
151
  retry_on StandardError, wait: :exponentially_longer, attempts: Float::INFINITY
152
+
153
+ around_perform do |_job, block|
154
+ block.call
155
+ rescue StandardError => e
156
+ Raven.capture_exception(e)
157
+ raise
158
+ end
159
+ # ...
160
+ end
161
+ ```
162
+
163
+
164
+ ActiveJob's `discard_on` functionality is supported too.
165
+
166
+ #### ActionMailer retries
113
167
 
114
- # Timeout jobs after 10 minutes
168
+ Using a Mailer's `#deliver_later` will enqueue an instance of `ActionMailer::DeliveryJob` which inherits from `ActiveJob::Base` rather than your applications `ApplicationJob`. You can use an initializer to configure retries on `ActionMailer::DeliveryJob`:
169
+
170
+ ```ruby
171
+ # config/initializers/good_job.rb
172
+ ActionMailer::DeliveryJob.retry_on StandardError, wait: :exponentially_longer, attempts: Float::INFINITY
173
+
174
+ # With Sentry (or Bugsnag, Airbrake, Honeybadger, etc.)
175
+ ActionMailer::DeliveryJob.around_perform do |_job, block|
176
+ block.call
177
+ rescue StandardError => e
178
+ Raven.capture_exception(e)
179
+ raise
180
+ end
181
+ ```
182
+
183
+ #### Timeouts
184
+
185
+ Job timeouts can be configured with an `around_perform`:
186
+
187
+ ```ruby
188
+ class ApplicationJob < ActiveJob::Base
115
189
  JobTimeoutError = Class.new(StandardError)
190
+
116
191
  around_perform do |_job, block|
192
+ # Timeout jobs after 10 minutes
117
193
  Timeout.timeout(10.minutes, JobTimeoutError) do
118
194
  block.call
119
195
  end
120
196
  end
121
197
  end
122
198
  ```
123
-
124
- ### Configuring Job Execution Threads
199
+
200
+ ### Configuring job execution threads
125
201
 
126
202
  GoodJob executes enqueued jobs using threads. There is a lot than can be said about [multithreaded behavior in Ruby on Rails](https://guides.rubyonrails.org/threading_and_code_execution.html), but briefly:
127
203
 
@@ -132,6 +208,51 @@ GoodJob executes enqueued jobs using threads. There is a lot than can be said ab
132
208
  3. `$ RAILS_MAX_THREADS=4 bundle exec good_job`
133
209
  4. Implicitly via Rails's database connection pool size (`ActiveRecord::Base.connection_pool.size`)
134
210
 
211
+ ### Executing jobs async / in-process
212
+
213
+ GoodJob is able to run "async" in the same process as the webserver (e.g. `bin/rail s`). GoodJob's async execution mode offers benefits of economy by not requiring a separate job worker process, but with the tradeoff of increased complexity. Async mode can be configured in two ways:
214
+
215
+ - Directly configure the ActiveJob adapter:
216
+
217
+ ```ruby
218
+ # config/environments/production.rb
219
+ config.active_job.queue_adapter = GoodJob::Adapter.new(execution_mode: :async, max_threads: 4, poll_interval: 30)
220
+ ```
221
+ - Or, when using `...queue_adapter = :good_job`, via environment variables:
222
+
223
+ ```bash
224
+ $ GOOD_JOB_EXECUTION_MODE=async GOOD_JOB_MAX_THREADS=4 GOOD_JOB_POLL_INTERVAL=30 bin/rails server
225
+ ```
226
+
227
+ Depending on your application configuration, you may need to take additional steps:
228
+
229
+ - Ensure that you have enough database connections for both web and job execution threads:
230
+
231
+ ```yaml
232
+ # config/database.yml
233
+ pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5).to_i + ENV.fetch("GOOD_JOB_MAX_THREADS", 4).to_i %>
234
+ ```
235
+
236
+ - When running Puma with workers (`WEB_CONCURRENCY > 0`) or another process-forking webserver, GoodJob's threadpool schedulers should be stopped before forking, restarted after fork, and cleanly shut down on exit. Stopping GoodJob's scheduler pre-fork is recommended to ensure that GoodJob does not continue executing jobs in the parent/controller process. For example, with Puma:
237
+
238
+ ```ruby
239
+ # config/puma.rb
240
+
241
+ before_fork do
242
+ GoodJob::Scheduler.instances.each { |s| s.shutdown }
243
+ end
244
+
245
+ on_worker_boot do
246
+ GoodJob::Scheduler.instances.each { |s| s.restart }
247
+ end
248
+
249
+ on_worker_shutdown do
250
+ GoodJob::Scheduler.instances.each { |s| s.shutdown }
251
+ end
252
+ ```
253
+
254
+ GoodJob is compatible with Puma's `preload_app!` method.
255
+
135
256
  ### Migrating to GoodJob from a different ActiveJob backend
136
257
 
137
258
  If your application is already using an ActiveJob backend, you will need to install GoodJob to enqueue and perform newly created jobs _and_ finish performing pre-existing jobs on the previous backend.
@@ -147,7 +268,8 @@ If your application is already using an ActiveJob backend, you will need to inst
147
268
  ```
148
269
 
149
270
  1. Continue running executors for both backends. For example, on Heroku it's possible to run [two processes](https://help.heroku.com/CTFS2TJK/how-do-i-run-multiple-processes-on-a-dyno) within the same dyno:
150
- ```procfile
271
+
272
+ ```procfile
151
273
  # Procfile
152
274
  # ...
153
275
  worker: bundle exec que ./config/environment.rb & bundle exec good_job & wait -n
@@ -173,15 +295,24 @@ It is also necessary to delete these preserved jobs from the database after a ce
173
295
  - For example, in a Rake task:
174
296
 
175
297
  ```ruby
176
- # GoodJob::Job.finished(1.day.ago).delete_all
298
+ GoodJob::Job.finished(1.day.ago).delete_all
177
299
  ```
300
+
178
301
  - For example, using the `good_job` command-line utility:
179
302
 
180
303
  ```bash
181
304
  $ bundle exec good_job cleanup_preserved_jobs --before-seconds-ago=86400
182
305
  ```
183
306
 
184
- ## Development
307
+ ## Contributing
308
+
309
+ Contributions are welcomed and appreciated 🙏
310
+
311
+ - Review the [Prioritized Project Backlog](https://github.com/bensheldon/good_job/projects/1).
312
+ - Open a new Issue or contribute to an [existing Issue](https://github.com/bensheldon/good_job/issues). Questions or suggestions are fantastic.
313
+ - Participate according to our [Code of Conduct](https://github.com/bensheldon/good_job/projects/1).
314
+
315
+ ### Gem development
185
316
 
186
317
  To run tests:
187
318
 
@@ -190,7 +321,7 @@ To run tests:
190
321
  $ git clone git@github.com:bensheldon/good_job.git
191
322
 
192
323
  # Set up the local environment
193
- $ bin/setup_test
324
+ $ bin/setup
194
325
 
195
326
  # Run the tests
196
327
  $ bin/rspec
@@ -204,7 +335,6 @@ $ bundle exec appraisal
204
335
 
205
336
  # Run tests
206
337
  $ bundle exec appraisal bin/rspec
207
-
208
338
  ```
209
339
 
210
340
  For developing locally within another Ruby on Rails project:
@@ -219,24 +349,23 @@ $ bundle install
219
349
  # => Using good_job 0.1.0 from https://github.com/bensheldon/good_job.git (at /Users/You/Projects/good_job@dc57fb0)
220
350
  ```
221
351
 
222
- ## Releasing
352
+ ### Releasing
223
353
 
224
- Package maintainers can release this gem with the following [gem-release](https://github.com/svenfuchs/gem-release) command:
354
+ Package maintainers can release this gem by running:
225
355
 
226
356
  ```bash
227
357
  # Sign into rubygems
228
358
  $ gem signin
229
359
 
360
+ # Add a .env file with the following:
361
+ # CHANGELOG_GITHUB_TOKEN= # Github Personal Access Token
362
+
230
363
  # Update version number, changelog, and create git commit:
231
- $ bundle exec rake commit_version[minor] # major,minor,patch
364
+ $ bundle exec rake release[minor] # major,minor,patch
232
365
 
233
366
  # ..and follow subsequent directions.
234
367
  ```
235
368
 
236
- ## Contributing
237
-
238
- Contribution directions go here.
239
-
240
369
  ## License
241
370
 
242
371
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -1,18 +1,35 @@
1
1
  module ActiveJob
2
2
  module QueueAdapters
3
3
  class GoodJobAdapter < GoodJob::Adapter
4
- def initialize(execution_mode: nil)
4
+ def initialize(execution_mode: nil, max_threads: nil, poll_interval: nil, scheduler: nil)
5
5
  execution_mode = if execution_mode
6
6
  execution_mode
7
7
  elsif ENV['GOOD_JOB_EXECUTION_MODE'].present?
8
8
  ENV['GOOD_JOB_EXECUTION_MODE'].to_sym
9
- elsif Rails.env.development? || Rails.env.test?
9
+ elsif Rails.env.development?
10
+ :inline
11
+ elsif Rails.env.test?
10
12
  :inline
11
13
  else
12
14
  :external
13
15
  end
14
16
 
15
- super(execution_mode: execution_mode)
17
+ if execution_mode == :async && scheduler.blank?
18
+ max_threads = (
19
+ max_threads.presence ||
20
+ ENV['GOOD_JOB_MAX_THREADS'] ||
21
+ ENV['RAILS_MAX_THREADS'] ||
22
+ ActiveRecord::Base.connection_pool.size
23
+ ).to_i
24
+
25
+ poll_interval = (
26
+ poll_interval.presence ||
27
+ ENV['GOOD_JOB_POLL_INTERVAL'] ||
28
+ 1
29
+ ).to_i
30
+ end
31
+
32
+ super(execution_mode: execution_mode, max_threads: max_threads, poll_interval: poll_interval, scheduler: scheduler)
16
33
  end
17
34
  end
18
35
  end
@@ -0,0 +1,24 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/active_record'
3
+
4
+ module GoodJob
5
+ class InstallGenerator < Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+
8
+ class << self
9
+ delegate :next_migration_number, to: ActiveRecord::Generators::Base
10
+ end
11
+
12
+ source_paths << File.join(File.dirname(__FILE__), "templates")
13
+
14
+ def create_migration_file
15
+ migration_template 'migration.rb.erb', 'db/migrate/create_good_jobs.rb', migration_version: migration_version
16
+ end
17
+
18
+ private
19
+
20
+ def migration_version
21
+ "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ class CreateGoodJobs < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ enable_extension 'pgcrypto'
4
+
5
+ create_table :good_jobs, id: :uuid do |t|
6
+ t.text :queue_name
7
+ t.integer :priority
8
+ t.jsonb :serialized_params
9
+ t.timestamp :scheduled_at
10
+ t.timestamp :performed_at
11
+ t.timestamp :finished_at
12
+ t.text :error
13
+
14
+ t.timestamps
15
+ end
16
+
17
+ add_index :good_jobs, :scheduled_at, where: "(finished_at IS NULL)"
18
+ add_index :good_jobs, [:queue_name, :scheduled_at], where: "(finished_at IS NULL)"
19
+ end
20
+ end
@@ -1,7 +1,7 @@
1
1
  require "rails"
2
2
  require 'good_job/railtie'
3
3
 
4
- require 'good_job/logging'
4
+ require 'good_job/log_subscriber'
5
5
  require 'good_job/lockable'
6
6
  require 'good_job/job'
7
7
  require 'good_job/scheduler'
@@ -12,8 +12,10 @@ require 'good_job/performer'
12
12
  require 'active_job/queue_adapters/good_job_adapter'
13
13
 
14
14
  module GoodJob
15
+ cattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
15
16
  mattr_accessor :preserve_job_records, default: false
16
- include Logging
17
+ mattr_accessor :reperform_jobs_on_standard_error, default: true
18
+ mattr_accessor :on_thread_error, default: nil
17
19
 
18
20
  ActiveSupport.run_load_hooks(:good_job, self)
19
21
  end
@@ -1,8 +1,8 @@
1
1
  module GoodJob
2
2
  class Adapter
3
- EXECUTION_MODES = [:inline, :external].freeze # TODO: async
3
+ EXECUTION_MODES = [:async, :external, :inline].freeze
4
4
 
5
- def initialize(execution_mode: nil, inline: false)
5
+ def initialize(execution_mode: nil, max_threads: nil, poll_interval: nil, scheduler: nil, inline: false)
6
6
  if inline
7
7
  ActiveSupport::Deprecation.warn('GoodJob::Adapter#new(inline: true) is deprecated; use GoodJob::Adapter.new(execution_mode: :inline) instead')
8
8
  @execution_mode = :inline
@@ -13,6 +13,18 @@ module GoodJob
13
13
  else
14
14
  @execution_mode = :external
15
15
  end
16
+
17
+ @scheduler = scheduler
18
+ if @execution_mode == :async && @scheduler.blank? # rubocop:disable Style/GuardClause
19
+ timer_options = {}
20
+ timer_options[:execution_interval] = poll_interval if poll_interval.present?
21
+
22
+ pool_options = {}
23
+ pool_options[:max_threads] = max_threads if max_threads.present?
24
+
25
+ job_performer = GoodJob::Performer.new(GoodJob::Job, :perform_with_advisory_lock, name: '*')
26
+ @scheduler = GoodJob::Scheduler.new(job_performer, timer_options: timer_options, pool_options: pool_options)
27
+ end
16
28
  end
17
29
 
18
30
  def enqueue(active_job)
@@ -34,11 +46,21 @@ module GoodJob
34
46
  end
35
47
  end
36
48
 
49
+ @scheduler.create_thread if execute_async?
50
+
37
51
  good_job
38
52
  end
39
53
 
40
- def shutdown(wait: true) # rubocop:disable Lint/UnusedMethodArgument
41
- nil
54
+ def shutdown(wait: true)
55
+ @scheduler&.shutdown(wait: wait)
56
+ end
57
+
58
+ def execute_async?
59
+ @execution_mode == :async
60
+ end
61
+
62
+ def execute_externally?
63
+ @execution_mode == :external
42
64
  end
43
65
 
44
66
  def execute_inline?
@@ -49,9 +71,5 @@ module GoodJob
49
71
  ActiveSupport::Deprecation.warn('GoodJob::Adapter::inline? is deprecated; use GoodJob::Adapter::execute_inline? instead')
50
72
  execute_inline?
51
73
  end
52
-
53
- def execute_externally?
54
- @execution_mode == :external
55
- end
56
74
  end
57
75
  end
@@ -16,7 +16,7 @@ module GoodJob
16
16
  type: :numeric,
17
17
  desc: "Interval between polls for available jobs in seconds (default: 1)"
18
18
  def start
19
- require RAILS_ENVIRONMENT_RB
19
+ set_up_application!
20
20
 
21
21
  max_threads = (
22
22
  options[:max_threads] ||
@@ -25,29 +25,19 @@ module GoodJob
25
25
  ActiveRecord::Base.connection_pool.size
26
26
  ).to_i
27
27
 
28
- queue_names = (
28
+ queue_string = (
29
29
  options[:queues] ||
30
30
  ENV['GOOD_JOB_QUEUES'] ||
31
31
  '*'
32
- ).split(',').map(&:strip)
32
+ )
33
33
 
34
34
  poll_interval = (
35
35
  options[:poll_interval] ||
36
36
  ENV['GOOD_JOB_POLL_INTERVAL']
37
37
  ).to_i
38
38
 
39
- job_query = GoodJob::Job.all.priority_ordered
40
- queue_names_without_all = queue_names.reject { |q| q == '*' }
41
- job_query = job_query.where(queue_name: queue_names_without_all) unless queue_names_without_all.size.zero?
42
-
43
- performer_method = if GoodJob.preserve_job_records
44
- :perform_with_advisory_lock_and_preserve_job_records
45
- else
46
- :perform_with_advisory_lock_and_destroy_job_records
47
- end
48
- job_performer = GoodJob::Performer.new(job_query, performer_method)
49
-
50
- $stdout.puts "GoodJob worker starting with max_threads=#{max_threads} on queues=#{queue_names.join(',')}"
39
+ job_query = GoodJob::Job.queue_string(queue_string)
40
+ job_performer = GoodJob::Performer.new(job_query, :perform_with_advisory_lock, name: queue_string)
51
41
 
52
42
  timer_options = {}
53
43
  timer_options[:execution_interval] = poll_interval if poll_interval.positive?
@@ -68,24 +58,31 @@ module GoodJob
68
58
  break if @stop_good_job_executable || scheduler.shutdown?
69
59
  end
70
60
 
71
- $stdout.puts "\nFinishing GoodJob's current jobs before exiting..."
72
61
  scheduler.shutdown
73
- $stdout.puts "GoodJob's jobs finished, exiting..."
74
62
  end
75
63
 
64
+ default_task :start
65
+
76
66
  desc :cleanup_preserved_jobs, "Delete preserved job records"
77
67
  method_option :before_seconds_ago,
78
68
  type: :numeric,
79
69
  default: 24 * 60 * 60,
80
70
  desc: "Delete records finished more than this many seconds ago"
81
71
  def cleanup_preserved_jobs
82
- require RAILS_ENVIRONMENT_RB
72
+ set_up_application!
83
73
 
84
74
  timestamp = Time.current - options[:before_seconds_ago]
85
- result = GoodJob::Job.finished(timestamp).delete_all
86
- $stdout.puts "Deleted #{result} preserved #{'job'.pluralize(result)} finished before #{timestamp}."
75
+ ActiveSupport::Notifications.instrument("cleanup_preserved_jobs.good_job", { before_seconds_ago: options[:before_seconds_ago], timestamp: timestamp }) do |payload|
76
+ deleted_records_count = GoodJob::Job.finished(timestamp).delete_all
77
+
78
+ payload[:deleted_records_count] = deleted_records_count
79
+ end
87
80
  end
88
81
 
89
- default_task :start
82
+ no_commands do
83
+ def set_up_application!
84
+ require RAILS_ENVIRONMENT_RB
85
+ end
86
+ end
90
87
  end
91
88
  end
@@ -18,32 +18,28 @@ module GoodJob
18
18
  end
19
19
  end)
20
20
  scope :only_scheduled, -> { where(arel_table['scheduled_at'].lteq(Time.current)).or(where(scheduled_at: nil)) }
21
- scope :priority_ordered, -> { order(priority: :desc) }
21
+ scope :priority_ordered, -> { order('priority DESC NULLS LAST') }
22
22
  scope :finished, ->(timestamp = nil) { timestamp ? where(arel_table['finished_at'].lteq(timestamp)) : where.not(finished_at: nil) }
23
+ scope :queue_string, (lambda do |string|
24
+ queue_names_without_all = (string.presence || '*').split(',').map(&:strip).reject { |q| q == '*' }
25
+ where(queue_name: queue_names_without_all) unless queue_names_without_all.size.zero?
26
+ end)
23
27
 
24
- def self.perform_with_advisory_lock(destroy_after: !GoodJob.preserve_job_records)
28
+ def self.perform_with_advisory_lock
25
29
  good_job = nil
26
30
  result = nil
27
31
  error = nil
28
32
 
29
- unfinished.only_scheduled.limit(1).with_advisory_lock do |good_jobs|
33
+ unfinished.priority_ordered.only_scheduled.limit(1).with_advisory_lock do |good_jobs|
30
34
  good_job = good_jobs.first
31
35
  break unless good_job
32
36
 
33
- result, error = good_job.perform(destroy_after: destroy_after)
37
+ result, error = good_job.perform
34
38
  end
35
39
 
36
40
  [good_job, result, error] if good_job
37
41
  end
38
42
 
39
- def self.perform_with_advisory_lock_and_preserve_job_records
40
- perform_with_advisory_lock(destroy_after: false)
41
- end
42
-
43
- def self.perform_with_advisory_lock_and_destroy_job_records
44
- perform_with_advisory_lock(destroy_after: true)
45
- end
46
-
47
43
  def self.enqueue(active_job, scheduled_at: nil, create_with_advisory_lock: false)
48
44
  good_job = nil
49
45
  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|
@@ -64,40 +60,49 @@ module GoodJob
64
60
  good_job
65
61
  end
66
62
 
67
- def perform(destroy_after: true)
63
+ def perform(destroy_after: !GoodJob.preserve_job_records, reperform_on_standard_error: GoodJob.reperform_jobs_on_standard_error)
68
64
  raise PreviouslyPerformedError, 'Cannot perform a job that has already been performed' if finished_at
69
65
 
70
66
  result = nil
67
+ rescued_error = nil
71
68
  error = nil
72
69
 
73
70
  ActiveSupport::Notifications.instrument("before_perform_job.good_job", { good_job: self })
74
71
  self.performed_at = Time.current
75
72
  save! unless destroy_after
76
73
 
77
- ActiveSupport::Notifications.instrument("perform_job.good_job", { good_job: self }) do
78
- params = serialized_params.merge(
79
- "provider_job_id" => id
80
- )
81
- begin
74
+ params = serialized_params.merge(
75
+ "provider_job_id" => id
76
+ )
77
+
78
+ begin
79
+ ActiveSupport::Notifications.instrument("perform_job.good_job", { good_job: self }) do
82
80
  result = ActiveJob::Base.execute(params)
83
- rescue StandardError => e
84
- error = e
85
81
  end
82
+ rescue StandardError => e
83
+ rescued_error = e
86
84
  end
87
85
 
88
- if error.nil? && result.is_a?(Exception)
86
+ if rescued_error
87
+ error = rescued_error
88
+ elsif result.is_a?(Exception)
89
89
  error = result
90
90
  result = nil
91
91
  end
92
92
 
93
93
  error_message = "#{error.class}: #{error.message}" if error
94
94
  self.error = error_message
95
- self.finished_at = Time.current
96
95
 
97
- if destroy_after
98
- destroy!
99
- else
96
+ if rescued_error && reperform_on_standard_error
100
97
  save!
98
+ else
99
+ self.finished_at = Time.current
100
+
101
+ if destroy_after
102
+ destroy!
103
+ else
104
+ save!
105
+ end
101
106
  end
102
107
 
103
108
  [result, error]
@@ -0,0 +1,110 @@
1
+ module GoodJob
2
+ class LogSubscriber < ActiveSupport::LogSubscriber
3
+ def create(event)
4
+ good_job = event.payload[:good_job]
5
+
6
+ debug do
7
+ "GoodJob created job resource with id #{good_job.id}"
8
+ end
9
+ end
10
+
11
+ def timer_task_finished(event)
12
+ exception = event.payload[:error]
13
+ return unless exception
14
+
15
+ error do
16
+ "GoodJob error: #{exception}\n #{exception.backtrace}"
17
+ end
18
+ end
19
+
20
+ def job_finished(event)
21
+ exception = event.payload[:error]
22
+ return unless exception
23
+
24
+ error do
25
+ "GoodJob error: #{exception}\n #{exception.backtrace}"
26
+ end
27
+ end
28
+
29
+ def scheduler_create_pools(event)
30
+ max_threads = event.payload[:max_threads]
31
+ poll_interval = event.payload[:poll_interval]
32
+ performer_name = event.payload[:performer_name]
33
+ process_id = event.payload[:process_id]
34
+
35
+ info_and_stdout(tags: [process_id]) do
36
+ "GoodJob started scheduler with queues=#{performer_name} max_threads=#{max_threads} poll_interval=#{poll_interval}."
37
+ end
38
+ end
39
+
40
+ def scheduler_shutdown_start(event)
41
+ process_id = event.payload[:process_id]
42
+
43
+ info_and_stdout(tags: [process_id]) do
44
+ "GoodJob shutting down scheduler..."
45
+ end
46
+ end
47
+
48
+ def scheduler_shutdown(event)
49
+ process_id = event.payload[:process_id]
50
+
51
+ info_and_stdout(tags: [process_id]) do
52
+ "GoodJob scheduler is shut down."
53
+ end
54
+ end
55
+
56
+ def scheduler_restart_pools(event)
57
+ process_id = event.payload[:process_id]
58
+
59
+ info_and_stdout(tags: [process_id]) do
60
+ "GoodJob scheduler has restarted."
61
+ end
62
+ end
63
+
64
+ def cleanup_preserved_jobs(event)
65
+ timestamp = event.payload[:timestamp]
66
+ deleted_records_count = event.payload[:deleted_records_count]
67
+
68
+ info_and_stdout do
69
+ "GoodJob deleted #{deleted_records_count} preserved #{'job'.pluralize(deleted_records_count)} finished before #{timestamp}."
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def logger
76
+ GoodJob.logger
77
+ end
78
+
79
+ %w(info debug warn error fatal unknown).each do |level|
80
+ class_eval <<-METHOD, __FILE__, __LINE__ + 1
81
+ def #{level}(progname = nil, tags: [], &block)
82
+ return unless logger
83
+
84
+ if logger.respond_to?(:tagged)
85
+ tags.unshift "GoodJob" unless logger.formatter.current_tags.include?("GoodJob")
86
+ logger.tagged(*tags.compact) do
87
+ logger.#{level}(progname, &block)
88
+ end
89
+ else
90
+ logger.#{level}(progname, &block)
91
+ end
92
+ end
93
+ METHOD
94
+ end
95
+
96
+ def info_and_stdout(progname = nil, tags: [], &block)
97
+ unless ActiveSupport::Logger.logger_outputs_to?(logger, STDOUT)
98
+ tags_string = (['GoodJob'] + tags).map { |t| "[#{t}]" }.join(' ')
99
+ stdout_message = "#{tags_string}#{yield}"
100
+ $stdout.puts stdout_message
101
+ end
102
+
103
+ info(progname, tags: [], &block)
104
+ end
105
+
106
+ def thread_name
107
+ Thread.current.name || Thread.current.object_id
108
+ end
109
+ end
110
+ end
@@ -1,8 +1,11 @@
1
1
  module GoodJob
2
2
  class Performer
3
- def initialize(target, method_name)
3
+ attr_reader :name
4
+
5
+ def initialize(target, method_name, name: nil)
4
6
  @target = target
5
7
  @method_name = method_name
8
+ @name = name
6
9
  end
7
10
 
8
11
  def next
@@ -2,6 +2,7 @@ module GoodJob
2
2
  class Railtie < ::Rails::Railtie
3
3
  initializer "good_job.logger" do
4
4
  ActiveSupport.on_load(:good_job) { self.logger = ::Rails.logger }
5
+ GoodJob::LogSubscriber.attach_to :good_job
5
6
  end
6
7
  end
7
8
  end
@@ -15,38 +15,36 @@ module GoodJob
15
15
  min_threads: 0,
16
16
  max_threads: Concurrent.processor_count,
17
17
  auto_terminate: true,
18
- idletime: 0,
19
- max_queue: 0,
20
- fallback_policy: :abort, # shouldn't matter -- 0 max queue
18
+ idletime: 60,
19
+ max_queue: -1,
20
+ fallback_policy: :discard,
21
21
  }.freeze
22
22
 
23
+ cattr_reader :instances, default: [], instance_reader: false
24
+
23
25
  def initialize(performer, timer_options: {}, pool_options: {})
24
26
  raise ArgumentError, "Performer argument must implement #next" unless performer.respond_to?(:next)
25
27
 
28
+ self.class.instances << self
29
+
26
30
  @performer = performer
27
- @pool = Concurrent::ThreadPoolExecutor.new(DEFAULT_POOL_OPTIONS.merge(pool_options))
28
- @timer = Concurrent::TimerTask.new(DEFAULT_TIMER_OPTIONS.merge(timer_options)) do
29
- idle_threads = @pool.max_length - @pool.length
30
- create_thread if idle_threads.positive?
31
- end
32
- @timer.add_observer(self, :timer_observer)
33
- @timer.execute
34
- end
31
+ @pool_options = DEFAULT_POOL_OPTIONS.merge(pool_options)
32
+ @timer_options = DEFAULT_TIMER_OPTIONS.merge(timer_options)
35
33
 
36
- def execute
34
+ create_pools
37
35
  end
38
36
 
39
37
  def shutdown(wait: true)
40
38
  @_shutdown = true
41
39
 
42
- ActiveSupport::Notifications.instrument("scheduler_start_shutdown.good_job", { wait: wait })
43
- ActiveSupport::Notifications.instrument("scheduler_shutdown.good_job", { wait: wait }) do
44
- if @timer.running?
40
+ ActiveSupport::Notifications.instrument("scheduler_shutdown_start.good_job", { wait: wait, process_id: process_id })
41
+ ActiveSupport::Notifications.instrument("scheduler_shutdown.good_job", { wait: wait, process_id: process_id }) do
42
+ if @timer&.running?
45
43
  @timer.shutdown
46
44
  @timer.wait_for_termination if wait
47
45
  end
48
46
 
49
- if @pool.running?
47
+ if @pool&.running?
50
48
  @pool.shutdown
51
49
  @pool.wait_for_termination if wait
52
50
  end
@@ -57,7 +55,16 @@ module GoodJob
57
55
  @_shutdown
58
56
  end
59
57
 
58
+ def restart(wait: true)
59
+ ActiveSupport::Notifications.instrument("scheduler_restart_pools.good_job", { process_id: process_id }) do
60
+ shutdown(wait: wait) unless shutdown?
61
+ create_pools
62
+ end
63
+ end
64
+
60
65
  def create_thread
66
+ return false unless @pool.ready_worker_count.positive?
67
+
61
68
  future = Concurrent::Future.new(args: [@performer], executor: @pool) do |performer|
62
69
  output = nil
63
70
  Rails.application.executor.wrap { output = performer.next }
@@ -68,12 +75,47 @@ module GoodJob
68
75
  end
69
76
 
70
77
  def timer_observer(time, executed_task, thread_error)
78
+ GoodJob.on_thread_error.call(thread_error) if thread_error && GoodJob.on_thread_error.respond_to?(:call)
71
79
  ActiveSupport::Notifications.instrument("finished_timer_task.good_job", { result: executed_task, error: thread_error, time: time })
72
80
  end
73
81
 
74
82
  def task_observer(time, output, thread_error)
83
+ GoodJob.on_thread_error.call(thread_error) if thread_error && GoodJob.on_thread_error.respond_to?(:call)
75
84
  ActiveSupport::Notifications.instrument("finished_job_task.good_job", { result: output, error: thread_error, time: time })
76
85
  create_thread if output
77
86
  end
87
+
88
+ class ThreadPoolExecutor < Concurrent::ThreadPoolExecutor
89
+ # https://github.com/ruby-concurrency/concurrent-ruby/issues/684#issuecomment-427594437
90
+ def ready_worker_count
91
+ synchronize do
92
+ workers_still_to_be_created = @max_length - @pool.length
93
+ workers_created_but_waiting = @ready.length
94
+
95
+ workers_still_to_be_created + workers_created_but_waiting
96
+ end
97
+ end
98
+ end
99
+
100
+ private
101
+
102
+ def create_pools
103
+ ActiveSupport::Notifications.instrument("scheduler_create_pools.good_job", { performer_name: @performer.name, max_threads: @pool_options[:max_threads], poll_interval: @timer_options[:execution_interval], process_id: process_id }) do
104
+ @pool = ThreadPoolExecutor.new(@pool_options)
105
+ next unless @timer_options[:execution_interval].positive?
106
+
107
+ @timer = Concurrent::TimerTask.new(@timer_options) { create_thread }
108
+ @timer.add_observer(self, :timer_observer)
109
+ @timer.execute
110
+ end
111
+ end
112
+
113
+ def process_id
114
+ Process.pid
115
+ end
116
+
117
+ def thread_name
118
+ Thread.current.name || Thread.current.object_id
119
+ end
78
120
  end
79
121
  end
@@ -1,3 +1,3 @@
1
1
  module GoodJob
2
- VERSION = '0.9.0'.freeze
2
+ VERSION = '1.1.0'.freeze
3
3
  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: 0.9.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Sheldon
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-07-20 00:00:00.000000000 Z
11
+ date: 2020-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: dotenv
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: foreman
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -150,6 +164,20 @@ dependencies:
150
164
  - - ">="
151
165
  - !ruby/object:Gem::Version
152
166
  version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: puma
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
153
181
  - !ruby/object:Gem::Dependency
154
182
  name: rspec-rails
155
183
  requirement: !ruby/object:Gem::Requirement
@@ -236,12 +264,14 @@ files:
236
264
  - README.md
237
265
  - exe/good_job
238
266
  - lib/active_job/queue_adapters/good_job_adapter.rb
267
+ - lib/generators/good_job/install_generator.rb
268
+ - lib/generators/good_job/templates/migration.rb.erb
239
269
  - lib/good_job.rb
240
270
  - lib/good_job/adapter.rb
241
271
  - lib/good_job/cli.rb
242
272
  - lib/good_job/job.rb
243
273
  - lib/good_job/lockable.rb
244
- - lib/good_job/logging.rb
274
+ - lib/good_job/log_subscriber.rb
245
275
  - lib/good_job/performer.rb
246
276
  - lib/good_job/pg_locks.rb
247
277
  - lib/good_job/railtie.rb
@@ -256,7 +286,7 @@ metadata:
256
286
  documentation_uri: https://rdoc.info/github/bensheldon/good_job
257
287
  homepage_uri: https://github.com/bensheldon/good_job
258
288
  source_code_uri: https://github.com/bensheldon/good_job
259
- post_install_message:
289
+ post_install_message:
260
290
  rdoc_options:
261
291
  - "--title"
262
292
  - GoodJob - a multithreaded, Postgres-based ActiveJob backend for Ruby on Rails
@@ -279,7 +309,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
279
309
  version: '0'
280
310
  requirements: []
281
311
  rubygems_version: 3.0.3
282
- signing_key:
312
+ signing_key:
283
313
  specification_version: 4
284
314
  summary: A multithreaded, Postgres-based ActiveJob backend for Ruby on Rails
285
315
  test_files: []
@@ -1,70 +0,0 @@
1
- module GoodJob
2
- module Logging
3
- extend ActiveSupport::Concern
4
-
5
- included do
6
- cattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
7
-
8
- def self.tag_logger(*tags)
9
- if logger.respond_to?(:tagged)
10
- tags.unshift "GoodJob" unless logger.formatter.current_tags.include?("GoodJob")
11
- logger.tagged(*tags) { yield }
12
- else
13
- yield
14
- end
15
- end
16
- end
17
-
18
- class LogSubscriber < ActiveSupport::LogSubscriber
19
- def create(event)
20
- good_job = event.payload[:good_job]
21
-
22
- info do
23
- "Created GoodJob resource with id #{good_job.id}"
24
- end
25
- end
26
-
27
- def timer_task_finished(event)
28
- exception = event.payload[:error]
29
- return unless exception
30
-
31
- error do
32
- "ERROR: #{exception}\n #{exception.backtrace}"
33
- end
34
- end
35
-
36
- def job_finished(event)
37
- exception = event.payload[:error]
38
- return unless exception
39
-
40
- error do
41
- "ERROR: #{exception}\n #{exception.backtrace}"
42
- end
43
- end
44
-
45
- def scheduler_start_shutdown(_event)
46
- info do
47
- "Shutting down scheduler..."
48
- end
49
- end
50
-
51
- def scheduler_shutdown(_event)
52
- info do
53
- "Scheduler is shut down."
54
- end
55
- end
56
-
57
- private
58
-
59
- def logger
60
- GoodJob.logger
61
- end
62
-
63
- def thread_name
64
- Thread.current.name || Thread.current.object_id
65
- end
66
- end
67
- end
68
- end
69
-
70
- GoodJob::Logging::LogSubscriber.attach_to :good_job