good_job 3.12.5 → 3.12.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,47 +1,45 @@
1
1
  # frozen_string_literal: true
2
2
  module GoodJob
3
3
  class AssetsController < ActionController::Base # rubocop:disable Rails/ApplicationController
4
- skip_before_action :verify_authenticity_token, raise: false
4
+ skip_after_action :verify_same_origin_request
5
+
6
+ STATIC_ASSETS = {
7
+ css: {
8
+ bootstrap: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "bootstrap", "bootstrap.min.css"),
9
+ style: GoodJob::Engine.root.join("app", "assets", "good_job", "style.css"),
10
+ },
11
+ js: {
12
+ bootstrap: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "bootstrap", "bootstrap.bundle.min.js"),
13
+ chartjs: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "chartjs", "chart.min.js"),
14
+ es_module_shims: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "es_module_shims.js"),
15
+ rails_ujs: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "rails_ujs.js"),
16
+ },
17
+ }.freeze
18
+
19
+ MODULE_OVERRIDES = {
20
+ application: GoodJob::Engine.root.join("app", "assets", "good_job", "application.js"),
21
+ stimulus: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "stimulus.js"),
22
+ }.freeze
5
23
 
6
24
  def self.js_modules
7
25
  @_js_modules ||= GoodJob::Engine.root.join("app", "assets", "good_job", "modules").children.select(&:file?).each_with_object({}) do |file, modules|
8
26
  key = File.basename(file.basename.to_s, ".js").to_sym
9
27
  modules[key] = file
10
- end
28
+ end.merge(MODULE_OVERRIDES)
11
29
  end
12
30
 
13
31
  before_action do
14
32
  expires_in 1.year, public: true
15
33
  end
16
34
 
17
- def es_module_shims_js
18
- render file: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "es_module_shims.js")
35
+ def static
36
+ render file: STATIC_ASSETS.dig(params[:format].to_sym, params[:name].to_sym) || raise(ActionController::RoutingError, 'Not Found')
19
37
  end
20
38
 
21
- def bootstrap_css
22
- render file: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "bootstrap", "bootstrap.min.css")
23
- end
24
-
25
- def bootstrap_js
26
- render file: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "bootstrap", "bootstrap.bundle.min.js")
27
- end
28
-
29
- def chartjs_js
30
- render file: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "chartjs", "chart.min.js")
31
- end
32
-
33
- def rails_ujs_js
34
- render file: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "rails_ujs.js")
35
- end
36
-
37
- def style_css
38
- render file: GoodJob::Engine.root.join("app", "assets", "good_job", "style.css")
39
- end
39
+ def module
40
+ raise(ActionController::RoutingError, 'Not Found') if params[:format] != "js"
40
41
 
41
- def modules_js
42
- module_name = params[:module].to_sym
43
- module_file = self.class.js_modules.fetch(module_name) { raise ActionController::RoutingError, 'Not Found' }
44
- render file: module_file
42
+ render file: self.class.js_modules[params[:name].to_sym] || raise(ActionController::RoutingError, 'Not Found')
45
43
  end
46
44
  end
47
45
  end
@@ -8,16 +8,16 @@
8
8
  <%= csp_meta_tag %>
9
9
 
10
10
  <%# Do not use asset tag helpers to avoid paths being overriden by config.asset_host %>
