good_job 1.1.3 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +83 -1
- data/README.md +22 -4
- data/exe/good_job +1 -0
- data/lib/good_job.rb +50 -1
- data/lib/good_job/adapter.rb +18 -12
- data/lib/good_job/cli.rb +11 -3
- data/lib/good_job/current_execution.rb +12 -0
- data/lib/good_job/job.rb +31 -17
- data/lib/good_job/lockable.rb +8 -10
- data/lib/good_job/log_subscriber.rb +84 -27
- data/lib/good_job/multi_scheduler.rb +11 -2
- data/lib/good_job/notifier.rb +127 -0
- data/lib/good_job/performer.rb +8 -1
- data/lib/good_job/scheduler.rb +41 -20
- data/lib/good_job/version.rb +1 -1
- metadata +61 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9ea00429e2b1515df6973f66c81d1827acdb4528e39ac811ece90a69fbd0b4f6
|
4
|
+
data.tar.gz: 9d3c7e104fda0d102789fc97fb716e5372c6e746d169fb521b84ecf13312ce24
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7f34e2681a4642c9c337dd76253950a46d1edbff21feb6940eace1fa99b63d3d919f1eb3b2ae56b90d06d0697229c47ac581998be8615abc594c308255325fb1
|
7
|
+
data.tar.gz: fc69543344585cbfc7cb7608cce5884526481cbf75474a70d2ddea5737ff1eaea722743ad1508e683a2bbeda91e4399d25c8b1294cb3a763d1436b36ece74b77
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,84 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v1.2.3](https://github.com/bensheldon/good_job/tree/v1.2.3) (2020-08-27)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.2.2...v1.2.3)
|
6
|
+
|
7
|
+
**Closed issues:**
|
8
|
+
|
9
|
+
- requiring more dependencies in then needed [\#103](https://github.com/bensheldon/good_job/issues/103)
|
10
|
+
|
11
|
+
**Merged pull requests:**
|
12
|
+
|
13
|
+
- stop depending on all rails libs [\#104](https://github.com/bensheldon/good_job/pull/104) ([thilo](https://github.com/thilo))
|
14
|
+
|
15
|
+
## [v1.2.2](https://github.com/bensheldon/good_job/tree/v1.2.2) (2020-08-27)
|
16
|
+
|
17
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.2.1...v1.2.2)
|
18
|
+
|
19
|
+
**Implemented enhancements:**
|
20
|
+
|
21
|
+
- Run Github Action tests against Ruby 2.5, 2.6, 2.7 [\#100](https://github.com/bensheldon/good_job/issues/100)
|
22
|
+
|
23
|
+
**Fixed bugs:**
|
24
|
+
|
25
|
+
- Freezes puma on code change [\#95](https://github.com/bensheldon/good_job/issues/95)
|
26
|
+
- Ruby 2.7 keyword arguments warning [\#93](https://github.com/bensheldon/good_job/issues/93)
|
27
|
+
|
28
|
+
**Closed issues:**
|
29
|
+
|
30
|
+
- Add test for `rails g good\_job:install` [\#57](https://github.com/bensheldon/good_job/issues/57)
|
31
|
+
|
32
|
+
**Merged pull requests:**
|
33
|
+
|
34
|
+
- Use more ActiveRecord in Lockable and not connection.execute [\#102](https://github.com/bensheldon/good_job/pull/102) ([bensheldon](https://github.com/bensheldon))
|
35
|
+
- 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))
|
36
|
+
- Return to using executor.wrap around Scheduler execution task [\#99](https://github.com/bensheldon/good_job/pull/99) ([bensheldon](https://github.com/bensheldon))
|
37
|
+
- Fix Ruby 2.7 keyword arguments warning [\#98](https://github.com/bensheldon/good_job/pull/98) ([arku](https://github.com/arku))
|
38
|
+
- Remove executor/reloader for less interlocking [\#97](https://github.com/bensheldon/good_job/pull/97) ([sj26](https://github.com/sj26))
|
39
|
+
- Name the thread pools [\#96](https://github.com/bensheldon/good_job/pull/96) ([sj26](https://github.com/sj26))
|
40
|
+
- Add test for `rails g good\_job:install` [\#94](https://github.com/bensheldon/good_job/pull/94) ([arku](https://github.com/arku))
|
41
|
+
|
42
|
+
## [v1.2.1](https://github.com/bensheldon/good_job/tree/v1.2.1) (2020-08-21)
|
43
|
+
|
44
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.2.0...v1.2.1)
|
45
|
+
|
46
|
+
**Fixed bugs:**
|
47
|
+
|
48
|
+
- undefined method `thread\_mattr\_accessor' when not requiring the Sprockets Railstie [\#85](https://github.com/bensheldon/good_job/issues/85)
|
49
|
+
|
50
|
+
**Closed issues:**
|
51
|
+
|
52
|
+
- Document comparison of GoodJob with other backends [\#51](https://github.com/bensheldon/good_job/issues/51)
|
53
|
+
|
54
|
+
**Merged pull requests:**
|
55
|
+
|
56
|
+
- Explicitly require thread\_mattr\_accessor from ActiveSupport [\#86](https://github.com/bensheldon/good_job/pull/86) ([bensheldon](https://github.com/bensheldon))
|
57
|
+
- Add comparison of other backends to Readme [\#84](https://github.com/bensheldon/good_job/pull/84) ([bensheldon](https://github.com/bensheldon))
|
58
|
+
|
59
|
+
## [v1.2.0](https://github.com/bensheldon/good_job/tree/v1.2.0) (2020-08-20)
|
60
|
+
|
61
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.1.4...v1.2.0)
|
62
|
+
|
63
|
+
**Merged pull requests:**
|
64
|
+
|
65
|
+
- Document GoodJob module [\#83](https://github.com/bensheldon/good_job/pull/83) ([bensheldon](https://github.com/bensheldon))
|
66
|
+
|
67
|
+
## [v1.1.4](https://github.com/bensheldon/good_job/tree/v1.1.4) (2020-08-19)
|
68
|
+
|
69
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.1.3...v1.1.4)
|
70
|
+
|
71
|
+
**Implemented enhancements:**
|
72
|
+
|
73
|
+
- Explicitly name threads for easier debugging [\#64](https://github.com/bensheldon/good_job/issues/64)
|
74
|
+
- Investigate Listen/Notify as alternative to polling [\#54](https://github.com/bensheldon/good_job/issues/54)
|
75
|
+
|
76
|
+
**Merged pull requests:**
|
77
|
+
|
78
|
+
- Add Postgres LISTEN/NOTIFY support [\#82](https://github.com/bensheldon/good_job/pull/82) ([bensheldon](https://github.com/bensheldon))
|
79
|
+
- Allow Schedulers to filter \#create\_thread to avoid flood of queries when running async with multiple schedulers [\#81](https://github.com/bensheldon/good_job/pull/81) ([bensheldon](https://github.com/bensheldon))
|
80
|
+
- Fully name scheduler threadpools and thread names; refactor CLI STDOUT [\#80](https://github.com/bensheldon/good_job/pull/80) ([bensheldon](https://github.com/bensheldon))
|
81
|
+
|
3
82
|
## [v1.1.3](https://github.com/bensheldon/good_job/tree/v1.1.3) (2020-08-14)
|
4
83
|
|
5
84
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.1.2...v1.1.3)
|
@@ -73,7 +152,6 @@
|
|
73
152
|
|
74
153
|
- 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))
|
75
154
|
- Update the setup documentation to use correct bin setup command [\#61](https://github.com/bensheldon/good_job/pull/61) ([jm96441n](https://github.com/jm96441n))
|
76
|
-
- Allow preservation of finished job records [\#46](https://github.com/bensheldon/good_job/pull/46) ([bensheldon](https://github.com/bensheldon))
|
77
155
|
|
78
156
|
## [v1.0.2](https://github.com/bensheldon/good_job/tree/v1.0.2) (2020-07-25)
|
79
157
|
|
@@ -104,6 +182,10 @@
|
|
104
182
|
|
105
183
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v0.8.2...v0.9.0)
|
106
184
|
|
185
|
+
**Merged pull requests:**
|
186
|
+
|
187
|
+
- Allow preservation of finished job records [\#46](https://github.com/bensheldon/good_job/pull/46) ([bensheldon](https://github.com/bensheldon))
|
188
|
+
|
107
189
|
## [v0.8.2](https://github.com/bensheldon/good_job/tree/v0.8.2) (2020-07-18)
|
108
190
|
|
109
191
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v0.8.1...v0.8.2)
|
data/README.md
CHANGED
@@ -6,11 +6,24 @@ GoodJob is a multithreaded, Postgres-based, ActiveJob backend for Ruby on Rails.
|
|
6
6
|
|
7
7
|
- **Designed for ActiveJob.** Complete support for [async, queues, delays, priorities, timeouts, and retries](https://edgeguides.rubyonrails.org/active_job_basics.html) with near-zero configuration.
|
8
8
|
- **Built for Rails.** Fully adopts Ruby on Rails [threading and code execution guidelines](https://guides.rubyonrails.org/threading_and_code_execution.html) with [Concurrent::Ruby](https://github.com/ruby-concurrency/concurrent-ruby).
|
9
|
-
- **Backed by Postgres.** Relies upon Postgres integrity
|
9
|
+
- **Backed by Postgres.** Relies upon Postgres integrity, session-level Advisory Locks to provide run-once safety and stay within the limits of `schema.rb`, and LISTEN/NOTIFY to reduce queuing latency.
|
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
12
|
For more of the story of GoodJob, read the [introductory blog post](https://island94.org/2020/07/introducing-goodjob-1-0).
|
13
13
|
|
14
|
+
<details>
|
15
|
+
<summary><strong>📊 Comparison of GoodJob with other job queue backends (click to expand)</strong></summary>
|
16
|
+
|
17
|
+
| | Queues, priority, retries | Database | Concurrency | Reliability/Integrity | Latency |
|
18
|
+
|-----------------|---------------------------|---------------------------------------|-------------------|------------------------|--------------------------|
|
19
|
+
| **GoodJob** | ✅ Yes | ✅ Postgres | ✅ Multithreaded | ✅ ACID, Advisory Locks | ✅ Postgres LISTEN/NOTIFY |
|
20
|
+
| **Que** | ✅ Yes | 🟨 Postgres, requires `structure.sql` | ✅ Multithreaded | ✅ ACID, Advisory Locks | ✅ Postgres LISTEN/NOTIFY |
|
21
|
+
| **Delayed Job** | ✅ Yes | ✅ Postgres | 🟥 Single-threaded | ✅ ACID, record-based | 🟨 Polling |
|
22
|
+
| **Sidekiq** | ✅ Yes | 🟥 Redis | ✅ Multithreaded | 🟥 Crashes lose jobs | ✅ Redis BRPOP |
|
23
|
+
| **Sidekiq Pro** | ✅ Yes | 🟥 Redis | ✅ Multithreaded | ✅ Redis RPOPLPUSH | ✅ Redis RPOPLPUSH |
|
24
|
+
|
25
|
+
</details>
|
26
|
+
|
14
27
|
## Installation
|
15
28
|
|
16
29
|
Add this line to your application's Gemfile:
|
@@ -284,15 +297,20 @@ Depending on your application configuration, you may need to take additional ste
|
|
284
297
|
# config/puma.rb
|
285
298
|
|
286
299
|
before_fork do
|
287
|
-
GoodJob
|
300
|
+
GoodJob.shutdown
|
288
301
|
end
|
289
302
|
|
290
303
|
on_worker_boot do
|
291
|
-
GoodJob
|
304
|
+
GoodJob.restart
|
292
305
|
end
|
293
306
|
|
294
307
|
on_worker_shutdown do
|
295
|
-
GoodJob
|
308
|
+
GoodJob.shutdown
|
309
|
+
end
|
310
|
+
|
311
|
+
MAIN_PID = Process.pid
|
312
|
+
at_exit do
|
313
|
+
GoodJob.shutdown if Process.pid == MAIN_PID
|
296
314
|
end
|
297
315
|
```
|
298
316
|
|
data/exe/good_job
CHANGED
data/lib/good_job.rb
CHANGED
@@ -11,14 +11,63 @@ require 'good_job/adapter'
|
|
11
11
|
require 'good_job/pg_locks'
|
12
12
|
require 'good_job/performer'
|
13
13
|
require 'good_job/current_execution'
|
14
|
+
require 'good_job/notifier'
|
14
15
|
|
15
16
|
require 'active_job/queue_adapters/good_job_adapter'
|
16
17
|
|
18
|
+
# GoodJob is a multithreaded, Postgres-based, ActiveJob backend for Ruby on Rails.
|
19
|
+
#
|
20
|
+
# +GoodJob+ is the top-level namespace and exposes configuration attributes.
|
17
21
|
module GoodJob
|
18
|
-
|
22
|
+
# @!attribute [rw] logger
|
23
|
+
# @!scope class
|
24
|
+
# The logger used by GoodJob
|
25
|
+
# @return [Logger]
|
26
|
+
mattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
|
27
|
+
|
28
|
+
# @!attribute [rw] preserve_job_records
|
29
|
+
# @!scope class
|
30
|
+
# Whether to preserve job records in the database after they have finished for inspection
|
31
|
+
# @return [Boolean]
|
19
32
|
mattr_accessor :preserve_job_records, default: false
|
33
|
+
|
34
|
+
# @!attribute [rw] reperform_jobs_on_standard_error
|
35
|
+
# @!scope class
|
36
|
+
# Whether to re-perform a job when a type of +StandardError+ is raised and bubbles up to the GoodJob backend
|
37
|
+
# @return [Boolean]
|
20
38
|
mattr_accessor :reperform_jobs_on_standard_error, default: true
|
39
|
+
|
40
|
+
# @!attribute [rw] on_thread_error
|
41
|
+
# @!scope class
|
42
|
+
# Called when a thread raises an error
|
43
|
+
# @example Send errors to Sentry
|
44
|
+
# # config/initializers/good_job.rb
|
45
|
+
#
|
46
|
+
# # With Sentry (or Bugsnag, Airbrake, Honeybadger, etc.)
|
47
|
+
# GoodJob.on_thread_error = -> (exception) { Raven.capture_exception(exception) }
|
48
|
+
# @return [#call, nil]
|
21
49
|
mattr_accessor :on_thread_error, default: nil
|
22
50
|
|
51
|
+
# Shuts down all execution pools
|
52
|
+
# @param wait [Boolean] whether to wait for shutdown
|
53
|
+
# @return [void]
|
54
|
+
def self.shutdown(wait: true)
|
55
|
+
Notifier.instances.each { |adapter| adapter.shutdown(wait: wait) }
|
56
|
+
Scheduler.instances.each { |scheduler| scheduler.shutdown(wait: wait) }
|
57
|
+
end
|
58
|
+
|
59
|
+
# Tests if execution pools are shut down
|
60
|
+
# @return [Boolean] whether execution pools are shut down
|
61
|
+
def self.shutdown?
|
62
|
+
Notifier.instances.all?(&:shutdown?) && Scheduler.instances.all?(&:shutdown?)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Restarts all execution pools
|
66
|
+
# @return [void]
|
67
|
+
def self.restart
|
68
|
+
Notifier.instances.each(&:restart)
|
69
|
+
Scheduler.instances.each(&:restart)
|
70
|
+
end
|
71
|
+
|
23
72
|
ActiveSupport.run_load_hooks(:good_job, self)
|
24
73
|
end
|
data/lib/good_job/adapter.rb
CHANGED
@@ -2,25 +2,29 @@ module GoodJob
|
|
2
2
|
class Adapter
|
3
3
|
EXECUTION_MODES = [:async, :external, :inline].freeze
|
4
4
|
|
5
|
-
def initialize(execution_mode: nil, max_threads: nil, poll_interval: nil, scheduler: nil, inline: false)
|
5
|
+
def initialize(execution_mode: nil, queues: nil, max_threads: nil, poll_interval: nil, scheduler: nil, notifier: nil, inline: false)
|
6
6
|
if inline && execution_mode.nil?
|
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
|
9
9
|
end
|
10
10
|
|
11
|
-
configuration = GoodJob::Configuration.new(
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
11
|
+
configuration = GoodJob::Configuration.new(
|
12
|
+
{
|
13
|
+
execution_mode: execution_mode,
|
14
|
+
queues: queues,
|
15
|
+
max_threads: max_threads,
|
16
|
+
poll_interval: poll_interval,
|
17
|
+
}
|
18
|
+
)
|
19
19
|
|
20
20
|
@execution_mode = configuration.execution_mode
|
21
|
+
raise ArgumentError, "execution_mode: must be one of #{EXECUTION_MODES.join(', ')}." unless EXECUTION_MODES.include?(@execution_mode)
|
21
22
|
|
22
|
-
@
|
23
|
-
|
23
|
+
if @execution_mode == :async # rubocop:disable Style/GuardClause
|
24
|
+
@notifier = notifier || GoodJob::Notifier.new
|
25
|
+
@scheduler = scheduler || GoodJob::Scheduler.from_configuration(configuration)
|
26
|
+
@notifier.recipients << [@scheduler, :create_thread]
|
27
|
+
end
|
24
28
|
end
|
25
29
|
|
26
30
|
def enqueue(active_job)
|
@@ -42,12 +46,14 @@ module GoodJob
|
|
42
46
|
end
|
43
47
|
end
|
44
48
|
|
45
|
-
@scheduler.create_thread
|
49
|
+
executed_locally = execute_async? && @scheduler.create_thread(queue_name: good_job.queue_name)
|
50
|
+
Notifier.notify(queue_name: good_job.queue_name) unless executed_locally
|
46
51
|
|
47
52
|
good_job
|
48
53
|
end
|
49
54
|
|
50
55
|
def shutdown(wait: true)
|
56
|
+
@notifier&.shutdown(wait: wait)
|
51
57
|
@scheduler&.shutdown(wait: wait)
|
52
58
|
end
|
53
59
|
|
data/lib/good_job/cli.rb
CHANGED
@@ -18,8 +18,10 @@ module GoodJob
|
|
18
18
|
def start
|
19
19
|
set_up_application!
|
20
20
|
|
21
|
-
|
22
|
-
|
21
|
+
notifier = GoodJob::Notifier.new
|
22
|
+
configuration = GoodJob::Configuration.new(options)
|
23
|
+
scheduler = GoodJob::Scheduler.from_configuration(configuration)
|
24
|
+
notifier.recipients << [scheduler, :create_thread]
|
23
25
|
|
24
26
|
@stop_good_job_executable = false
|
25
27
|
%w[INT TERM].each do |signal|
|
@@ -28,9 +30,10 @@ module GoodJob
|
|
28
30
|
|
29
31
|
Kernel.loop do
|
30
32
|
sleep 0.1
|
31
|
-
break if @stop_good_job_executable || scheduler.shutdown?
|
33
|
+
break if @stop_good_job_executable || scheduler.shutdown? || notifier.shutdown?
|
32
34
|
end
|
33
35
|
|
36
|
+
notifier.shutdown
|
34
37
|
scheduler.shutdown
|
35
38
|
end
|
36
39
|
|
@@ -41,6 +44,7 @@ module GoodJob
|
|
41
44
|
type: :numeric,
|
42
45
|
default: 24 * 60 * 60,
|
43
46
|
desc: "Delete records finished more than this many seconds ago"
|
47
|
+
|
44
48
|
def cleanup_preserved_jobs
|
45
49
|
set_up_application!
|
46
50
|
|
@@ -55,6 +59,10 @@ module GoodJob
|
|
55
59
|
no_commands do
|
56
60
|
def set_up_application!
|
57
61
|
require RAILS_ENVIRONMENT_RB
|
62
|
+
return unless defined?(GOOD_JOB_LOG_TO_STDOUT) && GOOD_JOB_LOG_TO_STDOUT && !ActiveSupport::Logger.logger_outputs_to?(GoodJob.logger, STDOUT)
|
63
|
+
|
64
|
+
GoodJob::LogSubscriber.loggers << ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
|
65
|
+
GoodJob::LogSubscriber.reset_logger
|
58
66
|
end
|
59
67
|
end
|
60
68
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'active_support/core_ext/module/attribute_accessors_per_thread'
|
2
|
+
|
1
3
|
module GoodJob
|
2
4
|
# Thread-local attributes for passing values from Instrumentation.
|
3
5
|
# (Cannot use ActiveSupport::CurrentAttributes because ActiveJob resets it)
|
@@ -21,5 +23,15 @@ module GoodJob
|
|
21
23
|
self.error_on_retry = nil
|
22
24
|
self.error_on_discard = nil
|
23
25
|
end
|
26
|
+
|
27
|
+
# @return [Integer] Current process ID
|
28
|
+
def self.process_id
|
29
|
+
Process.pid
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [String] Current thread name
|
33
|
+
def self.thread_name
|
34
|
+
(Thread.current.name || Thread.current.object_id).to_s
|
35
|
+
end
|
24
36
|
end
|
25
37
|
end
|
data/lib/good_job/job.rb
CHANGED
@@ -9,6 +9,25 @@ module GoodJob
|
|
9
9
|
|
10
10
|
self.table_name = 'good_jobs'.freeze
|
11
11
|
|
12
|
+
def self.queue_parser(string)
|
13
|
+
string = string.presence || '*'
|
14
|
+
|
15
|
+
if string.first == '-'
|
16
|
+
exclude_queues = true
|
17
|
+
string = string[1..-1]
|
18
|
+
end
|
19
|
+
|
20
|
+
queues = string.split(',').map(&:strip)
|
21
|
+
|
22
|
+
if queues.include?('*')
|
23
|
+
{ all: true }
|
24
|
+
elsif exclude_queues
|
25
|
+
{ exclude: queues }
|
26
|
+
else
|
27
|
+
{ include: queues }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
12
31
|
scope :unfinished, (lambda do
|
13
32
|
if column_names.include?('finished_at')
|
14
33
|
where(finished_at: nil)
|
@@ -21,20 +40,14 @@ module GoodJob
|
|
21
40
|
scope :priority_ordered, -> { order('priority DESC NULLS LAST') }
|
22
41
|
scope :finished, ->(timestamp = nil) { timestamp ? where(arel_table['finished_at'].lteq(timestamp)) : where.not(finished_at: nil) }
|
23
42
|
scope :queue_string, (lambda do |string|
|
24
|
-
|
25
|
-
|
26
|
-
if
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
return if queue_names_without_all.size.zero?
|
33
|
-
|
34
|
-
if exclude_queues
|
35
|
-
where.not(queue_name: queue_names_without_all).or where(queue_name: nil)
|
36
|
-
else
|
37
|
-
where(queue_name: queue_names_without_all)
|
43
|
+
parsed = queue_parser(string)
|
44
|
+
|
45
|
+
if parsed[:all]
|
46
|
+
all
|
47
|
+
elsif parsed[:exclude]
|
48
|
+
where.not(queue_name: parsed[:exclude]).or where(queue_name: nil)
|
49
|
+
elsif parsed[:include]
|
50
|
+
where(queue_name: parsed[:include])
|
38
51
|
end
|
39
52
|
end)
|
40
53
|
|
@@ -45,7 +58,8 @@ module GoodJob
|
|
45
58
|
|
46
59
|
unfinished.priority_ordered.only_scheduled.limit(1).with_advisory_lock do |good_jobs|
|
47
60
|
good_job = good_jobs.first
|
48
|
-
|
61
|
+
# TODO: Determine why some records are fetched without an advisory lock at all
|
62
|
+
break unless good_job&.owns_advisory_lock?
|
49
63
|
|
50
64
|
result, error = good_job.perform
|
51
65
|
end
|
@@ -60,7 +74,7 @@ module GoodJob
|
|
60
74
|
queue_name: active_job.queue_name.presence || DEFAULT_QUEUE_NAME,
|
61
75
|
priority: active_job.priority || DEFAULT_PRIORITY,
|
62
76
|
serialized_params: active_job.serialize,
|
63
|
-
scheduled_at: scheduled_at
|
77
|
+
scheduled_at: scheduled_at,
|
64
78
|
create_with_advisory_lock: create_with_advisory_lock
|
65
79
|
)
|
66
80
|
|
@@ -89,7 +103,7 @@ module GoodJob
|
|
89
103
|
)
|
90
104
|
|
91
105
|
begin
|
92
|
-
ActiveSupport::Notifications.instrument("perform_job.good_job", { good_job: self }) do
|
106
|
+
ActiveSupport::Notifications.instrument("perform_job.good_job", { good_job: self, process_id: GoodJob::CurrentExecution.process_id, thread_name: GoodJob::CurrentExecution.thread_name }) do
|
93
107
|
result = ActiveJob::Base.execute(params)
|
94
108
|
end
|
95
109
|
rescue StandardError => e
|
data/lib/good_job/lockable.rb
CHANGED
@@ -55,19 +55,17 @@ module GoodJob
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def advisory_lock
|
58
|
-
|
59
|
-
|
60
|
-
WHERE pg_try_advisory_lock(('x'||substr(md5(:table_name || :id::text), 1, 16))::bit(64)::bigint)
|
58
|
+
where_sql = <<~SQL
|
59
|
+
pg_try_advisory_lock(('x'||substr(md5(:table_name || :id::text), 1, 16))::bit(64)::bigint)
|
61
60
|
SQL
|
62
|
-
self.class.
|
61
|
+
self.class.unscoped.where(where_sql, { table_name: self.class.table_name, id: send(self.class.primary_key) }).exists?
|
63
62
|
end
|
64
63
|
|
65
64
|
def advisory_unlock
|
66
|
-
|
67
|
-
|
68
|
-
WHERE pg_advisory_unlock(('x'||substr(md5(:table_name || :id::text), 1, 16))::bit(64)::bigint)
|
65
|
+
where_sql = <<~SQL
|
66
|
+
pg_advisory_unlock(('x'||substr(md5(:table_name || :id::text), 1, 16))::bit(64)::bigint)
|
69
67
|
SQL
|
70
|
-
self.class.
|
68
|
+
self.class.unscoped.where(where_sql, { table_name: self.class.table_name, id: send(self.class.primary_key) }).exists?
|
71
69
|
end
|
72
70
|
|
73
71
|
def advisory_lock!
|
@@ -85,11 +83,11 @@ module GoodJob
|
|
85
83
|
end
|
86
84
|
|
87
85
|
def advisory_locked?
|
88
|
-
self.class.advisory_locked.where(id: send(self.class.primary_key)).
|
86
|
+
self.class.unscoped.advisory_locked.where(id: send(self.class.primary_key)).exists?
|
89
87
|
end
|
90
88
|
|
91
89
|
def owns_advisory_lock?
|
92
|
-
self.class.owns_advisory_locked.where(id: send(self.class.primary_key)).
|
90
|
+
self.class.unscoped.owns_advisory_locked.where(id: send(self.class.primary_key)).exists?
|
93
91
|
end
|
94
92
|
|
95
93
|
def advisory_unlock!
|
@@ -32,7 +32,7 @@ module GoodJob
|
|
32
32
|
performer_name = event.payload[:performer_name]
|
33
33
|
process_id = event.payload[:process_id]
|
34
34
|
|
35
|
-
|
35
|
+
info(tags: [process_id]) do
|
36
36
|
"GoodJob started scheduler with queues=#{performer_name} max_threads=#{max_threads} poll_interval=#{poll_interval}."
|
37
37
|
end
|
38
38
|
end
|
@@ -40,7 +40,7 @@ module GoodJob
|
|
40
40
|
def scheduler_shutdown_start(event)
|
41
41
|
process_id = event.payload[:process_id]
|
42
42
|
|
43
|
-
|
43
|
+
info(tags: [process_id]) do
|
44
44
|
"GoodJob shutting down scheduler..."
|
45
45
|
end
|
46
46
|
end
|
@@ -48,7 +48,7 @@ module GoodJob
|
|
48
48
|
def scheduler_shutdown(event)
|
49
49
|
process_id = event.payload[:process_id]
|
50
50
|
|
51
|
-
|
51
|
+
info(tags: [process_id]) do
|
52
52
|
"GoodJob scheduler is shut down."
|
53
53
|
end
|
54
54
|
end
|
@@ -56,24 +56,100 @@ module GoodJob
|
|
56
56
|
def scheduler_restart_pools(event)
|
57
57
|
process_id = event.payload[:process_id]
|
58
58
|
|
59
|
-
|
59
|
+
info(tags: [process_id]) do
|
60
60
|
"GoodJob scheduler has restarted."
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
|
+
def perform_job(event)
|
65
|
+
good_job = event.payload[:good_job]
|
66
|
+
process_id = event.payload[:process_id]
|
67
|
+
thread_name = event.payload[:thread_name]
|
68
|
+
|
69
|
+
info(tags: [process_id, thread_name]) do
|
70
|
+
"Executed GoodJob #{good_job.id}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def notifier_listen(_event)
|
75
|
+
info do
|
76
|
+
"Notifier subscribed with LISTEN"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def notifier_notified(event)
|
81
|
+
payload = event.payload[:payload]
|
82
|
+
|
83
|
+
debug do
|
84
|
+
"Notifier received payload: #{payload}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def notifier_notify_error(event)
|
89
|
+
error = event.payload[:error]
|
90
|
+
|
91
|
+
error do
|
92
|
+
"Notifier errored: #{error}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def notifier_unlisten(_event)
|
97
|
+
info do
|
98
|
+
"Notifier unsubscribed with UNLISTEN"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
64
102
|
def cleanup_preserved_jobs(event)
|
65
103
|
timestamp = event.payload[:timestamp]
|
66
104
|
deleted_records_count = event.payload[:deleted_records_count]
|
67
105
|
|
68
|
-
|
106
|
+
info do
|
69
107
|
"GoodJob deleted #{deleted_records_count} preserved #{'job'.pluralize(deleted_records_count)} finished before #{timestamp}."
|
70
108
|
end
|
71
109
|
end
|
72
110
|
|
73
|
-
|
111
|
+
class << self
|
112
|
+
def loggers
|
113
|
+
@_loggers ||= [GoodJob.logger]
|
114
|
+
end
|
115
|
+
|
116
|
+
def logger
|
117
|
+
@_logger ||= begin
|
118
|
+
logger = Logger.new(StringIO.new)
|
119
|
+
loggers.each do |each_logger|
|
120
|
+
logger.extend(ActiveSupport::Logger.broadcast(each_logger))
|
121
|
+
end
|
122
|
+
logger
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def reset_logger
|
127
|
+
@_logger = nil
|
128
|
+
end
|
129
|
+
end
|
74
130
|
|
75
131
|
def logger
|
76
|
-
GoodJob.logger
|
132
|
+
GoodJob::LogSubscriber.logger
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def tag_logger(*tags, &block)
|
138
|
+
tags = tags.dup.unshift("GoodJob").compact
|
139
|
+
|
140
|
+
self.class.loggers.inject(block) do |inner, each_logger|
|
141
|
+
if each_logger.respond_to?(:tagged)
|
142
|
+
tags_for_logger = if each_logger.formatter.current_tags.include?("ActiveJob")
|
143
|
+
["ActiveJob"] + tags
|
144
|
+
else
|
145
|
+
tags
|
146
|
+
end
|
147
|
+
|
148
|
+
proc { each_logger.tagged(*tags_for_logger, &inner) }
|
149
|
+
else
|
150
|
+
inner
|
151
|
+
end
|
152
|
+
end.call
|
77
153
|
end
|
78
154
|
|
79
155
|
%w(info debug warn error fatal unknown).each do |level|
|
@@ -81,30 +157,11 @@ module GoodJob
|
|
81
157
|
def #{level}(progname = nil, tags: [], &block)
|
82
158
|
return unless logger
|
83
159
|
|
84
|
-
|
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
|
160
|
+
tag_logger(*tags) do
|
90
161
|
logger.#{level}(progname, &block)
|
91
162
|
end
|
92
163
|
end
|
93
164
|
METHOD
|
94
165
|
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
166
|
end
|
110
167
|
end
|
@@ -18,8 +18,17 @@ module GoodJob
|
|
18
18
|
schedulers.each { |s| s.restart(wait: wait) }
|
19
19
|
end
|
20
20
|
|
21
|
-
def create_thread
|
22
|
-
|
21
|
+
def create_thread(state = nil)
|
22
|
+
results = []
|
23
|
+
any_true = schedulers.any? do |scheduler|
|
24
|
+
scheduler.create_thread(state).tap { |result| results << result }
|
25
|
+
end
|
26
|
+
|
27
|
+
if any_true
|
28
|
+
true
|
29
|
+
else
|
30
|
+
results.any? { |result| result == false } ? false : nil
|
31
|
+
end
|
23
32
|
end
|
24
33
|
end
|
25
34
|
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'concurrent/atomic/atomic_boolean'
|
2
|
+
|
3
|
+
module GoodJob # :nodoc:
|
4
|
+
#
|
5
|
+
# Wrapper for Postgres LISTEN/NOTIFY
|
6
|
+
#
|
7
|
+
class Notifier
|
8
|
+
CHANNEL = 'good_job'.freeze
|
9
|
+
POOL_OPTIONS = {
|
10
|
+
name: name,
|
11
|
+
min_threads: 0,
|
12
|
+
max_threads: 1,
|
13
|
+
auto_terminate: true,
|
14
|
+
idletime: 60,
|
15
|
+
max_queue: 1,
|
16
|
+
fallback_policy: :discard,
|
17
|
+
}.freeze
|
18
|
+
WAIT_INTERVAL = 1
|
19
|
+
|
20
|
+
# @!attribute [r] instances
|
21
|
+
# @!scope class
|
22
|
+
# @return [array<GoodJob:Adapter>] the instances of +GoodJob::Notifier+
|
23
|
+
cattr_reader :instances, default: [], instance_reader: false
|
24
|
+
|
25
|
+
def self.notify(message)
|
26
|
+
connection = ActiveRecord::Base.connection
|
27
|
+
connection.exec_query <<~SQL
|
28
|
+
NOTIFY #{CHANNEL}, #{connection.quote(message.to_json)}
|
29
|
+
SQL
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :recipients
|
33
|
+
|
34
|
+
def initialize(*recipients)
|
35
|
+
@recipients = Concurrent::Array.new(recipients)
|
36
|
+
@listening = Concurrent::AtomicBoolean.new(false)
|
37
|
+
|
38
|
+
self.class.instances << self
|
39
|
+
|
40
|
+
create_pool
|
41
|
+
listen
|
42
|
+
end
|
43
|
+
|
44
|
+
def listening?
|
45
|
+
@listening.true?
|
46
|
+
end
|
47
|
+
|
48
|
+
def restart(wait: true)
|
49
|
+
shutdown(wait: wait)
|
50
|
+
create_pool
|
51
|
+
listen
|
52
|
+
end
|
53
|
+
|
54
|
+
def shutdown(wait: true)
|
55
|
+
return unless @pool.running?
|
56
|
+
|
57
|
+
@pool.shutdown
|
58
|
+
@pool.wait_for_termination if wait
|
59
|
+
end
|
60
|
+
|
61
|
+
def shutdown?
|
62
|
+
!@pool.running?
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def create_pool
|
68
|
+
@pool = Concurrent::ThreadPoolExecutor.new(POOL_OPTIONS)
|
69
|
+
end
|
70
|
+
|
71
|
+
def listen
|
72
|
+
future = Concurrent::Future.new(args: [@recipients, @pool, @listening], executor: @pool) do |recipients, pool, listening|
|
73
|
+
begin
|
74
|
+
with_listen_connection do |conn|
|
75
|
+
ActiveSupport::Notifications.instrument("notifier_listen.good_job") do
|
76
|
+
conn.async_exec "LISTEN #{CHANNEL}"
|
77
|
+
end
|
78
|
+
|
79
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
80
|
+
while pool.running?
|
81
|
+
listening.make_true
|
82
|
+
conn.wait_for_notify(WAIT_INTERVAL) do |channel, _pid, payload|
|
83
|
+
listening.make_false
|
84
|
+
next unless channel == CHANNEL
|
85
|
+
|
86
|
+
ActiveSupport::Notifications.instrument("notifier_notified.good_job", { payload: payload })
|
87
|
+
parsed_payload = JSON.parse(payload, symbolize_names: true)
|
88
|
+
recipients.each do |recipient|
|
89
|
+
target, method_name = recipient.is_a?(Array) ? recipient : [recipient, :call]
|
90
|
+
target.send(method_name, parsed_payload)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
listening.make_false
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
rescue StandardError => e
|
98
|
+
ActiveSupport::Notifications.instrument("notifier_notify_error.good_job", { error: e })
|
99
|
+
raise
|
100
|
+
ensure
|
101
|
+
@listening.make_false
|
102
|
+
ActiveSupport::Notifications.instrument("notifier_unlisten.good_job") do
|
103
|
+
conn.async_exec "UNLISTEN *"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
future.add_observer(self, :listen_observer)
|
109
|
+
future.execute
|
110
|
+
end
|
111
|
+
|
112
|
+
def listen_observer(_time, _result, _thread_error)
|
113
|
+
listen unless shutdown?
|
114
|
+
end
|
115
|
+
|
116
|
+
def with_listen_connection
|
117
|
+
ar_conn = ActiveRecord::Base.connection_pool.checkout.tap do |conn|
|
118
|
+
ActiveRecord::Base.connection_pool.remove(conn)
|
119
|
+
end
|
120
|
+
pg_conn = ar_conn.raw_connection
|
121
|
+
pg_conn.exec("SET application_name = #{pg_conn.escape_identifier(self.class.name)}")
|
122
|
+
yield pg_conn
|
123
|
+
ensure
|
124
|
+
ar_conn.disconnect!
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/lib/good_job/performer.rb
CHANGED
@@ -2,14 +2,21 @@ module GoodJob
|
|
2
2
|
class Performer
|
3
3
|
attr_reader :name
|
4
4
|
|
5
|
-
def initialize(target, method_name, name: nil)
|
5
|
+
def initialize(target, method_name, name: nil, filter: nil)
|
6
6
|
@target = target
|
7
7
|
@method_name = method_name
|
8
8
|
@name = name
|
9
|
+
@filter = filter
|
9
10
|
end
|
10
11
|
|
11
12
|
def next
|
12
13
|
@target.public_send(@method_name)
|
13
14
|
end
|
15
|
+
|
16
|
+
def next?(state = {})
|
17
|
+
return true unless @filter.respond_to?(:call)
|
18
|
+
|
19
|
+
@filter.call(state)
|
20
|
+
end
|
14
21
|
end
|
15
22
|
end
|
data/lib/good_job/scheduler.rb
CHANGED
@@ -3,12 +3,14 @@ require "concurrent/timer_task"
|
|
3
3
|
require "concurrent/utility/processor_counter"
|
4
4
|
|
5
5
|
module GoodJob # :nodoc:
|
6
|
+
#
|
6
7
|
# Schedulers are generic thread execution pools that are responsible for
|
7
8
|
# periodically checking for available execution tasks, executing tasks in a
|
8
9
|
# bounded thread-pool, and efficiently scaling execution threads.
|
9
10
|
#
|
10
11
|
# Schedulers are "generic" in the sense that they delegate task execution
|
11
12
|
# details to a "Performer" object that responds to #next.
|
13
|
+
#
|
12
14
|
class Scheduler
|
13
15
|
# Defaults for instance of Concurrent::TimerTask
|
14
16
|
DEFAULT_TIMER_OPTIONS = {
|
@@ -19,7 +21,7 @@ module GoodJob # :nodoc:
|
|
19
21
|
|
20
22
|
# Defaults for instance of Concurrent::ThreadPoolExecutor
|
21
23
|
DEFAULT_POOL_OPTIONS = {
|
22
|
-
name:
|
24
|
+
name: name,
|
23
25
|
min_threads: 0,
|
24
26
|
max_threads: Concurrent.processor_count,
|
25
27
|
auto_terminate: true,
|
@@ -43,10 +45,20 @@ module GoodJob # :nodoc:
|
|
43
45
|
max_threads = (max_threads || configuration.max_threads).to_i
|
44
46
|
|
45
47
|
job_query = GoodJob::Job.queue_string(queue_string)
|
46
|
-
|
48
|
+
parsed = GoodJob::Job.queue_parser(queue_string)
|
49
|
+
job_filter = proc do |state|
|
50
|
+
if parsed[:exclude]
|
51
|
+
!parsed[:exclude].include? state[:queue_name]
|
52
|
+
elsif parsed[:include]
|
53
|
+
parsed[:include].include? state[:queue_name]
|
54
|
+
else
|
55
|
+
true
|
56
|
+
end
|
57
|
+
end
|
58
|
+
job_performer = GoodJob::Performer.new(job_query, :perform_with_advisory_lock, name: queue_string, filter: job_filter)
|
47
59
|
|
48
60
|
timer_options = {}
|
49
|
-
timer_options[:execution_interval] = configuration.poll_interval
|
61
|
+
timer_options[:execution_interval] = configuration.poll_interval
|
50
62
|
|
51
63
|
pool_options = {
|
52
64
|
max_threads: max_threads,
|
@@ -74,6 +86,8 @@ module GoodJob # :nodoc:
|
|
74
86
|
@pool_options = DEFAULT_POOL_OPTIONS.merge(pool_options)
|
75
87
|
@timer_options = DEFAULT_TIMER_OPTIONS.merge(timer_options)
|
76
88
|
|
89
|
+
@pool_options[:name] = "GoodJob::Scheduler(queues=#{@performer.name} max_threads=#{@pool_options[:max_threads]} poll_interval=#{@timer_options[:execution_interval]})"
|
90
|
+
|
77
91
|
create_pools
|
78
92
|
end
|
79
93
|
|
@@ -83,8 +97,8 @@ module GoodJob # :nodoc:
|
|
83
97
|
def shutdown(wait: true)
|
84
98
|
@_shutdown = true
|
85
99
|
|
86
|
-
|
87
|
-
|
100
|
+
instrument("scheduler_shutdown_start", { wait: wait })
|
101
|
+
instrument("scheduler_shutdown", { wait: wait }) do
|
88
102
|
if @timer&.running?
|
89
103
|
@timer.shutdown
|
90
104
|
@timer.wait_for_termination if wait
|
@@ -107,16 +121,22 @@ module GoodJob # :nodoc:
|
|
107
121
|
# @param wait [Boolean] Wait for actively executing jobs to finish
|
108
122
|
# @return [void]
|
109
123
|
def restart(wait: true)
|
110
|
-
|
124
|
+
instrument("scheduler_restart_pools") do
|
111
125
|
shutdown(wait: wait) unless shutdown?
|
112
126
|
create_pools
|
127
|
+
@_shutdown = false
|
113
128
|
end
|
114
129
|
end
|
115
130
|
|
116
|
-
# Triggers
|
117
|
-
# @
|
118
|
-
|
119
|
-
|
131
|
+
# Triggers a Performer execution, if an execution thread is available.
|
132
|
+
# @param state [nil, Object] Allows Performer#next? to accept or reject the execution
|
133
|
+
# @return [nil, Boolean] if the thread was created
|
134
|
+
def create_thread(state = nil)
|
135
|
+
return nil unless @pool.running? && @pool.ready_worker_count.positive?
|
136
|
+
|
137
|
+
if state
|
138
|
+
return false unless @performer.next?(state)
|
139
|
+
end
|
120
140
|
|
121
141
|
future = Concurrent::Future.new(args: [@performer], executor: @pool) do |performer|
|
122
142
|
output = nil
|
@@ -125,6 +145,7 @@ module GoodJob # :nodoc:
|
|
125
145
|
end
|
126
146
|
future.add_observer(self, :task_observer)
|
127
147
|
future.execute
|
148
|
+
|
128
149
|
true
|
129
150
|
end
|
130
151
|
|
@@ -133,7 +154,7 @@ module GoodJob # :nodoc:
|
|
133
154
|
# @return [void]
|
134
155
|
def timer_observer(time, executed_task, thread_error)
|
135
156
|
GoodJob.on_thread_error.call(thread_error) if thread_error && GoodJob.on_thread_error.respond_to?(:call)
|
136
|
-
|
157
|
+
instrument("finished_timer_task", { result: executed_task, error: thread_error, time: time })
|
137
158
|
end
|
138
159
|
|
139
160
|
# Invoked on completion of ThreadPoolExecutor task
|
@@ -141,7 +162,7 @@ module GoodJob # :nodoc:
|
|
141
162
|
# @return [void]
|
142
163
|
def task_observer(time, output, thread_error)
|
143
164
|
GoodJob.on_thread_error.call(thread_error) if thread_error && GoodJob.on_thread_error.respond_to?(:call)
|
144
|
-
|
165
|
+
instrument("finished_job_task", { result: output, error: thread_error, time: time })
|
145
166
|
create_thread if output
|
146
167
|
end
|
147
168
|
|
@@ -149,7 +170,7 @@ module GoodJob # :nodoc:
|
|
149
170
|
|
150
171
|
# @return [void]
|
151
172
|
def create_pools
|
152
|
-
|
173
|
+
instrument("scheduler_create_pools", { performer_name: @performer.name, max_threads: @pool_options[:max_threads], poll_interval: @timer_options[:execution_interval] }) do
|
153
174
|
@pool = ThreadPoolExecutor.new(@pool_options)
|
154
175
|
next unless @timer_options[:execution_interval].positive?
|
155
176
|
|
@@ -159,14 +180,14 @@ module GoodJob # :nodoc:
|
|
159
180
|
end
|
160
181
|
end
|
161
182
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
183
|
+
def instrument(name, payload = {}, &block)
|
184
|
+
payload = payload.reverse_merge({
|
185
|
+
scheduler: self,
|
186
|
+
process_id: GoodJob::CurrentExecution.process_id,
|
187
|
+
thread_name: GoodJob::CurrentExecution.thread_name,
|
188
|
+
})
|
166
189
|
|
167
|
-
|
168
|
-
def thread_name
|
169
|
-
(Thread.current.name || Thread.current.object_id).to_s
|
190
|
+
ActiveSupport::Notifications.instrument("#{name}.good_job", payload, &block)
|
170
191
|
end
|
171
192
|
end
|
172
193
|
|
data/lib/good_job/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: good_job
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Sheldon
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-08-
|
11
|
+
date: 2020-08-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activejob
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 5.1.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 5.1.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activerecord
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 5.1.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 5.1.0
|
13
41
|
- !ruby/object:Gem::Dependency
|
14
42
|
name: concurrent-ruby
|
15
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -39,7 +67,7 @@ dependencies:
|
|
39
67
|
- !ruby/object:Gem::Version
|
40
68
|
version: 1.0.0
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
70
|
+
name: railties
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
44
72
|
requirements:
|
45
73
|
- - ">="
|
@@ -151,7 +179,7 @@ dependencies:
|
|
151
179
|
- !ruby/object:Gem::Version
|
152
180
|
version: '0'
|
153
181
|
- !ruby/object:Gem::Dependency
|
154
|
-
name: pry
|
182
|
+
name: pry-rails
|
155
183
|
requirement: !ruby/object:Gem::Requirement
|
156
184
|
requirements:
|
157
185
|
- - ">="
|
@@ -178,6 +206,20 @@ dependencies:
|
|
178
206
|
- - ">="
|
179
207
|
- !ruby/object:Gem::Version
|
180
208
|
version: '0'
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: rbtrace
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - ">="
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: '0'
|
216
|
+
type: :development
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ">="
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '0'
|
181
223
|
- !ruby/object:Gem::Dependency
|
182
224
|
name: rspec-rails
|
183
225
|
requirement: !ruby/object:Gem::Requirement
|
@@ -248,6 +290,20 @@ dependencies:
|
|
248
290
|
- - ">="
|
249
291
|
- !ruby/object:Gem::Version
|
250
292
|
version: '0'
|
293
|
+
- !ruby/object:Gem::Dependency
|
294
|
+
name: sigdump
|
295
|
+
requirement: !ruby/object:Gem::Requirement
|
296
|
+
requirements:
|
297
|
+
- - ">="
|
298
|
+
- !ruby/object:Gem::Version
|
299
|
+
version: '0'
|
300
|
+
type: :development
|
301
|
+
prerelease: false
|
302
|
+
version_requirements: !ruby/object:Gem::Requirement
|
303
|
+
requirements:
|
304
|
+
- - ">="
|
305
|
+
- !ruby/object:Gem::Version
|
306
|
+
version: '0'
|
251
307
|
- !ruby/object:Gem::Dependency
|
252
308
|
name: yard
|
253
309
|
requirement: !ruby/object:Gem::Requirement
|
@@ -289,6 +345,7 @@ files:
|
|
289
345
|
- lib/good_job/lockable.rb
|
290
346
|
- lib/good_job/log_subscriber.rb
|
291
347
|
- lib/good_job/multi_scheduler.rb
|
348
|
+
- lib/good_job/notifier.rb
|
292
349
|
- lib/good_job/performer.rb
|
293
350
|
- lib/good_job/pg_locks.rb
|
294
351
|
- lib/good_job/railtie.rb
|