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.
@@ -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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GoodJob
4
+ class ProbeServer
5
+ module NotFoundApp
6
+ def self.call(_env)
7
+ [404, {}, ["Not found"]]
8
+ end
9
+ end
10
+ end
11
+ 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 initialize(port:)
12
- @port = port
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
- @handler = HttpServer.new(self, port: @port, logger: GoodJob.logger)
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 call(env)
32
- case Rack::Request.new(env).path
33
- when '/', '/status'
34
- [200, {}, ["OK"]]
35
- when '/status/started'
36
- started = GoodJob::Scheduler.instances.any? && GoodJob::Scheduler.instances.all?(&:running?)
37
- started ? [200, {}, ["Started"]] : [503, {}, ["Not started"]]
38
- when '/status/connected'
39
- connected = GoodJob::Scheduler.instances.any? && GoodJob::Scheduler.instances.all?(&:running?) &&
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
- [404, {}, ["Not found"]]
48
+ SimpleHandler.new(app, port: port, logger: GoodJob.logger)
44
49
  end
45
50
  end
46
51
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module GoodJob
4
4
  # GoodJob gem version.
5
- VERSION = '3.22.0'
5
+ VERSION = '3.24.0'
6
6
 
7
7
  # GoodJob version as Gem::Version object
8
8
  GEM_VERSION = Gem::Version.new(VERSION)
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.reloader.wrap { job_performer.next }
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.active_job_id_index_removal_migrated?
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.22.0
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-01-03 00:00:00.000000000 Z
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.4.10
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
@@ -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