11
- <%= tag.link rel: "stylesheet", media: "screen", href: bootstrap_path(format: :css, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
12
- <%= tag.link rel: "stylesheet", media: "screen", href: style_path(format: :css, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
11
+ <%= tag.link rel: "stylesheet", href: static_asset_path(:bootstrap, format: :css, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
12
+ <%= tag.link rel: "stylesheet", href: static_asset_path(:style, format: :css, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
13
13
 
14
- <%= tag.script "", src: bootstrap_path(format: :js, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
15
- <%= tag.script "", src: chartjs_path(format: :js, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
16
- <%= tag.script "", src: rails_ujs_path(format: :js, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
14
+ <%= tag.script "", src: static_asset_path(:bootstrap, format: :js, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
15
+ <%= tag.script "", src: static_asset_path(:chartjs, format: :js, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
16
+ <%= tag.script "", src: static_asset_path(:rails_ujs, format: :js, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
17
17
 
18
- <%= tag.script "", src: es_module_shims_path(format: :js, v: GoodJob::VERSION, locale: nil), async: true, nonce: content_security_policy_nonce %>
19
- <% importmaps = { imports: GoodJob::AssetsController.js_modules.keys.index_with { |module_name| modules_path(module_name, format: :js, locale: nil, v: GoodJob::VERSION) } } %>
20
- <%= tag.script importmaps.to_json.html_safe, type: "importmap", nonce: content_security_policy_nonce %>
18
+ <%= tag.script "", src: static_asset_path(:es_module_shims, format: :js, v: GoodJob::VERSION, locale: nil), async: true, nonce: content_security_policy_nonce %>
19
+ <% importmaps = GoodJob::AssetsController.js_modules.keys.index_with { |module_name| module_asset_path(module_name, format: :js, locale: nil, v: GoodJob::VERSION) } %>
20
+ <%= tag.script({ imports: importmaps }.to_json.html_safe, type: "importmap", nonce: content_security_policy_nonce) %>
21
21
  <%= tag.script "", type: "module", nonce: content_security_policy_nonce do %> import "application"; <% end %>
22
22
  </head>
23
23
  <body>
data/config/routes.rb CHANGED
@@ -28,17 +28,7 @@ GoodJob::Engine.routes.draw do
28
28
  resources :processes, only: %i[index]
29
29
 
30
30
  scope :assets, controller: :assets do
31
- constraints(format: :css) do
32
- get :bootstrap, action: :bootstrap_css
33
- get :style, action: :style_css
34
- end
35
-
36
- constraints(format: :js) do
37
- get :bootstrap, action: :bootstrap_js
38
- get :chartjs, action: :chartjs_js
39
- get :rails_ujs, action: :rails_ujs_js
40
- get :es_module_shims, action: :es_module_shims_js
41
- get "modules/:module", action: :modules_js, as: :modules
42
- end
31
+ get "modules/:name", action: :module, as: :module_asset, constraints: { format: 'js' }
32
+ get "static/:name", action: :static, as: :static_asset, constraints: { format: %w[css js] }
43
33
  end
44
34
  end
@@ -24,9 +24,10 @@ module GoodJob
24
24
  # -+test+: +:inline+
25
25
  # - +production+ and all other environments: +:external+
26
26
  #
27
- def initialize(execution_mode: nil)
27
+ def initialize(execution_mode: nil, _capsule: GoodJob.capsule) # rubocop:disable Lint/UnderscorePrefixedVariableName
28
28
  @_execution_mode_override = execution_mode
29
29
  GoodJob::Configuration.validate_execution_mode(@_execution_mode_override) if @_execution_mode_override
30
+ @capsule = _capsule
30
31
 
31
32
  self.class.instances << self
32
33
  start_async if GoodJob.async_ready?
@@ -98,7 +99,7 @@ module GoodJob
98
99
  state = { queue_name: queue_name, count: executions_by_queue_and_scheduled_at.size }
99
100
  state[:scheduled_at] = scheduled_at if scheduled_at
100
101
 
101
- executed_locally = execute_async? && @scheduler&.create_thread(state)
102
+ executed_locally = execute_async? && @capsule&.create_thread(state)
102
103
  unless executed_locally
103
104
  state[:count] = job_id_to_active_jobs.values_at(*executions_by_queue_and_scheduled_at.map(&:active_job_id)).count { |active_job| send_notify?(active_job) }
104
105
  Notifier.notify(state) unless state[:count].zero?
@@ -141,7 +142,7 @@ module GoodJob
141
142
  job_state = { queue_name: execution.queue_name }
142
143
  job_state[:scheduled_at] = execution.scheduled_at if execution.scheduled_at
143
144
 
144
- executed_locally = execute_async? && @scheduler&.create_thread(job_state)
145
+ executed_locally = execute_async? && @capsule&.create_thread(job_state)
145
146
  Notifier.notify(job_state) if !executed_locally && send_notify?(active_job)
146
147
  end
147
148
 
@@ -150,20 +151,13 @@ module GoodJob
150
151
 
151
152
  # Shut down the thread pool executors.
152
153
  # @param timeout [nil, Numeric, Symbol] Seconds to wait for active threads.
153
- # * +nil+, the scheduler will trigger a shutdown but not wait for it to complete.
154
- # * +-1+, the scheduler will wait until the shutdown is complete.
155
- # * +0+, the scheduler will immediately shutdown and stop any threads.
154
+ # * +nil+ trigger a shutdown but not wait for it to complete.
155
+ # * +-1+ wait until the shutdown is complete.
156
+ # * +0+ immediately shutdown and stop any threads.
156
157
  # * A positive number will wait that many seconds before stopping any remaining active threads.
157
158
  # @return [void]
158
159
  def shutdown(timeout: :default)
159
- timeout = if timeout == :default
160
- GoodJob.configuration.shutdown_timeout
161
- else
162
- timeout
163
- end
164
-
165
- executables = [@notifier, @poller, @scheduler].compact
166
- GoodJob._shutdown_all(executables, timeout: timeout)
160
+ @capsule&.shutdown(timeout: timeout)
167
161
  @_async_started = false
168
162
  end
169
163
 
@@ -199,14 +193,7 @@ module GoodJob
199
193
  def start_async
200
194
  return unless execute_async?
201
195
 
202
- @notifier = GoodJob::Notifier.new(enable_listening: GoodJob.configuration.enable_listen_notify)
203
- @poller = GoodJob::Poller.new(poll_interval: GoodJob.configuration.poll_interval)
204
- @scheduler = GoodJob::Scheduler.from_configuration(GoodJob.configuration, warm_cache_on_initialize: true)
205
- @notifier.recipients << [@scheduler, :create_thread]
206
- @poller.recipients << [@scheduler, :create_thread]
207
-
208
- @cron_manager = GoodJob::CronManager.new(GoodJob.configuration.cron_entries, start_on_initialize: true) if GoodJob.configuration.enable_cron?
209
-
196
+ @capsule.start
210
197
  @_async_started = true
211
198
  end
212
199
 
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+ module GoodJob
3
+ # A GoodJob::Capsule contains the resources necessary to execute jobs, including
4
+ # a {GoodJob::Scheduler}, {GoodJob::Poller}, {GoodJob::Notifier}, and {GoodJob::CronManager}.
5
+ # GoodJob creates a default capsule on initialization.
6
+ class Capsule
7
+ # @!attribute [r] instances
8
+ # @!scope class
9
+ # List of all instantiated Capsules in the current process.
10
+ # @return [Array<GoodJob::Capsule>, nil]
11
+ cattr_reader :instances, default: [], instance_reader: false
12
+
13
+ # @param configuration [GoodJob::Configuration] Configuration to use for this capsule.
14
+ def initialize(configuration: GoodJob.configuration)
15
+ self.class.instances << self
16
+ @configuration = configuration
17
+
18
+ @autostart = true
19
+ @running = false
20
+ @mutex = Mutex.new
21
+ end
22
+
23
+ # Start executing jobs (if not already running).
24
+ def start
25
+ return if @running
26
+
27
+ @mutex.synchronize do
28
+ return if @running
29
+
30
+ @notifier = GoodJob::Notifier.new(enable_listening: @configuration.enable_listen_notify)
31
+ @poller = GoodJob::Poller.new(poll_interval: @configuration.poll_interval)
32
+ @scheduler = GoodJob::Scheduler.from_configuration(@configuration, warm_cache_on_initialize: true)
33
+ @notifier.recipients << [@scheduler, :create_thread]
34
+ @poller.recipients << [@scheduler, :create_thread]
35
+
36
+ @cron_manager = GoodJob::CronManager.new(@configuration.cron_entries, start_on_initialize: true) if @configuration.enable_cron?
37
+
38
+ @autostart = false
39
+ @running = true
40
+ end
41
+ end
42
+
43
+ # Shut down the thread pool executors.
44
+ # @param timeout [nil, Numeric, Symbol] Seconds to wait for active threads.
45
+ # * +-1+ will wait for all active threads to complete.
46
+ # * +0+ will interrupt active threads.
47
+ # * +N+ will wait at most N seconds and then interrupt active threads.
48
+ # * +nil+ will trigger a shutdown but not wait for it to complete.
49
+ # @return [void]
50
+ def shutdown(timeout: :default)
51
+ timeout = timeout == :default ? @configuration.shutdown_timeout : timeout
52
+ GoodJob._shutdown_all([@notifier, @poller, @scheduler, @cron_manager].compact, timeout: timeout)
53
+ @autostart = false
54
+ @running = false
55
+ end
56
+
57
+ # Shutdown and then start the capsule again.
58
+ # @param timeout [nil, Numeric, Symbol] Seconds to wait for active threads.
59
+ # @return [void]
60
+ def restart(timeout: :default)
61
+ shutdown(timeout: timeout)
62
+ start
63
+ end
64
+
65
+ # @return [Boolean] Whether the capsule is currently running.
66
+ def running?
67
+ @running
68
+ end
69
+
70
+ # @return [Boolean] Whether the capsule has been shutdown.
71
+ def shutdown?
72
+ [@notifier, @poller, @scheduler, @cron_manager].compact.all?(&:shutdown?)
73
+ end
74
+
75
+ # Creates an execution thread(s) with the given attributes.
76
+ # @param job_state [Hash, nil] See {GoodJob::Scheduler#create_thread}.
77
+ # @return [Boolean, nil] Whether work was started.
78
+ def create_thread(job_state = nil)
79
+ start if !running? && @autostart
80
+ @scheduler&.create_thread(job_state)
81
+ end
82
+ end
83
+ end
data/lib/good_job/cli.rb CHANGED
@@ -95,13 +95,8 @@ module GoodJob
95
95
 
96
96
  Daemon.new(pidfile: configuration.pidfile).daemonize if configuration.daemonize?
97
97
 
98
- notifier = GoodJob::Notifier.new(enable_listening: GoodJob.configuration.enable_listen_notify)
99
- poller = GoodJob::Poller.new(poll_interval: configuration.poll_interval)
100
- scheduler = GoodJob::Scheduler.from_configuration(configuration, warm_cache_on_initialize: true)
101
- notifier.recipients << [scheduler, :create_thread]
102
- poller.recipients << [scheduler, :create_thread]
103
-
104
- cron_manager = GoodJob::CronManager.new(configuration.cron_entries, start_on_initialize: true) if configuration.enable_cron?
98
+ capsule = GoodJob::Capsule.new
99
+ capsule.start
105
100
 
106
101
  if configuration.probe_port
107
102
  probe_server = GoodJob::ProbeServer.new(port: configuration.probe_port)
@@ -115,11 +110,10 @@ module GoodJob
115
110
 
116
111
  Kernel.loop do
117
112
  sleep 0.1
118
- break if @stop_good_job_executable || scheduler.shutdown? || notifier.shutdown?
113
+ break if @stop_good_job_executable || capsule.shutdown?
119
114
  end
120
115
 
121
- executors = [notifier, poller, cron_manager, scheduler].compact
122
- GoodJob._shutdown_all(executors, timeout: configuration.shutdown_timeout)
116
+ capsule.shutdown(timeout: configuration.shutdown_timeout)
123
117
  probe_server&.stop
124
118
  end
125
119
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module GoodJob
3
3
  # GoodJob gem version.
4
- VERSION = '3.12.5'
4
+ VERSION = '3.12.6'
5
5
  end
data/lib/good_job.rb CHANGED
@@ -15,6 +15,7 @@ require "good_job/active_job_extensions/notify_options"
15
15
 
16
16
  require "good_job/assignable_connection"
17
17
  require "good_job/bulk"
18
+ require "good_job/capsule"
18
19
  require "good_job/cleanup_tracker"
19
20
  require "good_job/cli"
20
21
  require "good_job/configuration"
@@ -91,6 +92,12 @@ module GoodJob
91
92
  # @return [GoodJob::Configuration, nil]
92
93
  mattr_accessor :configuration, default: GoodJob::Configuration.new({})
93
94
 
95
+ # @!attribute [rw] capsule
96
+ # @!scope class
97
+ # Global/default execution capsule for GoodJob.
98
+ # @return [GoodJob::Capsule, nil]
99
+ mattr_accessor :capsule, default: GoodJob::Capsule.new(configuration: configuration)
100
+
94
101
  # Called with exception when a GoodJob thread raises an exception
95
102
  # @param exception [Exception] Exception that was raised
96
103
  # @return [void]
@@ -108,16 +115,15 @@ module GoodJob
108
115
  # * +-1+, the scheduler will wait until the shutdown is complete.
109
116
  # * +0+, the scheduler will immediately shutdown and stop any active tasks.
110
117
  # * +1..+, the scheduler will wait that many seconds before stopping any remaining active tasks.
111
- # @param wait [Boolean] whether to wait for shutdown
112
118
  # @return [void]
113
119
  def self.shutdown(timeout: -1)
114
- _shutdown_all(_executables, timeout: timeout)
120
+ _shutdown_all(Capsule.instances, timeout: timeout)
115
121
  end
116
122
 
117
123
  # Tests whether jobs have stopped executing.
118
124
  # @return [Boolean] whether background threads are shut down
119
125
  def self.shutdown?
120
- _executables.all?(&:shutdown?)
126
+ Capsule.instances.all?(&:shutdown?)
121
127
  end
122
128
 
123
129
  # Stops and restarts executing jobs.
@@ -128,7 +134,7 @@ module GoodJob
128
134
  # @param timeout [Numeric, nil] Seconds to wait for active threads to finish.
129
135
  # @return [void]
130
136
  def self.restart(timeout: -1)
131
- _shutdown_all(_executables, :restart, timeout: timeout)
137
+ _shutdown_all(Capsule.instances, :restart, timeout: timeout)
132
138
  end
133
139
 
134
140
  # Sends +#shutdown+ or +#restart+ to executable objects ({GoodJob::Notifier}, {GoodJob::Poller}, {GoodJob::Scheduler}, {GoodJob::MultiScheduler}, {GoodJob::CronManager})
@@ -154,7 +160,7 @@ module GoodJob
154
160
  # analyze or inspect job performance.
155
161
  # If you are preserving job records this way, use this method regularly to
156
162
  # destroy old records and preserve space in your database.
157
- # @params older_than [nil,Numeric,ActiveSupport::Duration] Jobs older than this will be destroyed (default: +86400+).
163
+ # @param older_than [nil,Numeric,ActiveSupport::Duration] Jobs older than this will be destroyed (default: +86400+).
158
164
  # @return [Integer] Number of job execution records and batches that were destroyed.
159
165
  def self.cleanup_preserved_jobs(older_than: nil)
160
166
  older_than ||= GoodJob.configuration.cleanup_preserved_jobs_before_seconds_ago
@@ -194,14 +200,5 @@ module GoodJob
194
200
  end
195
201
  end
196
202
 
197
- def self._executables
198
- [].concat(
199
- CronManager.instances,
200
- Notifier.instances,
201
- Poller.instances,
202
- Scheduler.instances
203
- )
204
- end
205
-
206
203
  ActiveSupport.run_load_hooks(:good_job, self)
207
204
  end
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.12.5
4
+ version: 3.12.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Sheldon
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-02-24 00:00:00.000000000 Z
11
+ date: 2023-03-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -318,7 +318,7 @@ files:
318
318
  - CHANGELOG.md
319
319
  - LICENSE.txt
320
320
  - README.md
321
- - app/assets/good_job/modules/application.js
321
+ - app/assets/good_job/application.js
322
322
  - app/assets/good_job/modules/charts.js
323
323
  - app/assets/good_job/modules/checkbox_toggle.js
324
324
  - app/assets/good_job/modules/document_ready.js
@@ -331,6 +331,7 @@ files:
331
331
  - app/assets/good_job/vendor/chartjs/chart.min.js
332
332
  - app/assets/good_job/vendor/es_module_shims.js
333
333
  - app/assets/good_job/vendor/rails_ujs.js
334
+ - app/assets/good_job/vendor/stimulus.js
334
335
  - app/charts/good_job/scheduled_by_queue_chart.rb
335
336
  - app/controllers/good_job/application_controller.rb
336
337
  - app/controllers/good_job/assets_controller.rb
@@ -408,6 +409,7 @@ files:
408
409
  - lib/good_job/adapter.rb
409
410
  - lib/good_job/assignable_connection.rb
410
411
  - lib/good_job/bulk.rb
412
+ - lib/good_job/capsule.rb
411
413
  - lib/good_job/cleanup_tracker.rb
412
414
  - lib/good_job/cli.rb
413
415
  - lib/good_job/configuration.rb