Skip to content

Commit 940c6ac

Browse files
authored
Merge pull request #308 from rails/binstub
Add a `bin/jobs` binstub to run the supervisor more easily and remove the `async` mode
2 parents 1b930ff + 2549c53 commit 940c6ac

30 files changed

+341
-533
lines changed

Gemfile.lock

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ PATH
77
concurrent-ruby (>= 1.3.1)
88
fugit (~> 1.11.0)
99
railties (>= 7.1)
10+
thor (~> 1.3.1)
1011

1112
GEM
1213
remote: https://rubygems.org/
@@ -161,7 +162,7 @@ GEM
161162
sqlite3 (1.5.4)
162163
mini_portile2 (~> 2.8.0)
163164
strscan (3.1.0)
164-
thor (1.2.2)
165+
thor (1.3.1)
165166
timeout (0.4.1)
166167
tzinfo (2.0.6)
167168
concurrent-ruby (~> 1.0)

README.md

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Solid Queue is a DB-based queuing backend for [Active Job](https://edgeguides.rubyonrails.org/active_job_basics.html), designed with simplicity and performance in mind.
44

5-
Besides regular job enqueuing and processing, Solid Queue supports delayed jobs, concurrency controls, pausing queues, numeric priorities per job, priorities by queue order, and bulk enqueuing (`enqueue_all` for Active Job's `perform_all_later`). _Improvements to logging and instrumentation, a better CLI tool, a way to run within an existing process in "async" mode, and some way of specifying unique jobs are coming very soon._
5+
Besides regular job enqueuing and processing, Solid Queue supports delayed jobs, concurrency controls, pausing queues, numeric priorities per job, priorities by queue order, and bulk enqueuing (`enqueue_all` for Active Job's `perform_all_later`).
66

77
Solid Queue can be used with SQL databases such as MySQL, PostgreSQL or SQLite, and it leverages the `FOR UPDATE SKIP LOCKED` clause, if available, to avoid blocking and waiting on locks when polling jobs. It relies on Active Job for retries, discarding, error handling, serialization, or delays, and it's compatible with Ruby on Rails multi-threading.
88

@@ -31,9 +31,9 @@ $ bin/rails generate solid_queue:install
3131

3232
This will set `solid_queue` as the Active Job's adapter in production, and will copy the required migration over to your app.
3333

34-
Alternatively, you can add only the migration to your app:
34+
Alternatively, you can skip setting the Active Job's adapter with:
3535
```bash
36-
$ bin/rails solid_queue:install:migrations
36+
$ bin/rails generate solid_queue:install --skip_adapter
3737
```
3838

3939
And set Solid Queue as your Active Job's queue backend manually, in your environment config:
@@ -42,7 +42,7 @@ And set Solid Queue as your Active Job's queue backend manually, in your environ
4242
config.active_job.queue_adapter = :solid_queue
4343
```
4444

45-
Alternatively, you can set only specific jobs to use Solid Queue as their backend if you're migrating from another adapter and want to move jobs progressively:
45+
Or you can set only specific jobs to use Solid Queue as their backend if you're migrating from another adapter and want to move jobs progressively:
4646

4747
```ruby
4848
# app/jobs/my_job.rb
@@ -59,14 +59,14 @@ Finally, you need to run the migrations:
5959
$ bin/rails db:migrate
6060
```
6161

62-
After this, you'll be ready to enqueue jobs using Solid Queue, but you need to start Solid Queue's supervisor to run them.
62+
After this, you'll be ready to enqueue jobs using Solid Queue, but you need to start Solid Queue's supervisor to run them. You can use the provided binstub:`
6363
```bash
64-
$ bundle exec rake solid_queue:start
64+
$ bin/jobs
6565
```
6666

6767
This will start processing jobs in all queues using the default configuration. See [below](#configuration) to learn more about configuring Solid Queue.
6868

69-
For small projects, you can run Solid Queue on the same machine as your webserver. When you're ready to scale, Solid Queue supports horizontal scaling out-of-the-box. You can run Solid Queue on a separate server from your webserver, or even run `bundle exec rake solid_queue:start` on multiple machines at the same time. Depending on the configuration, you can designate some machines to run only dispatchers or only workers. See the [configuration](#configuration) section for more details on this.
69+
For small projects, you can run Solid Queue on the same machine as your webserver. When you're ready to scale, Solid Queue supports horizontal scaling out-of-the-box. You can run Solid Queue on a separate server from your webserver, or even run `bin/jobs` on multiple machines at the same time. Depending on the configuration, you can designate some machines to run only dispatchers or only workers. See the [configuration](#configuration) section for more details on this.
7070

7171
## Requirements
7272
Besides Rails 7.1, Solid Queue works best with MySQL 8+ or PostgreSQL 9.5+, as they support `FOR UPDATE SKIP LOCKED`. You can use it with older versions, but in that case, you might run into lock waits if you run multiple workers for the same queue.
@@ -80,7 +80,7 @@ We have three types of actors in Solid Queue:
8080
- _Dispatchers_ are in charge of selecting jobs scheduled to run in the future that are due and _dispatching_ them, which is simply moving them from the `solid_queue_scheduled_executions` table over to the `solid_queue_ready_executions` table so that workers can pick them up. They're also in charge of managing [recurring tasks](#recurring-tasks), dispatching jobs to process them according to their schedule. On top of that, they do some maintenance work related to [concurrency controls](#concurrency-controls).
8181
- The _supervisor_ runs workers and dispatchers according to the configuration, controls their heartbeats, and stops and starts them when needed.
8282

83-
By default, Solid Queue runs in `fork` mode. This means the supervisor will fork a separate process for each supervised worker/dispatcher. There's also an `async` mode where each worker and dispatcher will be run as a thread of the supervisor process. This can be used with [the provided Puma plugin](#puma-plugin)
83+
Solid Queue's supervisor will fork a separate process for each supervised worker/dispatcher.
8484

8585
By default, Solid Queue will try to find your configuration under `config/solid_queue.yml`, but you can set a different path using the environment variable `SOLID_QUEUE_CONFIG`. This is what this configuration looks like:
8686

@@ -131,7 +131,7 @@ Here's an overview of the different options:
131131

132132
Finally, you can combine prefixes with exact names, like `[ staging*, background ]`, and the behaviour with respect to order will be the same as with only exact names.
133133
- `threads`: this is the max size of the thread pool that each worker will have to run jobs. Each worker will fetch this number of jobs from their queue(s), at most and will post them to the thread pool to be run. By default, this is `3`. Only workers have this setting.
134-
- `processes`: this is the number of worker processes that will be forked by the supervisor with the settings given. By default, this is `1`, just a single process. This setting is useful if you want to dedicate more than one CPU core to a queue or queues with the same configuration. Only workers have this setting. **Note**: this option will be ignored if [running in `async` mode](#running-as-a-fork-or-asynchronously).
134+
- `processes`: this is the number of worker processes that will be forked by the supervisor with the settings given. By default, this is `1`, just a single process. This setting is useful if you want to dedicate more than one CPU core to a queue or queues with the same configuration. Only workers have this setting.
135135
- `concurrency_maintenance`: whether the dispatcher will perform the concurrency maintenance work. This is `true` by default, and it's useful if you don't use any [concurrency controls](#concurrency-controls) and want to disable it or if you run multiple dispatchers and want some of them to just dispatch jobs without doing anything else.
136136
- `recurring_tasks`: a list of recurring tasks the dispatcher will manage. Read more details about this one in the [Recurring tasks](#recurring-tasks) section.
137137

@@ -194,13 +194,13 @@ development:
194194
# ...
195195
```
196196

197-
Install migrations and specify the dedicated database name with the `DATABASE` option. This will create the Solid Queue migration files in a separate directory, matching the value provided in `migrations_paths` in `config/database.yml`.
197+
Install migrations and specify the dedicated database name with the `--database` option. This will create the Solid Queue migration files in a separate directory, matching the value provided in `migrations_paths` in `config/database.yml`.
198198

199199
```bash
200-
$ bin/rails solid_queue:install:migrations DATABASE=solid_queue
200+
$ bin/rails g solid_queue:install --database solid_queue
201201
```
202202

203-
Note: If you've already run the solid queue install command (`bin/rails generate solid_queue:install`), the migration files will have already been generated under the primary database's `db/migrate/` directory. You can remove these files and keep the ones generated by the database-specific migration installation above.
203+
Note: If you've already run the solid queue install command (`bin/rails generate solid_queue:install`) without a `--database` option, the migration files will have already been generated under the primary database's `db/migrate/` directory. You can remove these files and keep the ones generated by the database-specific migration installation above.
204204

205205
Finally, run the migrations:
206206

@@ -305,18 +305,6 @@ plugin :solid_queue
305305
```
306306
to your `puma.rb` configuration.
307307

308-
### Running as a fork or asynchronously
309-
310-
By default, the Puma plugin will fork additional processes for each worker and dispatcher so that they run in different processes. This provides the best isolation and performance, but can have additional memory usage.
311-
312-
Alternatively, workers and dispatchers can be run within the same Puma process(s). To do so just configure the plugin as:
313-
314-
```ruby
315-
plugin :solid_queue
316-
solid_queue_mode :async
317-
```
318-
319-
Note that in this case, the `processes` configuration option will be ignored.
320308

321309
## Jobs and transactional integrity
322310
:warning: Having your jobs in the same ACID-compliant database as your application data enables a powerful yet sharp tool: taking advantage of transactional integrity to ensure some action in your app is not committed unless your job is also committed. This can be very powerful and useful, but it can also backfire if you base some of your logic on this behaviour, and in the future, you move to another active job backend, or if you simply move Solid Queue to its own database, and suddenly the behaviour changes under you.

UPGRADING.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
# Upgrading to version 0.7.x
2+
3+
This version removed the new async mode introduced in version 0.4.0 and introduced a new binstub that can be used to start Solid Queue's supervisor. It includes also a minor migration.
4+
5+
To install both the binstub `bin/jobs` and the migration, you can just run
6+
```
7+
bin/rails generate solid_queue:install
8+
```
9+
10+
Or, if you're using a different database for Solid Queue:
11+
12+
```bash
13+
$ bin/rails generate solid_queue:install --database <the_name_of_your_solid_queue_db>
14+
```
15+
16+
117
# Upgrading to version 0.6.x
218

319
## New migration in 3 steps
@@ -44,7 +60,7 @@ And then run the migrations.
4460

4561

4662
# Upgrading to version 0.4.x
47-
This version introduced an _async_ mode to run the supervisor and have all workers and dispatchers run as part of the same process as the supervisor, instead of separate, forked, processes. Together with this, we introduced some changes in how the supervisor is started. Prior this change, you could choose whether you wanted to run workers, dispatchers or both, by starting Solid Queue as `solid_queue:work` or `solid_queue:dispatch`. From version 0.4.0, the only option available is:
63+
This version introduced an _async_ mode (this mode has been removed in version 0.7.0) to run the supervisor and have all workers and dispatchers run as part of the same process as the supervisor, instead of separate, forked, processes. Together with this, we introduced some changes in how the supervisor is started. Prior this change, you could choose whether you wanted to run workers, dispatchers or both, by starting Solid Queue as `solid_queue:work` or `solid_queue:dispatch`. From version 0.4.0, the only option available is:
4864

4965
```
5066
$ bundle exec rake solid_queue:start

lib/generators/solid_queue/install/USAGE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ Example:
77
This will perform the following:
88
Installs solid_queue migrations
99
Replaces Active Job's adapter in environment configuration
10+
Installs bin/jobs binstub to start the supervisor

lib/generators/solid_queue/install/install_generator.rb

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,33 @@
33
class SolidQueue::InstallGenerator < Rails::Generators::Base
44
source_root File.expand_path("templates", __dir__)
55

6-
class_option :skip_migrations, type: :boolean, default: nil, desc: "Skip migrations"
6+
class_option :skip_adapter, type: :boolean, default: nil, desc: "Skip setting Solid Queue as the Active Job's adapter"
7+
class_option :database, type: :string, default: nil, desc: "The database to use for migrations, if different from the primary one."
78

89
def add_solid_queue
9-
if (env_config = Pathname(destination_root).join("config/environments/production.rb")).exist?
10-
gsub_file env_config, /(# )?config\.active_job\.queue_adapter\s+=.*/, "config.active_job.queue_adapter = :solid_queue"
10+
unless options[:skip_adapter]
11+
if (env_config = Pathname(destination_root).join("config/environments/production.rb")).exist?
12+
say "Setting solid_queue as Active Job's queue adapter"
13+
gsub_file env_config, /(# )?config\.active_job\.queue_adapter\s+=.*/, "config.active_job.queue_adapter = :solid_queue"
14+
end
1115
end
1216

13-
copy_file "config.yml", "config/solid_queue.yml"
17+
if File.exist?("config/solid_queue.yml")
18+
say "Skipping sample configuration as config/solid_queue.yml exists"
19+
else
20+
say "Copying sample configuration"
21+
copy_file "config.yml", "config/solid_queue.yml"
22+
end
23+
24+
say "Copying binstub"
25+
copy_file "jobs", "bin/jobs"
26+
chmod "bin/jobs", 0755 & ~File.umask, verbose: false
1427
end
1528

1629
def create_migrations
17-
unless options[:skip_migrations]
18-
rails_command "railties:install:migrations FROM=solid_queue", inline: true
19-
end
30+
say "Installing database migrations"
31+
arguments = [ "FROM=solid_queue" ]
32+
arguments << "DATABASE=#{options[:database]}" if options[:database].present?
33+
rails_command "railties:install:migrations #{arguments.join(" ")}", inline: true
2034
end
2135
end
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env ruby
2+
3+
require_relative "../config/environment"
4+
require "solid_queue/cli"
5+
6+
SolidQueue::Cli.start(ARGV)

lib/puma/plugin/solid_queue.rb

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,28 @@
11
require "puma/plugin"
22

3-
module Puma
4-
class DSL
5-
def solid_queue_mode(mode = :fork)
6-
@options[:solid_queue_mode] = mode.to_sym
7-
end
8-
end
9-
end
10-
113
Puma::Plugin.create do
124
attr_reader :puma_pid, :solid_queue_pid, :log_writer, :solid_queue_supervisor
135

146
def start(launcher)
157
@log_writer = launcher.log_writer
168
@puma_pid = $$
179

18-
if launcher.options[:solid_queue_mode] == :async
19-
start_async(launcher)
20-
else
21-
start_forked(launcher)
10+
in_background do
11+
monitor_solid_queue
2212
end
23-
end
2413

25-
private
26-
def start_forked(launcher)
27-
in_background do
28-
monitor_solid_queue
14+
launcher.events.on_booted do
15+
@solid_queue_pid = fork do
16+
Thread.new { monitor_puma }
17+
SolidQueue::Supervisor.start
2918
end
30-
31-
launcher.events.on_booted do
32-
@solid_queue_pid = fork do
33-
Thread.new { monitor_puma }
34-
SolidQueue::Supervisor.start(mode: :fork)
35-
end
36-
end
37-
38-
launcher.events.on_stopped { stop_solid_queue }
39-
launcher.events.on_restart { stop_solid_queue }
4019
end
4120

42-
def start_async(launcher)
43-
launcher.events.on_booted { @solid_queue_supervisor = SolidQueue::Supervisor.start(mode: :async) }
44-
launcher.events.on_stopped { solid_queue_supervisor.stop }
45-
launcher.events.on_restart { solid_queue_supervisor.stop; solid_queue_supervisor.start }
46-
end
21+
launcher.events.on_stopped { stop_solid_queue }
22+
launcher.events.on_restart { stop_solid_queue }
23+
end
4724

25+
private
4826
def stop_solid_queue
4927
Process.waitpid(solid_queue_pid, Process::WNOHANG)
5028
log "Stopping Solid Queue..."

lib/solid_queue/cli.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# frozen_string_literal: true
2+
3+
require "thor"
4+
5+
module SolidQueue
6+
class Cli < Thor
7+
class_option :config_file, type: :string, aliases: "-c", default: Configuration::DEFAULT_CONFIG_FILE_PATH, desc: "Path to config file"
8+
9+
def self.exit_on_failure?
10+
true
11+
end
12+
13+
desc :start, "Starts Solid Queue supervisor to dispatch and perform enqueued jobs. Default command."
14+
default_command :start
15+
16+
def start
17+
SolidQueue::Supervisor.start(load_configuration_from: options["config_file"])
18+
end
19+
end
20+
end

lib/solid_queue/configuration.rb

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ def instantiate
2828
dispatchers: [ DISPATCHER_DEFAULTS ]
2929
}
3030

31-
def initialize(mode: :fork, load_from: nil)
32-
@mode = mode.to_s.inquiry
31+
def initialize(load_from: nil)
3332
@raw_config = config_from(load_from)
3433
end
3534

@@ -43,17 +42,13 @@ def max_number_of_threads
4342
end
4443

4544
private
46-
attr_reader :raw_config, :mode
45+
attr_reader :raw_config
4746

4847
DEFAULT_CONFIG_FILE_PATH = "config/solid_queue.yml"
4948

5049
def workers
5150
workers_options.flat_map do |worker_options|
52-
processes = if mode.fork?
53-
worker_options.fetch(:processes, WORKER_DEFAULTS[:processes])
54-
else
55-
WORKER_DEFAULTS[:processes]
56-
end
51+
processes = worker_options.fetch(:processes, WORKER_DEFAULTS[:processes])
5752
processes.times.map { Process.new(:worker, worker_options.with_defaults(WORKER_DEFAULTS)) }
5853
end
5954
end

lib/solid_queue/dispatcher.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def all_work_completed?
5555
end
5656

5757
def set_procline
58-
procline "waiting"
58+
procline "dispatching every #{polling_interval.seconds} seconds"
5959
end
6060
end
6161
end

0 commit comments

Comments
 (0)