good_job 3.22.0 → 3.23.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 +35 -1
- data/README.md +90 -12
- data/app/helpers/good_job/application_helper.rb +3 -2
- data/app/models/good_job/base_execution.rb +10 -3
- data/app/models/good_job/batch.rb +1 -1
- data/app/views/good_job/shared/_filter.erb +2 -2
- data/app/views/good_job/shared/_navbar.erb +21 -3
- data/app/views/good_job/shared/icons/_globe.html.erb +3 -0
- data/config/locales/ko.yml +243 -0
- data/config/locales/ru.yml +74 -76
- data/lib/generators/good_job/templates/install/migrations/create_good_jobs.rb.erb +2 -0
- data/lib/generators/good_job/templates/update/migrations/11_create_index_good_job_jobs_for_candidate_lookup.rb.erb +19 -0
- data/lib/good_job/adapter.rb +2 -2
- data/lib/good_job/cli.rb +5 -2
- data/lib/good_job/configuration.rb +18 -2
- data/lib/good_job/probe_server/healthcheck_middleware.rb +27 -0
- data/lib/good_job/probe_server/not_found_app.rb +11 -0
- data/lib/good_job/probe_server/simple_handler.rb +83 -0
- data/lib/good_job/probe_server/webrick_handler.rb +28 -0
- data/lib/good_job/probe_server.rb +21 -16
- data/lib/good_job/version.rb +1 -1
- data/lib/good_job.rb +6 -3
- metadata +23 -3
- data/lib/good_job/http_server.rb +0 -77
data/config/locales/ru.yml
CHANGED
@@ -2,55 +2,55 @@
|
|
2
2
|
ru:
|
3
3
|
good_job:
|
4
4
|
actions:
|
5
|
-
destroy:
|
6
|
-
discard:
|
5
|
+
destroy: Удалить
|
6
|
+
discard: Отменить
|
7
7
|
force_discard: Принудительно отменить
|
8
8
|
inspect: Осмотреть
|
9
9
|
reschedule: Перенести
|
10
10
|
retry: Повторить попытку
|
11
11
|
batches:
|
12
12
|
index:
|
13
|
-
older_batches: Старые
|
13
|
+
older_batches: Старые группы заданий
|
14
14
|
pending_migrations: У GoodJob есть ожидающие миграции базы данных.
|
15
|
-
title:
|
15
|
+
title: Группы заданий
|
16
16
|
jobs:
|
17
17
|
actions:
|
18
|
-
confirm_destroy: Вы уверены, что хотите
|
19
|
-
confirm_discard: Вы уверены, что хотите
|
18
|
+
confirm_destroy: Вы уверены, что хотите удалить это задание?
|
19
|
+
confirm_discard: Вы уверены, что хотите отменить это задание?
|
20
20
|
confirm_reschedule: Вы уверены, что хотите перенести это задание?
|
21
21
|
confirm_retry: Вы уверены, что хотите повторить это задание?
|
22
|
-
destroy:
|
22
|
+
destroy: Удалить задание
|
23
23
|
discard: Отменить задание
|
24
24
|
reschedule: Перенести задание
|
25
25
|
retry: Повторить задание
|
26
26
|
title: Действия
|
27
|
-
no_jobs_found:
|
27
|
+
no_jobs_found: Заданий не найдено
|
28
28
|
show:
|
29
29
|
attributes: Атрибуты
|
30
|
-
batched_jobs:
|
31
|
-
callback_jobs:
|
30
|
+
batched_jobs: Группы заданий
|
31
|
+
callback_jobs: Коллбеки
|
32
32
|
table:
|
33
|
-
no_batches_found:
|
33
|
+
no_batches_found: Группы заданий не найдены.
|
34
34
|
cron_entries:
|
35
35
|
actions:
|
36
|
-
confirm_disable: Вы уверены, что хотите отключить эту
|
37
|
-
confirm_enable: Вы уверены, что хотите подтвердить эту
|
38
|
-
confirm_enqueue: Вы уверены, что хотите поставить эту
|
39
|
-
disable: Отключить
|
40
|
-
enable: Включить
|
41
|
-
enqueue: Поставить в очередь
|
36
|
+
confirm_disable: Вы уверены, что хотите отключить эту задачу cron?
|
37
|
+
confirm_enable: Вы уверены, что хотите подтвердить эту задачу cron?
|
38
|
+
confirm_enqueue: Вы уверены, что хотите поставить эту задачу cron в очередь?
|
39
|
+
disable: Отключить задачу cron
|
40
|
+
enable: Включить задачу cron
|
41
|
+
enqueue: Поставить в очередь задачу cron сейчас
|
42
42
|
disable:
|
43
|
-
notice:
|
43
|
+
notice: Задача Cron отключена.
|
44
44
|
enable:
|
45
|
-
notice:
|
45
|
+
notice: Задача Cron включена.
|
46
46
|
enqueue:
|
47
|
-
notice:
|
47
|
+
notice: Задача Cron поставлена в очередь.
|
48
48
|
index:
|
49
49
|
no_cron_schedules_found: Расписания cron не найдены.
|
50
|
-
title: Расписания
|
51
|
-
pending_migrations:
|
50
|
+
title: Расписания cron
|
51
|
+
pending_migrations: У GoodJob есть ожидающие миграции базы данных.
|
52
52
|
show:
|
53
|
-
cron_entry_key: Ключ
|
53
|
+
cron_entry_key: Ключ cron-задания
|
54
54
|
datetime:
|
55
55
|
distance_in_words:
|
56
56
|
about_x_hours:
|
@@ -73,7 +73,7 @@ ru:
|
|
73
73
|
many: почти %{count} лет
|
74
74
|
one: почти 1 год
|
75
75
|
other: почти %{count} лет
|
76
|
-
half_a_minute:
|
76
|
+
half_a_minute: 30 секунд
|
77
77
|
less_than_x_minutes:
|
78
78
|
few: меньше %{count} минут
|
79
79
|
many: меньше %{count} минут
|
@@ -97,7 +97,7 @@ ru:
|
|
97
97
|
x_minutes:
|
98
98
|
few: "%{count} минуты"
|
99
99
|
many: "%{count} минут"
|
100
|
-
one: 1
|
100
|
+
one: 1 минута
|
101
101
|
other: "%{count} минуты"
|
102
102
|
x_months:
|
103
103
|
few: "%{count} месяца"
|
@@ -107,7 +107,7 @@ ru:
|
|
107
107
|
x_seconds:
|
108
108
|
few: "%{count} секунды"
|
109
109
|
many: "%{count} секунд"
|
110
|
-
one: 1
|
110
|
+
one: 1 секунда
|
111
111
|
other: "%{count} секунды"
|
112
112
|
x_years:
|
113
113
|
few: "%{count} года"
|
@@ -121,49 +121,47 @@ ru:
|
|
121
121
|
minutes: "%{min}м %{sec}с"
|
122
122
|
seconds: "%{sec}s"
|
123
123
|
error_event:
|
124
|
-
discarded:
|
124
|
+
discarded: Отклонено
|
125
125
|
handled: Обработано
|
126
126
|
interrupted: Прервано
|
127
127
|
retried: Повторная попытка
|
128
|
-
retry_stopped:
|
129
|
-
unhandled:
|
128
|
+
retry_stopped: Повторная попытка отменена
|
129
|
+
unhandled: Не обработано
|
130
130
|
helpers:
|
131
131
|
relative_time:
|
132
132
|
future: в %{time}
|
133
133
|
past: "%{time} назад"
|
134
134
|
jobs:
|
135
135
|
actions:
|
136
|
-
confirm_destroy: Вы уверены, что хотите
|
137
|
-
confirm_discard: Вы уверены, что хотите
|
138
|
-
confirm_force_discard:
|
139
|
-
|
140
|
-
'
|
136
|
+
confirm_destroy: Вы уверены, что хотите удалить это задание?
|
137
|
+
confirm_discard: Вы уверены, что хотите отменить это задание?
|
138
|
+
confirm_force_discard: Вы уверены, что хотите принудительно отменить это задание? Задание будет помечено как отмененное, но выполняемое в настоящий момент задание не будет остановлено. В случае сбоя отмененное задание не будет повторено.
|
141
139
|
confirm_reschedule: Вы уверены, что хотите перенести задание?
|
142
140
|
confirm_retry: Вы уверены, что хотите повторить задание?
|
143
|
-
destroy:
|
141
|
+
destroy: Удалить задание
|
144
142
|
discard: Отменить задание
|
145
143
|
force_discard: Принудительно отменить задание
|
146
144
|
reschedule: Перенести задание
|
147
145
|
retry: Повторить задание
|
148
146
|
destroy:
|
149
|
-
notice:
|
147
|
+
notice: Задание было удалено
|
150
148
|
discard:
|
151
|
-
notice:
|
149
|
+
notice: Задание было отменено
|
152
150
|
executions:
|
153
151
|
in_queue: в очереди
|
154
152
|
runtime: время выполнения
|
155
|
-
title:
|
153
|
+
title: Выполнение заданий
|
156
154
|
force_discard:
|
157
|
-
notice:
|
155
|
+
notice: Задание было принудительно отменено. Активное задание продолжит выполняться, но не будет повторно запущено в случае сбоя.
|
158
156
|
index:
|
159
|
-
job_pagination: Пагинация
|
160
|
-
older_jobs: Старые
|
157
|
+
job_pagination: Пагинация заданий
|
158
|
+
older_jobs: Старые задания
|
161
159
|
reschedule:
|
162
|
-
notice:
|
160
|
+
notice: Время исполнения задания было перенесено
|
163
161
|
retry:
|
164
162
|
notice: Задание было повторено
|
165
163
|
show:
|
166
|
-
jobs:
|
164
|
+
jobs: Задания
|
167
165
|
table:
|
168
166
|
actions:
|
169
167
|
apply_to_all:
|
@@ -171,40 +169,40 @@ ru:
|
|
171
169
|
many: около %{count} часов
|
172
170
|
one: около 1 часа
|
173
171
|
other: около %{count} часа
|
174
|
-
confirm_destroy_all: Вы уверены, что хотите
|
175
|
-
confirm_discard_all: Вы действительно хотите
|
176
|
-
confirm_reschedule_all: Вы уверены, что хотите изменить
|
172
|
+
confirm_destroy_all: Вы уверены, что хотите удалить выбранные задания?
|
173
|
+
confirm_discard_all: Вы действительно хотите отменить выбранные задания?
|
174
|
+
confirm_reschedule_all: Вы уверены, что хотите изменить время исполнения выбранных заданий?
|
177
175
|
confirm_retry_all: Вы уверены, что хотите повторить выбранные задания?
|
178
|
-
destroy_all:
|
176
|
+
destroy_all: Удалить все
|
179
177
|
discard_all: Отменить все
|
180
178
|
reschedule_all: Перенести все
|
181
179
|
retry_all: Повторить все
|
182
180
|
title: Действия
|
183
|
-
no_jobs_found:
|
181
|
+
no_jobs_found: Заданий не найдено.
|
184
182
|
toggle_actions: Переключить действия
|
185
183
|
toggle_all_jobs: Переключить все задания
|
186
184
|
models:
|
187
185
|
batch:
|
188
186
|
created: Созданный
|
189
187
|
created_at: Создан в
|
190
|
-
discarded:
|
191
|
-
discarded_at:
|
192
|
-
enqueued:
|
193
|
-
enqueued_at:
|
194
|
-
finished:
|
195
|
-
finished_at:
|
196
|
-
jobs:
|
197
|
-
name:
|
188
|
+
discarded: Отмененный
|
189
|
+
discarded_at: Отмененный в
|
190
|
+
enqueued: Запланированный
|
191
|
+
enqueued_at: Запланированный в
|
192
|
+
finished: Завершенный
|
193
|
+
finished_at: Завершенный в
|
194
|
+
jobs: Задание
|
195
|
+
name: Название
|
198
196
|
cron:
|
199
|
-
class:
|
197
|
+
class: Класс
|
200
198
|
last_run: Последний запуск
|
201
199
|
next_scheduled: Следующий по расписанию
|
202
200
|
schedule: Расписание
|
203
201
|
job:
|
204
|
-
arguments:
|
202
|
+
arguments: Параметры
|
205
203
|
attempts: Попытки
|
206
|
-
priority:
|
207
|
-
queue: Очередь
|
204
|
+
priority: Приоритет
|
205
|
+
queue: Очередь исполнения
|
208
206
|
number:
|
209
207
|
format:
|
210
208
|
delimiter: " "
|
@@ -224,7 +222,7 @@ ru:
|
|
224
222
|
unit: ''
|
225
223
|
processes:
|
226
224
|
index:
|
227
|
-
cron_enabled:
|
225
|
+
cron_enabled: Cron включен
|
228
226
|
no_good_job_processes_found: Процессы GoodJob не найдены.
|
229
227
|
process: Процесс
|
230
228
|
schedulers: Планировщики
|
@@ -238,32 +236,32 @@ ru:
|
|
238
236
|
error: Ошибка
|
239
237
|
filter:
|
240
238
|
all: Все
|
241
|
-
all_jobs: Все
|
239
|
+
all_jobs: Все задания
|
242
240
|
all_queues: Все очереди
|
243
|
-
clear:
|
244
|
-
job_name: Название
|
245
|
-
placeholder: Поиск по классу, идентификатору задания, параметрам задания
|
246
|
-
queue_name:
|
241
|
+
clear: Очистить
|
242
|
+
job_name: Название задания
|
243
|
+
placeholder: Поиск по классу, идентификатору задания, параметрам задания или тексту ошибки.
|
244
|
+
queue_name: Название очереди
|
247
245
|
search: Поиск
|
248
246
|
navbar:
|
249
|
-
batches:
|
247
|
+
batches: Пакетные задания
|
250
248
|
cron_schedules: Cron
|
251
|
-
jobs:
|
252
|
-
live_poll:
|
249
|
+
jobs: Задания
|
250
|
+
live_poll: Обновления в реальном времени
|
253
251
|
name: "GoodJob 👍"
|
254
252
|
processes: Процессы
|
255
253
|
theme:
|
256
254
|
auto: Авто
|
257
255
|
dark: Темный
|
258
|
-
light:
|
259
|
-
theme: Тема
|
256
|
+
light: Светлая
|
257
|
+
theme: Тема офрмдления
|
260
258
|
secondary_navbar:
|
261
|
-
inspiration:
|
259
|
+
inspiration: Благодаря вам Good Job становится лучше! Спасибо!
|
262
260
|
last_updated: Последнее обновление
|
263
261
|
status:
|
264
|
-
discarded:
|
262
|
+
discarded: Отменено
|
265
263
|
queued: В очереди
|
266
264
|
retried: Повторная попытка
|
267
|
-
running:
|
268
|
-
scheduled:
|
269
|
-
succeeded:
|
265
|
+
running: Исполняется
|
266
|
+
scheduled: Запланировано
|
267
|
+
succeeded: Успешно
|
@@ -80,6 +80,8 @@ class CreateGoodJobs < ActiveRecord::Migration<%= migration_version %>
|
|
80
80
|
add_index :good_jobs, [:finished_at], where: "retried_good_job_id IS NULL AND finished_at IS NOT NULL", name: :index_good_jobs_jobs_on_finished_at
|
81
81
|
add_index :good_jobs, [:priority, :created_at], order: { priority: "DESC NULLS LAST", created_at: :asc },
|
82
82
|
where: "finished_at IS NULL", name: :index_good_jobs_jobs_on_priority_created_at_when_unfinished
|
83
|
+
add_index :good_jobs, [:priority, :created_at], order: { priority: "ASC NULLS LAST", created_at: :asc },
|
84
|
+
where: "finished_at IS NULL", name: :index_good_job_jobs_for_candidate_lookup
|
83
85
|
add_index :good_jobs, [:batch_id], where: "batch_id IS NOT NULL"
|
84
86
|
add_index :good_jobs, [:batch_callback_id], where: "batch_callback_id IS NOT NULL"
|
85
87
|
add_index :good_jobs, :labels, using: :gin, where: "(labels IS NOT NULL)", name: :index_good_jobs_on_labels
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateIndexGoodJobJobsForCandidateLookup < ActiveRecord::Migration<%= migration_version %>
|
4
|
+
disable_ddl_transaction!
|
5
|
+
|
6
|
+
def change
|
7
|
+
reversible do |dir|
|
8
|
+
dir.up do
|
9
|
+
# Ensure this incremental update migration is idempotent
|
10
|
+
# with monolithic install migration.
|
11
|
+
return if connection.index_name_exists?(:good_jobs, :index_good_job_jobs_for_candidate_lookup)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
add_index :good_jobs, [:priority, :created_at], order: { priority: "ASC NULLS LAST", created_at: :asc },
|
16
|
+
where: "finished_at IS NULL", name: :index_good_job_jobs_for_candidate_lookup,
|
17
|
+
algorithm: :concurrently
|
18
|
+
end
|
19
|
+
end
|
data/lib/good_job/adapter.rb
CHANGED
@@ -49,7 +49,7 @@ module GoodJob
|
|
49
49
|
active_jobs = Array(active_jobs)
|
50
50
|
return 0 if active_jobs.empty?
|
51
51
|
|
52
|
-
Rails.application.
|
52
|
+
Rails.application.executor.wrap do
|
53
53
|
current_time = Time.current
|
54
54
|
executions = active_jobs.map do |active_job|
|
55
55
|
GoodJob::Execution.build_for_enqueue(active_job).tap do |execution|
|
@@ -139,7 +139,7 @@ module GoodJob
|
|
139
139
|
# job there to be enqueued using enqueue_all
|
140
140
|
return if GoodJob::Bulk.capture(active_job, queue_adapter: self)
|
141
141
|
|
142
|
-
Rails.application.
|
142
|
+
Rails.application.executor.wrap do
|
143
143
|
will_execute_inline = execute_inline? && (scheduled_at.nil? || scheduled_at <= Time.current)
|
144
144
|
execution = GoodJob::Execution.enqueue(
|
145
145
|
active_job,
|
data/lib/good_job/cli.rb
CHANGED
@@ -68,7 +68,7 @@ module GoodJob
|
|
68
68
|
method_option :poll_interval,
|
69
69
|
type: :numeric,
|
70
70
|
banner: 'SECONDS',
|
71
|
-
desc: "Interval between polls for available jobs in seconds (env var: GOOD_JOB_POLL_INTERVAL, default:
|
71
|
+
desc: "Interval between polls for available jobs in seconds (env var: GOOD_JOB_POLL_INTERVAL, default: 10)"
|
72
72
|
method_option :max_cache,
|
73
73
|
type: :numeric,
|
74
74
|
banner: 'COUNT',
|
@@ -94,6 +94,9 @@ module GoodJob
|
|
94
94
|
type: :numeric,
|
95
95
|
banner: 'PORT',
|
96
96
|
desc: "Port for http health check (env var: GOOD_JOB_PROBE_PORT, default: nil)"
|
97
|
+
method_option :probe_handler,
|
98
|
+
type: :string,
|
99
|
+
desc: "Use 'webrick' to use WEBrick to handle probe server requests which is Rack compliant, otherwise default server that is not Rack compliant is used. (env var: GOOD_JOB_PROBE_HANDLER, default: nil)"
|
97
100
|
method_option :queue_select_limit,
|
98
101
|
type: :numeric,
|
99
102
|
banner: 'COUNT',
|
@@ -112,7 +115,7 @@ module GoodJob
|
|
112
115
|
systemd.start
|
113
116
|
|
114
117
|
if configuration.probe_port
|
115
|
-
probe_server = GoodJob::ProbeServer.new(port: configuration.probe_port)
|
118
|
+
probe_server = GoodJob::ProbeServer.new(app: configuration.probe_app, port: configuration.probe_port, handler: configuration.probe_handler)
|
116
119
|
probe_server.start
|
117
120
|
end
|
118
121
|
|
@@ -342,10 +342,26 @@ module GoodJob
|
|
342
342
|
end
|
343
343
|
|
344
344
|
# Port of the probe server
|
345
|
-
# @return [nil,Integer]
|
345
|
+
# @return [nil, Integer]
|
346
346
|
def probe_port
|
347
|
-
options[:probe_port] ||
|
347
|
+
(options[:probe_port] ||
|
348
348
|
env['GOOD_JOB_PROBE_PORT']
|
349
|
+
)&.to_i
|
350
|
+
end
|
351
|
+
|
352
|
+
# Probe server handler
|
353
|
+
# @return [nil, Symbol]
|
354
|
+
def probe_handler
|
355
|
+
(options[:probe_handler] ||
|
356
|
+
rails_config[:probe_handler] ||
|
357
|
+
env['GOOD_JOB_PROBE_HANDLER']
|
358
|
+
)&.to_sym
|
359
|
+
end
|
360
|
+
|
361
|
+
# Rack compliant application to be run on the ProbeServer
|
362
|
+
# @return [nil, Class]
|
363
|
+
def probe_app
|
364
|
+
rails_config[:probe_app]
|
349
365
|
end
|
350
366
|
|
351
367
|
def enable_listen_notify
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GoodJob
|
4
|
+
class ProbeServer
|
5
|
+
class HealthcheckMiddleware
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
case Rack::Request.new(env).path
|
12
|
+
when '/', '/status'
|
13
|
+
[200, {}, ["OK"]]
|
14
|
+
when '/status/started'
|
15
|
+
started = GoodJob::Scheduler.instances.any? && GoodJob::Scheduler.instances.all?(&:running?)
|
16
|
+
started ? [200, {}, ["Started"]] : [503, {}, ["Not started"]]
|
17
|
+
when '/status/connected'
|
18
|
+
connected = GoodJob::Scheduler.instances.any? && GoodJob::Scheduler.instances.all?(&:running?) &&
|
19
|
+
GoodJob::Notifier.instances.any? && GoodJob::Notifier.instances.all?(&:connected?)
|
20
|
+
connected ? [200, {}, ["Connected"]] : [503, {}, ["Not connected"]]
|
21
|
+
else
|
22
|
+
@app.call(env)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GoodJob
|
4
|
+
class ProbeServer
|
5
|
+
class SimpleHandler
|
6
|
+
SOCKET_READ_TIMEOUT = 5 # in seconds
|
7
|
+
|
8
|
+
def initialize(app, options = {})
|
9
|
+
@app = app
|
10
|
+
@port = options[:port]
|
11
|
+
@logger = options[:logger]
|
12
|
+
|
13
|
+
@running = Concurrent::AtomicBoolean.new(false)
|
14
|
+
end
|
15
|
+
|
16
|
+
def stop
|
17
|
+
@running.make_false
|
18
|
+
@server&.close
|
19
|
+
end
|
20
|
+
|
21
|
+
def running?
|
22
|
+
@running.true?
|
23
|
+
end
|
24
|
+
|
25
|
+
def build_future
|
26
|
+
Concurrent::Future.new { run }
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def run
|
32
|
+
@running.make_true
|
33
|
+
start_server
|
34
|
+
handle_connections if @running.true?
|
35
|
+
rescue StandardError => e
|
36
|
+
@logger.error "Server encountered an error: #{e}"
|
37
|
+
ensure
|
38
|
+
stop
|
39
|
+
end
|
40
|
+
|
41
|
+
def start_server
|
42
|
+
@server = TCPServer.new('0.0.0.0', @port)
|
43
|
+
rescue StandardError => e
|
44
|
+
@logger.error "Failed to start server: #{e}"
|
45
|
+
@running.make_false
|
46
|
+
end
|
47
|
+
|
48
|
+
def handle_connections
|
49
|
+
while @running.true?
|
50
|
+
begin
|
51
|
+
ready_sockets, = IO.select([@server], nil, nil, SOCKET_READ_TIMEOUT)
|
52
|
+
next unless ready_sockets
|
53
|
+
|
54
|
+
client = @server.accept_nonblock
|
55
|
+
request = client.gets
|
56
|
+
|
57
|
+
if request
|
58
|
+
status, headers, body = @app.call(parse_request(request))
|
59
|
+
respond(client, status, headers, body)
|
60
|
+
end
|
61
|
+
|
62
|
+
client.close
|
63
|
+
rescue IO::WaitReadable, Errno::EINTR, Errno::EPIPE
|
64
|
+
retry
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def parse_request(request)
|
70
|
+
method, full_path = request.split
|
71
|
+
path, query = full_path.split('?')
|
72
|
+
{ 'REQUEST_METHOD' => method, 'PATH_INFO' => path, 'QUERY_STRING' => query || '' }
|
73
|
+
end
|
74
|
+
|
75
|
+
def respond(client, status, headers, body)
|
76
|
+
client.write "HTTP/1.1 #{status}\r\n"
|
77
|
+
headers.each { |key, value| client.write "#{key}: #{value}\r\n" }
|
78
|
+
client.write "\r\n"
|
79
|
+
body.each { |part| client.write part.to_s }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GoodJob
|
4
|
+
class ProbeServer
|
5
|
+
class WebrickHandler
|
6
|
+
def initialize(app, options = {})
|
7
|
+
@app = app
|
8
|
+
@port = options[:port]
|
9
|
+
@logger = options[:logger]
|
10
|
+
@handler = ::Rack::Handler.get('webrick')
|
11
|
+
end
|
12
|
+
|
13
|
+
def stop
|
14
|
+
@handler&.shutdown
|
15
|
+
end
|
16
|
+
|
17
|
+
def running?
|
18
|
+
@handler&.instance_variable_get(:@server)&.status == :Running
|
19
|
+
end
|
20
|
+
|
21
|
+
def build_future
|
22
|
+
Concurrent::Future.new(args: [@handler, @port, GoodJob.logger]) do |thr_handler, thr_port, thr_logger|
|
23
|
+
thr_handler.run(@app, Port: thr_port, Host: '0.0.0.0', Logger: thr_logger, AccessLog: [])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -8,13 +8,20 @@ module GoodJob
|
|
8
8
|
GoodJob._on_thread_error(thread_error) if thread_error
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
12
|
-
|
11
|
+
def self.default_app
|
12
|
+
::Rack::Builder.new do
|
13
|
+
use GoodJob::ProbeServer::HealthcheckMiddleware
|
14
|
+
run GoodJob::ProbeServer::NotFoundApp
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(port:, handler: nil, app: nil)
|
19
|
+
app ||= self.class.default_app
|
20
|
+
@handler = build_handler(port: port, handler: handler, app: app)
|
13
21
|
end
|
14
22
|
|
15
23
|
def start
|
16
|
-
@
|
17
|
-
@future = Concurrent::Future.new { @handler.run }
|
24
|
+
@future = @handler.build_future
|
18
25
|
@future.add_observer(self.class, :task_observer)
|
19
26
|
@future.execute
|
20
27
|
end
|
@@ -28,19 +35,17 @@ module GoodJob
|
|
28
35
|
@future&.value # wait for Future to exit
|
29
36
|
end
|
30
37
|
|
31
|
-
def
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
GoodJob::Notifier.instances.any? && GoodJob::Notifier.instances.all?(&:connected?)
|
41
|
-
connected ? [200, {}, ["Connected"]] : [503, {}, ["Not connected"]]
|
38
|
+
def build_handler(port:, handler:, app:)
|
39
|
+
if handler == :webrick
|
40
|
+
begin
|
41
|
+
require 'webrick'
|
42
|
+
WebrickHandler.new(app, port: port, logger: GoodJob.logger)
|
43
|
+
rescue LoadError
|
44
|
+
GoodJob.logger.warn("WEBrick was requested as the probe server handler, but it's not in the load path. GoodJob doesn't keep WEBrick as a dependency, so you'll have to make sure its added to your Gemfile to make use of it. GoodJob will fallback to its own webserver in the meantime.")
|
45
|
+
SimpleHandler.new(app, port: port, logger: GoodJob.logger)
|
46
|
+
end
|
42
47
|
else
|
43
|
-
|
48
|
+
SimpleHandler.new(app, port: port, logger: GoodJob.logger)
|
44
49
|
end
|
45
50
|
end
|
46
51
|
end
|
data/lib/good_job/version.rb
CHANGED
data/lib/good_job.rb
CHANGED
@@ -32,8 +32,11 @@ require "good_job/log_subscriber"
|
|
32
32
|
require "good_job/multi_scheduler"
|
33
33
|
require "good_job/notifier"
|
34
34
|
require "good_job/poller"
|
35
|
-
require "good_job/http_server"
|
36
35
|
require "good_job/probe_server"
|
36
|
+
require "good_job/probe_server/healthcheck_middleware"
|
37
|
+
require "good_job/probe_server/not_found_app"
|
38
|
+
require "good_job/probe_server/simple_handler"
|
39
|
+
require "good_job/probe_server/webrick_handler"
|
37
40
|
require "good_job/scheduler"
|
38
41
|
require "good_job/shared_executor"
|
39
42
|
require "good_job/systemd_service"
|
@@ -247,7 +250,7 @@ module GoodJob
|
|
247
250
|
loop do
|
248
251
|
break if limit && iteration >= limit
|
249
252
|
|
250
|
-
result = Rails.application.
|
253
|
+
result = Rails.application.executor.wrap { job_performer.next }
|
251
254
|
break unless result
|
252
255
|
raise result.unhandled_error if result.unhandled_error
|
253
256
|
|
@@ -273,7 +276,7 @@ module GoodJob
|
|
273
276
|
def self.migrated?
|
274
277
|
# Always update with the most recent migration check
|
275
278
|
GoodJob::Execution.reset_column_information
|
276
|
-
GoodJob::Execution.
|
279
|
+
GoodJob::Execution.candidate_lookup_index_migrated?
|
277
280
|
end
|
278
281
|
|
279
282
|
ActiveSupport.run_load_hooks(:good_job, self)
|