-
-
Notifications
You must be signed in to change notification settings - Fork 200
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
616e070
commit fe1ec5b
Showing
10 changed files
with
227 additions
and
119 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
# frozen_string_literal: true | ||
module GoodJob | ||
# A GoodJob::Capsule contains the resources necessary to execute jobs, including | ||
# a {GoodJob::Scheduler}, {GoodJob::Poller}, {GoodJob::Notifier}, and {GoodJob::CronManager}. | ||
# GoodJob creates a default capsule on initialization. | ||
class Capsule | ||
# @!attribute [r] instances | ||
# @!scope class | ||
# List of all instantiated Capsules in the current process. | ||
# @return [Array<GoodJob::Capsule>, nil] | ||
cattr_reader :instances, default: [], instance_reader: false | ||
|
||
# @param configuration [GoodJob::Configuration] Configuration to use for this capsule. | ||
def initialize(configuration: GoodJob.configuration) | ||
self.class.instances << self | ||
@configuration = configuration | ||
|
||
@autostart = true | ||
@running = false | ||
@mutex = Mutex.new | ||
end | ||
|
||
# Start executing jobs (if not already running). | ||
def start | ||
return if @running | ||
|
||
@mutex.synchronize do | ||
return if @running | ||
|
||
@notifier = GoodJob::Notifier.new(enable_listening: @configuration.enable_listen_notify) | ||
@poller = GoodJob::Poller.new(poll_interval: @configuration.poll_interval) | ||
@scheduler = GoodJob::Scheduler.from_configuration(@configuration, warm_cache_on_initialize: true) | ||
@notifier.recipients << [@scheduler, :create_thread] | ||
@poller.recipients << [@scheduler, :create_thread] | ||
|
||
@cron_manager = GoodJob::CronManager.new(@configuration.cron_entries, start_on_initialize: true) if @configuration.enable_cron? | ||
|
||
@autostart = false | ||
@running = true | ||
end | ||
end | ||
|
||
# Shut down the thread pool executors. | ||
# @param timeout [nil, Numeric, Symbol] Seconds to wait for active threads. | ||
# * +-1+ will wait for all active threads to complete. | ||
# * +0+ will interrupt active threads. | ||
# * +N+ will wait at most N seconds and then interrupt active threads. | ||
# * +nil+ will trigger a shutdown but not wait for it to complete. | ||
# @return [void] | ||
def shutdown(timeout: :default) | ||
timeout = timeout == :default ? @configuration.shutdown_timeout : timeout | ||
GoodJob._shutdown_all([@notifier, @poller, @scheduler, @cron_manager].compact, timeout: timeout) | ||
@autostart = false | ||
@running = false | ||
end | ||
|
||
# Shutdown and then start the capsule again. | ||
# @param timeout [nil, Numeric, Symbol] Seconds to wait for active threads. | ||
# @return [void] | ||
def restart(timeout: :default) | ||
shutdown(timeout: timeout) | ||
start | ||
end | ||
|
||
# @return [Boolean] Whether the capsule is currently running. | ||
def running? | ||
@running | ||
end | ||
|
||
# @return [Boolean] Whether the capsule has been shutdown. | ||
def shutdown? | ||
[@notifier, @poller, @scheduler, @cron_manager].compact.all?(&:shutdown?) | ||
end | ||
|
||
# Creates an execution thread(s) with the given attributes. | ||
# @param job_state [Hash, nil] See {GoodJob::Scheduler#create_thread}. | ||
# @return [Boolean, nil] Whether work was started. | ||
def create_thread(job_state = nil) | ||
start if !running? && @autostart | ||
@scheduler&.create_thread(job_state) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
# frozen_string_literal: true | ||
require 'rails_helper' | ||
|
||
describe GoodJob::Capsule do | ||
describe '#initialize' do | ||
it 'does not start' do | ||
capsule = described_class.new | ||
expect(capsule).not_to be_running | ||
end | ||
end | ||
|
||
describe '#start' do | ||
it 'creates execution objects' do | ||
capsule = described_class.new | ||
expect { capsule.start } | ||
.to change(GoodJob::Notifier.instances, :size).by(1) | ||
.and change(GoodJob::Scheduler.instances, :size).by(1) | ||
.and change(GoodJob::Poller.instances, :size).by(1) | ||
.and change(GoodJob::Poller.instances, :size).by(1) | ||
capsule.shutdown | ||
end | ||
|
||
it 'is safe to call from multiple threads' do | ||
capsule = described_class.new | ||
Array.new(100) { Thread.new { capsule.start } }.each(&:join) | ||
capsule.shutdown | ||
expect(GoodJob::Scheduler.instances.size).to eq 1 | ||
end | ||
end | ||
|
||
describe '#shutdown' do | ||
it 'operates if the capsule has not been started' do | ||
capsule = described_class.new | ||
expect { capsule.shutdown }.not_to raise_error | ||
end | ||
end | ||
|
||
describe '#create_thread' do | ||
it 'passes the job state to the scheduler' do | ||
scheduler = instance_double(GoodJob::Scheduler, create_thread: nil, shutdown?: true, shutdown: nil) | ||
allow(GoodJob::Scheduler).to receive(:new).and_return(scheduler) | ||
job_state = "STATE" | ||
|
||
capsule = described_class.new | ||
capsule.start | ||
capsule.create_thread(job_state) | ||
|
||
expect(scheduler).to have_received(:create_thread).with(job_state) | ||
end | ||
|
||
it 'starts the capsule if it is not running' do | ||
capsule = described_class.new | ||
expect { capsule.create_thread }.to change(capsule, :running?).from(false).to(true) | ||
end | ||
|
||
it 'will not start the capsule if it has been shutdown' do | ||
capsule = described_class.new | ||
capsule.start | ||
capsule.shutdown | ||
expect { capsule.create_thread }.not_to change(capsule, :running?).from(false) | ||
end | ||
|
||
it 'returns nil if the capsule is not running' do | ||
capsule = described_class.new | ||
capsule.shutdown | ||
expect(capsule.create_thread).to be_nil | ||
end | ||
end | ||
end |
Oops, something went wrong.