Skip to content

Commit

Permalink
Rename :async to :async_all; :async_server to :async and set …
Browse files Browse the repository at this point in the history
…as Development environment default; do not poll in async development
  • Loading branch information
bensheldon committed Aug 18, 2021
1 parent ceed57c commit 06bfdb8
Show file tree
Hide file tree
Showing 10 changed files with 83 additions and 71 deletions.
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla
- GoodJob can also be configured to execute jobs within the web server process to save on resources. This is useful for low-workloads when economy is paramount.
```
$ GOOD_JOB_EXECUTION_MODE=async_server rails server
$ GOOD_JOB_EXECUTION_MODE=async rails server
```
Additional configuration is likely necessary, see the reference below for f configuration.
Expand Down Expand Up @@ -211,7 +211,7 @@ Additional configuration can be provided via `config.good_job.OPTION = ...` for
config.active_job.queue_adapter = :good_job
# Configure options individually...
config.good_job.execution_mode = :async_server
config.good_job.execution_mode = :async
config.good_job.max_threads = 5
config.good_job.poll_interval = 30 # seconds
config.good_job.shutdown_timeout = 25 # seconds
Expand All @@ -220,7 +220,7 @@ config.good_job.cron = { example: { cron: '0 * * * *', class: 'ExampleJob' } }
# ...or all at once.
config.good_job = {
execution_mode: :async_server,
execution_mode: :async,
max_threads: 5,
poll_interval: 30,
shutdown_timeout: 25,
Expand All @@ -239,11 +239,11 @@ Available configuration options are:
- `execution_mode` (symbol) specifies how and where jobs should be executed. You can also set this with the environment variable `GOOD_JOB_EXECUTION_MODE`. It can be any one of:
- `:inline` executes jobs immediately in whatever process queued them (usually the web server process). This should only be used in test and development environments.
- `:external` causes the adapter to enqueue jobs, but not execute them. When using this option (the default for production environments), you’ll need to use the command-line tool to actually execute your jobs.
- `:async_server` executes jobs in separate threads within the Rails webserver process (`bundle exec rails server`). It can be more economical for small workloads because you don’t need a separate machine or environment for running your jobs, but if your web server is under heavy load or your jobs require a lot of resources, you should choose `:external` instead. When not in the Rails webserver, jobs will execute in `:external` mode to ensure jobs are not executed within `rails console`, `rails db:migrate`, `rails assets:prepare`, etc.
- `:async` executes jobs in separate threads in _any_ Rails process.
- `max_threads` (integer) sets the maximum number of threads to use when `execution_mode` is set to `:async` or `:async_server`. You can also set this with the environment variable `GOOD_JOB_MAX_THREADS`.
- `queues` (string) determines which queues to execute jobs from when `execution_mode` is set to `:async` or `:async_server`. See the description of `good_job start` for more details on the format of this string. You can also set this with the environment variable `GOOD_JOB_QUEUES`.
- `poll_interval` (integer) sets the number of seconds between polls for jobs when `execution_mode` is set to `:async` or `:async_server`. You can also set this with the environment variable `GOOD_JOB_POLL_INTERVAL`.
- `:async` (or `:async_server`) executes jobs in separate threads within the Rails webserver process (`bundle exec rails server`). It can be more economical for small workloads because you don’t need a separate machine or environment for running your jobs, but if your web server is under heavy load or your jobs require a lot of resources, you should choose `:external` instead. When not in the Rails webserver, jobs will execute in `:external` mode to ensure jobs are not executed within `rails console`, `rails db:migrate`, `rails assets:prepare`, etc.
- `:async_all` executes jobs in separate threads in _any_ Rails process.
- `max_threads` (integer) sets the maximum number of threads to use when `execution_mode` is set to `:async`. You can also set this with the environment variable `GOOD_JOB_MAX_THREADS`.
- `queues` (string) determines which queues to execute jobs from when `execution_mode` is set to `:async`. See the description of `good_job start` for more details on the format of this string. You can also set this with the environment variable `GOOD_JOB_QUEUES`.
- `poll_interval` (integer) sets the number of seconds between polls for jobs when `execution_mode` is set to `:async`. You can also set this with the environment variable `GOOD_JOB_POLL_INTERVAL`.
- `max_cache` (integer) sets the maximum number of scheduled jobs that will be stored in memory to reduce execution latency when also polling for scheduled jobs. Caching 10,000 scheduled jobs uses approximately 20MB of memory. You can also set this with the environment variable `GOOD_JOB_MAX_CACHE`.
- `shutdown_timeout` (float) number of seconds to wait for jobs to finish when shutting down before stopping the thread. Defaults to forever: `-1`. You can also set this with the environment variable `GOOD_JOB_SHUTDOWN_TIMEOUT`.
- `enable_cron` (boolean) whether to run cron process. Defaults to `false`. You can also set this with the environment variable `GOOD_JOB_ENABLE_CRON`.
Expand All @@ -255,7 +255,7 @@ By default, GoodJob configures the following execution modes per environment:
# config/environments/development.rb
config.active_job.queue_adapter = :good_job
config.good_job.execution_mode = :inline
config.good_job.execution_mode = :async
# config/environments/test.rb
config.active_job.queue_adapter = :good_job
Expand Down Expand Up @@ -587,11 +587,11 @@ GoodJob can execute jobs "async" in the same process as the webserver (e.g. `bin
config.active_job.queue_adapter = :good_job
# To change the execution mode
config.good_job.execution_mode = :async_server
config.good_job.execution_mode = :async
# Or with more configuration
config.good_job = {
execution_mode: :async_server,
execution_mode: :async,
max_threads: 4,
poll_interval: 30
}
Expand All @@ -600,7 +600,7 @@ GoodJob can execute jobs "async" in the same process as the webserver (e.g. `bin
- Or, with environment variables:

```bash
$ GOOD_JOB_EXECUTION_MODE=async_server GOOD_JOB_MAX_THREADS=4 GOOD_JOB_POLL_INTERVAL=30 bin/rails server
$ GOOD_JOB_EXECUTION_MODE=async GOOD_JOB_MAX_THREADS=4 GOOD_JOB_POLL_INTERVAL=30 bin/rails server
```

Depending on your application configuration, you may need to take additional steps:
Expand Down
13 changes: 5 additions & 8 deletions lib/good_job/adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,13 @@ module GoodJob
# ActiveJob Adapter.
#
class Adapter
# Valid execution modes.
EXECUTION_MODES = [:async, :async_server, :external, :inline].freeze

# @param execution_mode [Symbol, nil] specifies how and where jobs should be executed. You can also set this with the environment variable +GOOD_JOB_EXECUTION_MODE+.
#
# - +:inline+ executes jobs immediately in whatever process queued them (usually the web server process). This should only be used in test and development environments.
# - +:external+ causes the adapter to enqueue jobs, but not execute them. When using this option (the default for production environments), you'll need to use the command-line tool to actually execute your jobs.
# - +:async_server+ executes jobs in separate threads within the Rails webserver process (`bundle exec rails server`). It can be more economical for small workloads because you don't need a separate machine or environment for running your jobs, but if your web server is under heavy load or your jobs require a lot of resources, you should choose +:external+ instead.
# - +:async+ (or +:async_server+) executes jobs in separate threads within the Rails webserver process (`bundle exec rails server`). It can be more economical for small workloads because you don't need a separate machine or environment for running your jobs, but if your web server is under heavy load or your jobs require a lot of resources, you should choose +:external+ instead.
# When not in the Rails webserver, jobs will execute in +:external+ mode to ensure jobs are not executed within `rails console`, `rails db:migrate`, `rails assets:prepare`, etc.
# - +:async+ executes jobs in any Rails process.
# - +:async_all+ executes jobs in any Rails process.
#
# The default value depends on the Rails environment:
#
Expand Down Expand Up @@ -130,15 +127,15 @@ def shutdown(timeout: :default, wait: nil)
# Whether in +:async+ execution mode.
# @return [Boolean]
def execute_async?
@configuration.execution_mode.in?([:async, :async_all]) ||
@configuration.execution_mode == :async_server && in_server_process?
@configuration.execution_mode == :async_all ||
@configuration.execution_mode.in?([:async, :async_server]) && in_server_process?
end

# Whether in +:external+ execution mode.
# @return [Boolean]
def execute_externally?
@configuration.execution_mode == :external ||
@configuration.execution_mode == :async_server && !in_server_process?
@configuration.execution_mode.in?([:async, :async_server]) && !in_server_process?
end

# Whether in +:inline+ execution mode.
Expand Down
41 changes: 21 additions & 20 deletions lib/good_job/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class Configuration
DEFAULT_MAX_THREADS = 5
# Default number of seconds between polls for jobs
DEFAULT_POLL_INTERVAL = 10
# Default poll interval for async in development environment
DEFAULT_DEVELOPMENT_ASYNC_POLL_INTERVAL = -1
# Default number of threads to use per {Scheduler}
DEFAULT_MAX_CACHE = 10000
# Default number of seconds to preserve jobs for {CLI#cleanup_preserved_jobs}
Expand Down Expand Up @@ -58,19 +60,10 @@ def execution_mode
end

if mode
mode_sym = mode.to_sym
if mode_sym == :async
ActiveSupport::Deprecation.warn <<~DEPRECATION
The next major version of GoodJob will redefine the meaning of 'async'
execution mode to be equivalent to 'async_server' and only execute
within the webserver process.
To continue using the v1.0 semantics of 'async', use `async_all` instead.
DEPRECATION
end
mode_sym
elsif Rails.env.development? || Rails.env.test?
mode.to_sym
elsif Rails.env.development?
:async
elsif Rails.env.test?
:inline
else
:external
Expand Down Expand Up @@ -109,12 +102,19 @@ def queue_string
# poll (using this interval) for new queued jobs to execute.
# @return [Integer]
def poll_interval
(
interval = (
options[:poll_interval] ||
rails_config[:poll_interval] ||
env['GOOD_JOB_POLL_INTERVAL'] ||
DEFAULT_POLL_INTERVAL
).to_i
env['GOOD_JOB_POLL_INTERVAL']
)

if interval
interval.to_i
elsif Rails.env.development? && execution_mode.in?([:async, :async_all, :async_server])
DEFAULT_DEVELOPMENT_ASYNC_POLL_INTERVAL
else
DEFAULT_POLL_INTERVAL
end
end

# The maximum number of future-scheduled jobs to store in memory.
Expand Down Expand Up @@ -147,12 +147,13 @@ def shutdown_timeout
def enable_cron
value = ActiveModel::Type::Boolean.new.cast(
options[:enable_cron] ||
rails_config[:enable_cron] ||
env['GOOD_JOB_ENABLE_CRON'] ||
false
rails_config[:enable_cron] ||
env['GOOD_JOB_ENABLE_CRON'] ||
false
)
value && cron.size.positive?
end

alias enable_cron? enable_cron

def cron
Expand Down
4 changes: 2 additions & 2 deletions spec/integration/adapter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def perform(*_args, **_kwargs)

describe 'Async execution mode' do
context 'when Scheduler polling is disabled' do
let(:adapter) { GoodJob::Adapter.new execution_mode: :async, queues: 'mice:2', poll_interval: -1 }
let(:adapter) { GoodJob::Adapter.new execution_mode: :async_all, queues: 'mice:2', poll_interval: -1 }

it 'Jobs are directly handed to the performer, if they match the queues' do
elephant_ajob = TestJob.set(queue: 'elephants').perform_later
Expand All @@ -81,7 +81,7 @@ def perform(*_args, **_kwargs)

it 'invokes the notifier if the job is not locally runnable', skip_if_java: true do
# Create another adapter but do not attach it
elephant_adapter = GoodJob::Adapter.new execution_mode: :async, queues: 'elephants:1', poll_interval: -1
elephant_adapter = GoodJob::Adapter.new execution_mode: :async_all, queues: 'elephants:1', poll_interval: -1
sleep_until { GoodJob::Notifier.instances.all?(&:listening?) }

elephant_ajob = TestJob.set(queue: 'elephants').perform_later
Expand Down
9 changes: 6 additions & 3 deletions spec/lib/active_job/queue_adapters/good_job_adapter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@
before { allow(Rails.env).to receive(:test?).and_return(false) }

context 'when in development environment' do
before { allow(Rails.env).to receive(:development?).and_return(true) }
before do
allow(Rails.env).to receive(:development?).and_return(true)
stub_const("Rails::Server", Class.new)
end

it 'runs inline' do
it 'runs async' do
adapter = described_class.new
expect(adapter.execute_inline?).to eq true
expect(adapter.execute_async?).to eq true
end
end

Expand Down
32 changes: 25 additions & 7 deletions spec/lib/good_job/adapter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
scheduler = instance_double(GoodJob::Scheduler, shutdown: nil, create_thread: nil)
allow(GoodJob::Scheduler).to receive(:new).and_return(scheduler)

adapter = described_class.new(execution_mode: :async, poll_interval: -1)
adapter = described_class.new(execution_mode: :async_all, poll_interval: -1)
adapter.enqueue(active_job)

expect(scheduler).to have_received(:create_thread)
Expand Down Expand Up @@ -80,19 +80,37 @@
end

describe '#execute_async?' do
context 'when execution mode async' do
let(:adapter) { described_class.new(execution_mode: :async) }
context 'when execution mode async_all' do
let(:adapter) { described_class.new(execution_mode: :async_all) }

it 'returns true' do
expect(adapter.execute_async?).to eq true
end
end

context 'when execution mode async_all' do
let(:adapter) { described_class.new(execution_mode: :async_all) }
context 'when execution mode async' do
let(:adapter) { described_class.new(execution_mode: :async) }

it 'returns true' do
expect(adapter.execute_async?).to eq true
context 'when Rails::Server is defined' do
before do
stub_const("Rails::Server", Class.new)
end

it 'returns true' do
expect(adapter.execute_async?).to eq true
expect(adapter.execute_externally?).to eq false
end
end

context 'when Rails::Server is not defined' do
before do
hide_const("Rails::Server")
end

it 'returns false' do
expect(adapter.execute_async?).to eq false
expect(adapter.execute_externally?).to eq true
end
end
end

Expand Down
11 changes: 11 additions & 0 deletions spec/lib/good_job/configuration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@
allow(Rails).to receive(:env) { "development".inquiry }
end

it 'defaults to :inline' do
configuration = described_class.new({})
expect(configuration.execution_mode).to eq :async
end
end

context 'when in test' do
before do
allow(Rails).to receive(:env) { "test".inquiry }
end

it 'defaults to :inline' do
configuration = described_class.new({})
expect(configuration.execution_mode).to eq :inline
Expand Down
2 changes: 1 addition & 1 deletion spec/test_app/config/environments/demo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

Rails.application.configure do
config.active_job.queue_adapter = :good_job
config.good_job.execution_mode = :async_server
config.good_job.execution_mode = :async
config.good_job.poll_interval = 30

config.good_job.enable_cron = true
Expand Down
9 changes: 0 additions & 9 deletions spec/test_app/config/environments/development.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,4 @@
# config.file_watcher = ActiveSupport::EventedFileUpdateChecker

config.active_job.queue_adapter = :good_job
if ENV['GOOD_JOB_EXECUTION_MODE']
config.good_job.execution_mode = ENV['GOOD_JOB_EXECUTION_MODE'].to_sym
else
config.good_job.execution_mode = :async_server
end

if config.good_job.execution_mode.in? [:async_server, :async]
config.good_job.poll_interval = 30
end
end
9 changes: 0 additions & 9 deletions spec/test_app/config/environments/production.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,6 @@
config.active_record.dump_schema_after_migration = false

config.active_job.queue_adapter = :good_job
if ENV['GOOD_JOB_EXECUTION_MODE']
config.good_job.execution_mode = ENV['GOOD_JOB_EXECUTION_MODE'].to_sym
else
config.good_job.execution_mode = :async_server
end

if config.good_job.execution_mode.in? [:async_server, :async]
config.good_job.poll_interval = 30
end

# Inserts middleware to perform automatic connection switching.
# The `database_selector` hash is used to pass options to the DatabaseSelector
Expand Down

0 comments on commit 06bfdb8

Please sign in to comment.