good_job 2.5.0 → 2.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +69 -0
- data/README.md +58 -1
- data/engine/app/assets/scripts.js +1 -0
- data/engine/app/assets/style.css +5 -0
- data/engine/app/assets/vendor/chartjs/chart.min.js +13 -0
- data/engine/app/assets/vendor/rails_ujs.js +747 -0
- data/engine/app/charts/good_job/scheduled_by_queue_chart.rb +69 -0
- data/engine/app/controllers/good_job/assets_controller.rb +8 -4
- data/engine/app/controllers/good_job/base_controller.rb +19 -0
- data/engine/app/controllers/good_job/cron_entries_controller.rb +19 -0
- data/engine/app/filters/good_job/base_filter.rb +12 -54
- data/engine/app/filters/good_job/executions_filter.rb +9 -8
- data/engine/app/filters/good_job/jobs_filter.rb +9 -8
- data/engine/app/views/good_job/cron_entries/index.html.erb +51 -0
- data/engine/app/views/good_job/cron_entries/show.html.erb +4 -0
- data/engine/app/views/good_job/{shared/_executions_table.erb → executions/_table.erb} +1 -1
- data/engine/app/views/good_job/executions/index.html.erb +2 -2
- data/engine/app/views/good_job/{shared/_jobs_table.erb → jobs/_table.erb} +4 -4
- data/engine/app/views/good_job/jobs/index.html.erb +2 -2
- data/engine/app/views/good_job/jobs/show.html.erb +2 -2
- data/engine/app/views/good_job/shared/_chart.erb +19 -46
- data/engine/app/views/good_job/shared/_filter.erb +27 -13
- data/engine/app/views/good_job/shared/icons/_play.html.erb +4 -0
- data/engine/app/views/layouts/good_job/base.html.erb +6 -4
- data/engine/config/routes.rb +10 -3
- data/{engine/app/models → lib}/good_job/active_job_job.rb +2 -19
- data/lib/good_job/cli.rb +8 -0
- data/lib/good_job/configuration.rb +8 -1
- data/lib/good_job/cron_entry.rb +75 -4
- data/lib/good_job/cron_manager.rb +1 -5
- data/lib/good_job/current_thread.rb +26 -8
- data/lib/good_job/execution.rb +16 -31
- data/lib/good_job/filterable.rb +42 -0
- data/lib/good_job/notifier.rb +17 -7
- data/lib/good_job/probe_server.rb +51 -0
- data/lib/good_job/version.rb +1 -1
- metadata +41 -21
- data/engine/app/assets/vendor/chartist/chartist.css +0 -613
- data/engine/app/assets/vendor/chartist/chartist.js +0 -4516
- data/engine/app/controllers/good_job/cron_schedules_controller.rb +0 -9
- data/engine/app/views/good_job/cron_schedules/index.html.erb +0 -72
data/engine/config/routes.rb
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
GoodJob::Engine.routes.draw do
|
3
3
|
root to: 'executions#index'
|
4
|
-
|
4
|
+
|
5
|
+
resources :cron_entries, only: %i[index show] do
|
6
|
+
member do
|
7
|
+
post :enqueue
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
5
11
|
resources :jobs, only: %i[index show] do
|
6
12
|
member do
|
7
13
|
put :discard
|
@@ -14,13 +20,14 @@ GoodJob::Engine.routes.draw do
|
|
14
20
|
scope controller: :assets do
|
15
21
|
constraints(format: :css) do
|
16
22
|
get :bootstrap, action: :bootstrap_css
|
17
|
-
get :chartist, action: :chartist_css
|
18
23
|
get :style, action: :style_css
|
19
24
|
end
|
20
25
|
|
21
26
|
constraints(format: :js) do
|
22
27
|
get :bootstrap, action: :bootstrap_js
|
23
|
-
get :
|
28
|
+
get :rails_ujs, action: :rails_ujs_js
|
29
|
+
get :chartjs, action: :chartjs_js
|
30
|
+
get :scripts, action: :scripts_js
|
24
31
|
end
|
25
32
|
end
|
26
33
|
end
|
@@ -8,7 +8,8 @@ module GoodJob
|
|
8
8
|
# @!parse
|
9
9
|
# class ActiveJob < ActiveRecord::Base; end
|
10
10
|
class ActiveJobJob < Object.const_get(GoodJob.active_record_parent_class)
|
11
|
-
include
|
11
|
+
include Filterable
|
12
|
+
include Lockable
|
12
13
|
|
13
14
|
# Raised when an inappropriate action is applied to a Job based on its state.
|
14
15
|
ActionForStateMismatchError = Class.new(StandardError)
|
@@ -47,24 +48,6 @@ module GoodJob
|
|
47
48
|
# Errored but will not be retried
|
48
49
|
scope :discarded, -> { where.not(finished_at: nil).where.not(error: nil) }
|
49
50
|
|
50
|
-
# Get Jobs in display order with optional keyset pagination.
|
51
|
-
# @!method display_all(after_scheduled_at: nil, after_id: nil)
|
52
|
-
# @!scope class
|
53
|
-
# @param after_scheduled_at [DateTime, String, nil]
|
54
|
-
# Display records scheduled after this time for keyset pagination
|
55
|
-
# @param after_id [Numeric, String, nil]
|
56
|
-
# Display records after this ID for keyset pagination
|
57
|
-
# @return [ActiveRecord::Relation]
|
58
|
-
scope :display_all, (lambda do |after_scheduled_at: nil, after_id: nil|
|
59
|
-
query = order(Arel.sql('COALESCE(scheduled_at, created_at) DESC, id DESC'))
|
60
|
-
if after_scheduled_at.present? && after_id.present?
|
61
|
-
query = query.where(Arel.sql('(COALESCE(scheduled_at, created_at), id) < (:after_scheduled_at, :after_id)'), after_scheduled_at: after_scheduled_at, after_id: after_id)
|
62
|
-
elsif after_scheduled_at.present?
|
63
|
-
query = query.where(Arel.sql('(COALESCE(scheduled_at, created_at)) < (:after_scheduled_at)'), after_scheduled_at: after_scheduled_at)
|
64
|
-
end
|
65
|
-
query
|
66
|
-
end)
|
67
|
-
|
68
51
|
# The job's ActiveJob UUID
|
69
52
|
# @return [String]
|
70
53
|
def id
|
data/lib/good_job/cli.rb
CHANGED
@@ -79,6 +79,9 @@ module GoodJob
|
|
79
79
|
method_option :pidfile,
|
80
80
|
type: :string,
|
81
81
|
desc: "Path to write daemonized Process ID (env var: GOOD_JOB_PIDFILE, default: tmp/pids/good_job.pid)"
|
82
|
+
method_option :probe_port,
|
83
|
+
type: :numeric,
|
84
|
+
desc: "Port for http health check (env var: GOOD_JOB_PROBE_PORT, default: nil)"
|
82
85
|
|
83
86
|
def start
|
84
87
|
set_up_application!
|
@@ -93,6 +96,10 @@ module GoodJob
|
|
93
96
|
poller.recipients << [scheduler, :create_thread]
|
94
97
|
|
95
98
|
cron_manager = GoodJob::CronManager.new(configuration.cron_entries, start_on_initialize: true) if configuration.enable_cron?
|
99
|
+
if configuration.probe_port
|
100
|
+
probe_server = GoodJob::ProbeServer.new(port: configuration.probe_port)
|
101
|
+
probe_server.start
|
102
|
+
end
|
96
103
|
|
97
104
|
@stop_good_job_executable = false
|
98
105
|
%w[INT TERM].each do |signal|
|
@@ -106,6 +113,7 @@ module GoodJob
|
|
106
113
|
|
107
114
|
executors = [notifier, poller, cron_manager, scheduler].compact
|
108
115
|
GoodJob._shutdown_all(executors, timeout: configuration.shutdown_timeout)
|
116
|
+
probe_server&.stop
|
109
117
|
end
|
110
118
|
|
111
119
|
default_task :start
|
@@ -157,7 +157,7 @@ module GoodJob
|
|
157
157
|
alias enable_cron? enable_cron
|
158
158
|
|
159
159
|
def cron
|
160
|
-
env_cron = JSON.parse(ENV['GOOD_JOB_CRON']) if ENV['GOOD_JOB_CRON'].present?
|
160
|
+
env_cron = JSON.parse(ENV['GOOD_JOB_CRON'], symbolize_names: true) if ENV['GOOD_JOB_CRON'].present?
|
161
161
|
|
162
162
|
options[:cron] ||
|
163
163
|
rails_config[:cron] ||
|
@@ -195,6 +195,13 @@ module GoodJob
|
|
195
195
|
Rails.application.root.join('tmp', 'pids', 'good_job.pid')
|
196
196
|
end
|
197
197
|
|
198
|
+
# Port of the probe server
|
199
|
+
# @return [nil,Integer]
|
200
|
+
def probe_port
|
201
|
+
options[:probe_port] ||
|
202
|
+
env['GOOD_JOB_PROBE_PORT']
|
203
|
+
end
|
204
|
+
|
198
205
|
private
|
199
206
|
|
200
207
|
def rails_config
|
data/lib/good_job/cron_entry.rb
CHANGED
@@ -12,14 +12,29 @@ module GoodJob # :nodoc:
|
|
12
12
|
|
13
13
|
attr_reader :params
|
14
14
|
|
15
|
+
def self.all(configuration: nil)
|
16
|
+
configuration ||= GoodJob::Configuration.new({})
|
17
|
+
configuration.cron_entries
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.find(key, configuration: nil)
|
21
|
+
all(configuration: configuration).find { |entry| entry.key == key.to_sym }.tap do |cron_entry|
|
22
|
+
raise ActiveRecord::RecordNotFound unless cron_entry
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
15
26
|
def initialize(params = {})
|
16
|
-
@params = params
|
27
|
+
@params = params
|
28
|
+
|
29
|
+
raise ArgumentError, "Invalid cron format: '#{cron}'" unless fugit.instance_of?(Fugit::Cron)
|
17
30
|
end
|
18
31
|
|
19
32
|
def key
|
20
33
|
params.fetch(:key)
|
21
34
|
end
|
35
|
+
|
22
36
|
alias id key
|
37
|
+
alias to_param key
|
23
38
|
|
24
39
|
def job_class
|
25
40
|
params.fetch(:class)
|
@@ -42,16 +57,61 @@ module GoodJob # :nodoc:
|
|
42
57
|
end
|
43
58
|
|
44
59
|
def next_at
|
45
|
-
fugit = Fugit::Cron.parse(cron)
|
46
60
|
fugit.next_time.to_t
|
47
61
|
end
|
48
62
|
|
49
|
-
def
|
50
|
-
|
63
|
+
def schedule
|
64
|
+
fugit.original
|
65
|
+
end
|
66
|
+
|
67
|
+
def fugit
|
68
|
+
@_fugit ||= Fugit.parse(cron)
|
69
|
+
end
|
70
|
+
|
71
|
+
def jobs
|
72
|
+
GoodJob::ActiveJobJob.where(cron_key: key)
|
73
|
+
end
|
74
|
+
|
75
|
+
def last_at
|
76
|
+
return if last_job.blank?
|
77
|
+
|
78
|
+
if GoodJob::ActiveJobJob.column_names.include?('cron_at')
|
79
|
+
(last_job.cron_at || last_job.created_at).localtime
|
80
|
+
else
|
81
|
+
last_job.created_at
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def enqueue(cron_at = nil)
|
86
|
+
GoodJob::CurrentThread.within do |current_thread|
|
87
|
+
current_thread.cron_key = key
|
88
|
+
current_thread.cron_at = cron_at
|
89
|
+
|
90
|
+
job_class.constantize.set(set_value).perform_later(*args_value)
|
91
|
+
end
|
51
92
|
rescue ActiveRecord::RecordNotUnique
|
52
93
|
false
|
53
94
|
end
|
54
95
|
|
96
|
+
def last_job
|
97
|
+
if GoodJob::ActiveJobJob.column_names.include?('cron_at')
|
98
|
+
jobs.order("cron_at DESC NULLS LAST").first
|
99
|
+
else
|
100
|
+
jobs.order(created_at: :asc).last
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def display_properties
|
105
|
+
{
|
106
|
+
key: key,
|
107
|
+
class: job_class,
|
108
|
+
cron: schedule,
|
109
|
+
set: display_property(set),
|
110
|
+
args: display_property(args),
|
111
|
+
description: display_property(description),
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
55
115
|
private
|
56
116
|
|
57
117
|
def set_value
|
@@ -63,5 +123,16 @@ module GoodJob # :nodoc:
|
|
63
123
|
value = args || []
|
64
124
|
value.respond_to?(:call) ? value.call : value
|
65
125
|
end
|
126
|
+
|
127
|
+
def display_property(value)
|
128
|
+
case value
|
129
|
+
when NilClass
|
130
|
+
"None"
|
131
|
+
when Proc
|
132
|
+
"Lambda/Callable"
|
133
|
+
else
|
134
|
+
value
|
135
|
+
end
|
136
|
+
end
|
66
137
|
end
|
67
138
|
end
|
@@ -89,11 +89,7 @@ module GoodJob # :nodoc:
|
|
89
89
|
thr_scheduler.create_task(thr_cron_entry)
|
90
90
|
|
91
91
|
Rails.application.executor.wrap do
|
92
|
-
|
93
|
-
CurrentThread.cron_key = thr_cron_entry.key
|
94
|
-
CurrentThread.cron_at = thr_cron_at
|
95
|
-
|
96
|
-
cron_entry.enqueue
|
92
|
+
cron_entry.enqueue(thr_cron_at)
|
97
93
|
end
|
98
94
|
end
|
99
95
|
|
@@ -5,6 +5,15 @@ module GoodJob
|
|
5
5
|
# Thread-local attributes for passing values from Instrumentation.
|
6
6
|
# (Cannot use ActiveSupport::CurrentAttributes because ActiveJob resets it)
|
7
7
|
module CurrentThread
|
8
|
+
# Resettable accessors for thread-local values.
|
9
|
+
ACCESSORS = %i[
|
10
|
+
cron_at
|
11
|
+
cron_key
|
12
|
+
error_on_discard
|
13
|
+
error_on_retry
|
14
|
+
execution
|
15
|
+
].freeze
|
16
|
+
|
8
17
|
# @!attribute [rw] cron_at
|
9
18
|
# @!scope class
|
10
19
|
# Cron At
|
@@ -36,13 +45,20 @@ module GoodJob
|
|
36
45
|
thread_mattr_accessor :execution
|
37
46
|
|
38
47
|
# Resets attributes
|
48
|
+
# @param [Hash] values to assign
|
39
49
|
# @return [void]
|
40
|
-
def self.reset
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
50
|
+
def self.reset(values = {})
|
51
|
+
ACCESSORS.each do |accessor|
|
52
|
+
send("#{accessor}=", values[accessor])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Exports values to hash
|
57
|
+
# @return [Hash]
|
58
|
+
def self.to_h
|
59
|
+
ACCESSORS.each_with_object({}) do |accessor, hash|
|
60
|
+
hash[accessor] = send(accessor)
|
61
|
+
end
|
46
62
|
end
|
47
63
|
|
48
64
|
# @return [String] UUID of the currently executing GoodJob::Execution
|
@@ -60,12 +76,14 @@ module GoodJob
|
|
60
76
|
(Thread.current.name || Thread.current.object_id).to_s
|
61
77
|
end
|
62
78
|
|
79
|
+
# Wrap the yielded block with CurrentThread values and reset after the block
|
80
|
+
# @yield [self]
|
63
81
|
# @return [void]
|
64
82
|
def self.within
|
65
|
-
|
83
|
+
original_values = to_h
|
66
84
|
yield(self)
|
67
85
|
ensure
|
68
|
-
reset
|
86
|
+
reset(original_values)
|
69
87
|
end
|
70
88
|
end
|
71
89
|
end
|
data/lib/good_job/execution.rb
CHANGED
@@ -6,6 +6,7 @@ module GoodJob
|
|
6
6
|
# class Execution < ActiveRecord::Base; end
|
7
7
|
class Execution < Object.const_get(GoodJob.active_record_parent_class)
|
8
8
|
include Lockable
|
9
|
+
include Filterable
|
9
10
|
|
10
11
|
# Raised if something attempts to execute a previously completed Execution again.
|
11
12
|
PreviouslyPerformedError = Class.new(StandardError)
|
@@ -156,24 +157,6 @@ module GoodJob
|
|
156
157
|
end
|
157
158
|
end)
|
158
159
|
|
159
|
-
# Get Jobs in display order with optional keyset pagination.
|
160
|
-
# @!method display_all(after_scheduled_at: nil, after_id: nil)
|
161
|
-
# @!scope class
|
162
|
-
# @param after_scheduled_at [DateTime, String, nil]
|
163
|
-
# Display records scheduled after this time for keyset pagination
|
164
|
-
# @param after_id [Numeric, String, nil]
|
165
|
-
# Display records after this ID for keyset pagination
|
166
|
-
# @return [ActiveRecord::Relation]
|
167
|
-
scope :display_all, (lambda do |after_scheduled_at: nil, after_id: nil|
|
168
|
-
query = order(Arel.sql('COALESCE(scheduled_at, created_at) DESC, id DESC'))
|
169
|
-
if after_scheduled_at.present? && after_id.present?
|
170
|
-
query = query.where(Arel.sql('(COALESCE(scheduled_at, created_at), id) < (:after_scheduled_at, :after_id)'), after_scheduled_at: after_scheduled_at, after_id: after_id)
|
171
|
-
elsif after_scheduled_at.present?
|
172
|
-
query = query.where(Arel.sql('(COALESCE(scheduled_at, created_at)) < (:after_scheduled_at)'), after_scheduled_at: after_scheduled_at)
|
173
|
-
end
|
174
|
-
query
|
175
|
-
end)
|
176
|
-
|
177
160
|
# Finds the next eligible Execution, acquire an advisory lock related to it, and
|
178
161
|
# executes the job.
|
179
162
|
# @return [ExecutionResult, nil]
|
@@ -309,22 +292,24 @@ module GoodJob
|
|
309
292
|
|
310
293
|
# @return [ExecutionResult]
|
311
294
|
def execute
|
312
|
-
GoodJob::CurrentThread.
|
313
|
-
|
295
|
+
GoodJob::CurrentThread.within do |current_thread|
|
296
|
+
current_thread.reset
|
297
|
+
current_thread.execution = self
|
314
298
|
|
315
|
-
|
316
|
-
|
317
|
-
|
299
|
+
# DEPRECATION: Remove deprecated `good_job:` parameter in GoodJob v3
|
300
|
+
ActiveSupport::Notifications.instrument("perform_job.good_job", { good_job: self, execution: self, process_id: current_thread.process_id, thread_name: current_thread.thread_name }) do
|
301
|
+
value = ActiveJob::Base.execute(active_job_data)
|
318
302
|
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
303
|
+
if value.is_a?(Exception)
|
304
|
+
handled_error = value
|
305
|
+
value = nil
|
306
|
+
end
|
307
|
+
handled_error ||= current_thread.error_on_retry || current_thread.error_on_discard
|
324
308
|
|
325
|
-
|
326
|
-
|
327
|
-
|
309
|
+
ExecutionResult.new(value: value, handled_error: handled_error)
|
310
|
+
rescue StandardError => e
|
311
|
+
ExecutionResult.new(value: nil, unhandled_error: e)
|
312
|
+
end
|
328
313
|
end
|
329
314
|
end
|
330
315
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GoodJob
|
3
|
+
# Shared methods for filtering Execution/Job records from the +good_jobs+ table.
|
4
|
+
module Filterable
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
# Get records in display order with optional keyset pagination.
|
9
|
+
# @!method display_all(after_scheduled_at: nil, after_id: nil)
|
10
|
+
# @!scope class
|
11
|
+
# @param after_scheduled_at [DateTime, String, nil]
|
12
|
+
# Display records scheduled after this time for keyset pagination
|
13
|
+
# @param after_id [Numeric, String, nil]
|
14
|
+
# Display records after this ID for keyset pagination
|
15
|
+
# @return [ActiveRecord::Relation]
|
16
|
+
scope :display_all, (lambda do |after_scheduled_at: nil, after_id: nil|
|
17
|
+
query = order(Arel.sql('COALESCE(scheduled_at, created_at) DESC, id DESC'))
|
18
|
+
if after_scheduled_at.present? && after_id.present?
|
19
|
+
query = query.where(Arel.sql('(COALESCE(scheduled_at, created_at), id) < (:after_scheduled_at, :after_id)'), after_scheduled_at: after_scheduled_at, after_id: after_id)
|
20
|
+
elsif after_scheduled_at.present?
|
21
|
+
query = query.where(Arel.sql('(COALESCE(scheduled_at, created_at)) < (:after_scheduled_at)'), after_scheduled_at: after_scheduled_at)
|
22
|
+
end
|
23
|
+
query
|
24
|
+
end)
|
25
|
+
|
26
|
+
# Search records by text query.
|
27
|
+
# @!method search_text(query)
|
28
|
+
# @!scope class
|
29
|
+
# @param query [String]
|
30
|
+
# Search Query
|
31
|
+
# @return [ActiveRecord::Relation]
|
32
|
+
scope :search_text, (lambda do |query|
|
33
|
+
query = query.to_s.strip
|
34
|
+
next if query.blank?
|
35
|
+
|
36
|
+
tsvector = "(to_tsvector('english', serialized_params) || to_tsvector('english', id::text) || to_tsvector('english', COALESCE(error, '')::text))"
|
37
|
+
where("#{tsvector} @@ to_tsquery(?)", query)
|
38
|
+
.order(sanitize_sql_for_order([Arel.sql("ts_rank(#{tsvector}, to_tsquery(?))"), query]) => 'DESC')
|
39
|
+
end)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/good_job/notifier.rb
CHANGED
@@ -25,10 +25,17 @@ module GoodJob # :nodoc:
|
|
25
25
|
max_queue: 1,
|
26
26
|
fallback_policy: :discard,
|
27
27
|
}.freeze
|
28
|
-
# Seconds to wait if database cannot be connected to
|
29
|
-
RECONNECT_INTERVAL = 5
|
30
28
|
# Seconds to block while LISTENing for a message
|
31
29
|
WAIT_INTERVAL = 1
|
30
|
+
# Seconds to wait if database cannot be connected to
|
31
|
+
RECONNECT_INTERVAL = 5
|
32
|
+
# Connection errors that will wait {RECONNECT_INTERVAL} before reconnecting
|
33
|
+
CONNECTION_ERRORS = %w[
|
34
|
+
ActiveRecord::ConnectionNotEstablished
|
35
|
+
ActiveRecord::StatementInvalid
|
36
|
+
PG::UnableToSend
|
37
|
+
PG::Error
|
38
|
+
].freeze
|
32
39
|
|
33
40
|
# @!attribute [r] instances
|
34
41
|
# @!scope class
|
@@ -115,15 +122,18 @@ module GoodJob # :nodoc:
|
|
115
122
|
if thread_error
|
116
123
|
GoodJob.on_thread_error.call(thread_error) if GoodJob.on_thread_error.respond_to?(:call)
|
117
124
|
ActiveSupport::Notifications.instrument("notifier_notify_error.good_job", { error: thread_error })
|
125
|
+
|
126
|
+
connection_error = CONNECTION_ERRORS.any? do |error_string|
|
127
|
+
error_class = error_string.safe_constantize
|
128
|
+
next unless error_class
|
129
|
+
|
130
|
+
thread_error.is_a? error_class
|
131
|
+
end
|
118
132
|
end
|
119
133
|
|
120
134
|
return if shutdown?
|
121
135
|
|
122
|
-
|
123
|
-
listen(delay: RECONNECT_INTERVAL)
|
124
|
-
else
|
125
|
-
listen
|
126
|
-
end
|
136
|
+
listen(delay: connection_error ? RECONNECT_INTERVAL : 0)
|
127
137
|
end
|
128
138
|
|
129
139
|
private
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GoodJob
|
4
|
+
class ProbeServer
|
5
|
+
RACK_SERVER = 'webrick'
|
6
|
+
|
7
|
+
def self.task_observer(time, output, thread_error) # rubocop:disable Lint/UnusedMethodArgument
|
8
|
+
return if thread_error.is_a? Concurrent::CancelledOperationError
|
9
|
+
|
10
|
+
GoodJob.on_thread_error.call(thread_error) if thread_error && GoodJob.on_thread_error.respond_to?(:call)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(port:)
|
14
|
+
@port = port
|
15
|
+
end
|
16
|
+
|
17
|
+
def start
|
18
|
+
@handler = Rack::Handler.get(RACK_SERVER)
|
19
|
+
@future = Concurrent::Future.new(args: [@handler, @port, GoodJob.logger]) do |thr_handler, thr_port, thr_logger|
|
20
|
+
thr_handler.run(self, Port: thr_port, Logger: thr_logger, AccessLog: [])
|
21
|
+
end
|
22
|
+
@future.add_observer(self.class, :task_observer)
|
23
|
+
@future.execute
|
24
|
+
end
|
25
|
+
|
26
|
+
def running?
|
27
|
+
@handler&.instance_variable_get(:@server)&.status == :Running
|
28
|
+
end
|
29
|
+
|
30
|
+
def stop
|
31
|
+
@handler&.shutdown
|
32
|
+
@future&.value # wait for Future to exit
|
33
|
+
end
|
34
|
+
|
35
|
+
def call(env)
|
36
|
+
case Rack::Request.new(env).path
|
37
|
+
when '/', '/status'
|
38
|
+
[200, {}, ["OK"]]
|
39
|
+
when '/status/started'
|
40
|
+
started = GoodJob::Scheduler.instances.any? && GoodJob::Scheduler.instances.all?(&:running?)
|
41
|
+
started ? [200, {}, ["Started"]] : [503, {}, ["Not started"]]
|
42
|
+
when '/status/connected'
|
43
|
+
connected = GoodJob::Scheduler.instances.any? && GoodJob::Scheduler.instances.all?(&:running?) &&
|
44
|
+
GoodJob::Notifier.instances.any? && GoodJob::Notifier.instances.all?(&:listening?)
|
45
|
+
connected ? [200, {}, ["Connected"]] : [503, {}, ["Not connected"]]
|
46
|
+
else
|
47
|
+
[404, {}, ["Not found"]]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/good_job/version.rb
CHANGED