good_job 3.22.0 → 3.24.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 +57 -1
- data/README.md +107 -16
- 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/models/good_job/batch_record.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/pt-BR.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/active_job_extensions/concurrency.rb +8 -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 +25 -4
- data/lib/good_job/http_server.rb +0 -77
@@ -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)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: good_job
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.24.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Sheldon
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-02-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|
@@ -206,6 +206,20 @@ dependencies:
|
|
206
206
|
- - ">="
|
207
207
|
- !ruby/object:Gem::Version
|
208
208
|
version: '0'
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: webrick
|
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'
|
209
223
|
- !ruby/object:Gem::Dependency
|
210
224
|
name: yard
|
211
225
|
requirement: !ruby/object:Gem::Requirement
|
@@ -316,6 +330,7 @@ files:
|
|
316
330
|
- app/views/good_job/shared/icons/_dots.html.erb
|
317
331
|
- app/views/good_job/shared/icons/_eject.html.erb
|
318
332
|
- app/views/good_job/shared/icons/_exclamation.html.erb
|
333
|
+
- app/views/good_job/shared/icons/_globe.html.erb
|
319
334
|
- app/views/good_job/shared/icons/_info.html.erb
|
320
335
|
- app/views/good_job/shared/icons/_moon_stars_fill.html.erb
|
321
336
|
- app/views/good_job/shared/icons/_pause.html.erb
|
@@ -330,7 +345,9 @@ files:
|
|
330
345
|
- config/locales/es.yml
|
331
346
|
- config/locales/fr.yml
|
332
347
|
- config/locales/ja.yml
|
348
|
+
- config/locales/ko.yml
|
333
349
|
- config/locales/nl.yml
|
350
|
+
- config/locales/pt-BR.yml
|
334
351
|
- config/locales/ru.yml
|
335
352
|
- config/locales/tr.yml
|
336
353
|
- config/locales/uk.yml
|
@@ -349,6 +366,7 @@ files:
|
|
349
366
|
- lib/generators/good_job/templates/update/migrations/08_create_good_job_labels.rb.erb
|
350
367
|
- lib/generators/good_job/templates/update/migrations/09_create_good_job_labels_index.rb.erb
|
351
368
|
- lib/generators/good_job/templates/update/migrations/10_remove_good_job_active_id_index.rb.erb
|
369
|
+
- lib/generators/good_job/templates/update/migrations/11_create_index_good_job_jobs_for_candidate_lookup.rb.erb
|
352
370
|
- lib/generators/good_job/update_generator.rb
|
353
371
|
- lib/good_job.rb
|
354
372
|
- lib/good_job/active_job_extensions/batches.rb
|
@@ -369,7 +387,6 @@ files:
|
|
369
387
|
- lib/good_job/daemon.rb
|
370
388
|
- lib/good_job/dependencies.rb
|
371
389
|
- lib/good_job/engine.rb
|
372
|
-
- lib/good_job/http_server.rb
|
373
390
|
- lib/good_job/interrupt_error.rb
|
374
391
|
- lib/good_job/job_performer.rb
|
375
392
|
- lib/good_job/job_performer/metrics.rb
|
@@ -379,6 +396,10 @@ files:
|
|
379
396
|
- lib/good_job/notifier/process_heartbeat.rb
|
380
397
|
- lib/good_job/poller.rb
|
381
398
|
- lib/good_job/probe_server.rb
|
399
|
+
- lib/good_job/probe_server/healthcheck_middleware.rb
|
400
|
+
- lib/good_job/probe_server/not_found_app.rb
|
401
|
+
- lib/good_job/probe_server/simple_handler.rb
|
402
|
+
- lib/good_job/probe_server/webrick_handler.rb
|
382
403
|
- lib/good_job/scheduler.rb
|
383
404
|
- lib/good_job/sd_notify.rb
|
384
405
|
- lib/good_job/shared_executor.rb
|
@@ -416,7 +437,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
416
437
|
- !ruby/object:Gem::Version
|
417
438
|
version: '0'
|
418
439
|
requirements: []
|
419
|
-
rubygems_version: 3.
|
440
|
+
rubygems_version: 3.5.3
|
420
441
|
signing_key:
|
421
442
|
specification_version: 4
|
422
443
|
summary: A multithreaded, Postgres-based ActiveJob backend for Ruby on Rails
|
data/lib/good_job/http_server.rb
DELETED
@@ -1,77 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module GoodJob
|
4
|
-
class HttpServer
|
5
|
-
SOCKET_READ_TIMEOUT = 5 # in seconds
|
6
|
-
|
7
|
-
def initialize(app, options = {})
|
8
|
-
@app = app
|
9
|
-
@port = options[:port]
|
10
|
-
@logger = options[:logger]
|
11
|
-
|
12
|
-
@running = Concurrent::AtomicBoolean.new(false)
|
13
|
-
end
|
14
|
-
|
15
|
-
def run
|
16
|
-
@running.make_true
|
17
|
-
start_server
|
18
|
-
handle_connections if @running.true?
|
19
|
-
rescue StandardError => e
|
20
|
-
@logger.error "Server encountered an error: #{e}"
|
21
|
-
ensure
|
22
|
-
stop
|
23
|
-
end
|
24
|
-
|
25
|
-
def stop
|
26
|
-
@running.make_false
|
27
|
-
@server&.close
|
28
|
-
end
|
29
|
-
|
30
|
-
def running?
|
31
|
-
@running.true?
|
32
|
-
end
|
33
|
-
|
34
|
-
private
|
35
|
-
|
36
|
-
def start_server
|
37
|
-
@server = TCPServer.new('0.0.0.0', @port)
|
38
|
-
rescue StandardError => e
|
39
|
-
@logger.error "Failed to start server: #{e}"
|
40
|
-
@running.make_false
|
41
|
-
end
|
42
|
-
|
43
|
-
def handle_connections
|
44
|
-
while @running.true?
|
45
|
-
begin
|
46
|
-
ready_sockets, = IO.select([@server], nil, nil, SOCKET_READ_TIMEOUT)
|
47
|
-
next unless ready_sockets
|
48
|
-
|
49
|
-
client = @server.accept_nonblock
|
50
|
-
request = client.gets
|
51
|
-
|
52
|
-
if request
|
53
|
-
status, headers, body = @app.call(parse_request(request))
|
54
|
-
respond(client, status, headers, body)
|
55
|
-
end
|
56
|
-
|
57
|
-
client.close
|
58
|
-
rescue IO::WaitReadable, Errno::EINTR, Errno::EPIPE
|
59
|
-
retry
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def parse_request(request)
|
65
|
-
method, full_path = request.split
|
66
|
-
path, query = full_path.split('?')
|
67
|
-
{ 'REQUEST_METHOD' => method, 'PATH_INFO' => path, 'QUERY_STRING' => query || '' }
|
68
|
-
end
|
69
|
-
|
70
|
-
def respond(client, status, headers, body)
|
71
|
-
client.write "HTTP/1.1 #{status}\r\n"
|
72
|
-
headers.each { |key, value| client.write "#{key}: #{value}\r\n" }
|
73
|
-
client.write "\r\n"
|
74
|
-
body.each { |part| client.write part.to_s }
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|