good_job 3.22.0 → 3.23.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 +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)
|