riverqueue-rails 0.0.0.reserved → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +189 -5
- data/lib/active_job/queue_adapters/river_adapter.rb +67 -0
- data/lib/generators/river/install_generator.rb +18 -0
- data/lib/generators/river/templates/jobs +8 -0
- data/lib/generators/river/templates/river.rb +19 -0
- data/lib/river/rails/configuration.rb +86 -0
- data/lib/river/rails/railtie.rb +27 -0
- data/lib/river/rails/runner.rb +15 -0
- data/lib/river/rails/worker.rb +46 -0
- data/lib/riverqueue-rails.rb +9 -0
- metadata +70 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 16d36ce8062709aa5462aa2287bf95df03329fcbdd7a1ab145b3d0237b9f3cc3
|
|
4
|
+
data.tar.gz: fb340cbebbc096af53e530ad10cc927fb89c24b3a4b2e0256f2201403cb0b968
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1b2948a1bf84ced265b5b7e808060a8c172b7b8c1beac854b02ee5de6b5a432f5c328436cc3b8e5cf96bdb74c77ff766e2f17c4eb2f2077361e5f2f0e92bc3d9
|
|
7
|
+
data.tar.gz: 7c4e3718bbc6760a68d9f1cd58afaf5a4f584bc5b142a20748b4dbfb5e611644a6b0385834a35c5e429e226cb4350506e8c0b76accb2f75498a169f942fe2ae5
|
data/README.md
CHANGED
|
@@ -1,7 +1,191 @@
|
|
|
1
|
-
#
|
|
1
|
+
# River for Rails
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
It
|
|
5
|
-
|
|
3
|
+
`riverqueue-rails` integrates Active Job with River's existing PostgreSQL/SQLite
|
|
4
|
+
runtime. It supports Rails 7.2, 8.0, and 8.1 and lives separately from the
|
|
5
|
+
Rails-independent core gem. Database gems remain application-selected.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem "riverqueue-rails"
|
|
11
|
+
gem "pg" # Or sqlite3.
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
bin/rails generate river:install
|
|
16
|
+
bin/rails river:migrate
|
|
17
|
+
bin/jobs start
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The generator installs `config/initializers/river.rb` and `bin/jobs`. Migration
|
|
21
|
+
tasks use the canonical bundled Go SQL through `River::Migrator`; no separate
|
|
22
|
+
Rails schema is created. `bin/rails river:status` shows migration status.
|
|
23
|
+
Migrate explicitly during deployment, not on every application boot.
|
|
24
|
+
|
|
25
|
+
## Configuration
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
Rails.application.configure do
|
|
29
|
+
config.active_job.queue_adapter = :river unless Rails.env.test?
|
|
30
|
+
config.river.stop_timeout = 30
|
|
31
|
+
|
|
32
|
+
config.river.configure do
|
|
33
|
+
River::Config.new(
|
|
34
|
+
job_timeout: 300,
|
|
35
|
+
logger: Rails.logger,
|
|
36
|
+
queues: {default: 10, mailers: 5}
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The block runs lazily after application boot and again when constructing a
|
|
43
|
+
consumer. It must return a fresh `River::Config`. The integration registers the
|
|
44
|
+
reserved `active_job` worker and installs Rails execution wrapping automatically.
|
|
45
|
+
Web processes only insert; `river worker --rails` or `bin/jobs start` starts consumers. Configure all
|
|
46
|
+
actual queue names, including Active Job queue prefixes and Action Mailer queues.
|
|
47
|
+
Counts are per queue, per process. Size the database pool for application work,
|
|
48
|
+
producers, and maintenance as well as consumer threads.
|
|
49
|
+
|
|
50
|
+
Clients are rebuilt after a fork; Rails/the process server remains responsible
|
|
51
|
+
for its connection-pool fork lifecycle. Do not share clients across Ractors.
|
|
52
|
+
|
|
53
|
+
## Dedicated worker process
|
|
54
|
+
|
|
55
|
+
From the Rails application root:
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
RAILS_ENV=production bundle exec river worker --rails
|
|
59
|
+
# Override config.river.stop_timeout for this process:
|
|
60
|
+
RAILS_ENV=production bundle exec river worker --rails --stop-timeout 60
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The command boots Rails and builds a consumer using the configuration above.
|
|
64
|
+
Do not start it in a web initializer. Migrate before starting consumers, and use
|
|
65
|
+
an external supervisor for process counts and restarts.
|
|
66
|
+
|
|
67
|
+
TERM/INT request stop and drain active attempts. At the graceful deadline,
|
|
68
|
+
the runner interrupts attempts and allows five seconds for finalization before
|
|
69
|
+
forcing exit. TSTP requests `stop(wait: false)` but keeps the process alive until
|
|
70
|
+
a later TERM/INT. Successfully finalized interruptions make jobs available again;
|
|
71
|
+
forced termination may leave jobs for stuck-job recovery. Give the supervisor a
|
|
72
|
+
termination window longer than the grace period plus finalization and a margin.
|
|
73
|
+
|
|
74
|
+
See [Dedicated worker processes](../../docs/workers.md) for signal behavior,
|
|
75
|
+
exit statuses, pool sizing, and rolling replacements. The generated `bin/jobs
|
|
76
|
+
start` delegates to the same runner; CLI options belong to `river worker`.
|
|
77
|
+
|
|
78
|
+
## Jobs and mail
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
class FulfillOrderJob < ApplicationJob
|
|
82
|
+
queue_as :default
|
|
83
|
+
retry_on PaymentGateway::Unavailable, attempts: 5, wait: 30.seconds
|
|
84
|
+
discard_on ActiveJob::DeserializationError
|
|
85
|
+
|
|
86
|
+
def perform(order)
|
|
87
|
+
FulfillOrder.call(order)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
FulfillOrderJob.perform_later order
|
|
92
|
+
FulfillOrderJob.set(wait: 10.minutes).perform_later order
|
|
93
|
+
OrderMailer.receipt(order).deliver_later
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Active Job handles serialization, GlobalID, callbacks, locale, timezone, and
|
|
97
|
+
custom serializers. Bulk `ActiveJob.perform_all_later` uses River's atomic bulk
|
|
98
|
+
insertion. Priorities must be `nil` (River priority 1) or integers 1 through 4.
|
|
99
|
+
Unsupported priorities raise instead of silently changing meaning.
|
|
100
|
+
|
|
101
|
+
## Transactions
|
|
102
|
+
|
|
103
|
+
The default connection class is `ActiveRecord::Base`. Select a different abstract
|
|
104
|
+
Active Record class for producers, consumers, and migration tasks together:
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
Rails.application.configure do
|
|
108
|
+
config.river.connection_class = "ApplicationRecord"
|
|
109
|
+
end
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Prefer a class name in Rails initializers: it is resolved lazily after boot and
|
|
113
|
+
the insertion client is rebuilt if Rails reloads the class. An actual class
|
|
114
|
+
object is also accepted. Configure its database through ordinary Active Record
|
|
115
|
+
`establish_connection` or `connects_to` configuration. Restart consumers after
|
|
116
|
+
changing connection configuration.
|
|
117
|
+
|
|
118
|
+
Without the Rails integration, select the class directly on the driver:
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
River::Driver::ActiveRecord.new(connection_class: ApplicationRecord)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Insert jobs in the selected connection's transaction for atomicity. Sharing a
|
|
125
|
+
database URL alone does not provide atomicity across connections. A dedicated
|
|
126
|
+
queue connection class/database is supported, but does not commit atomically
|
|
127
|
+
with application writes on another connection.
|
|
128
|
+
|
|
129
|
+
Shard routing and worker management remain explicit. Inserts follow the selected
|
|
130
|
+
class's current Active Record role/shard; consumers need their own appropriate
|
|
131
|
+
connection configuration. A request's `connected_to` block is not propagated to
|
|
132
|
+
worker threads. Use the same database backend/schema layout across roles/shards
|
|
133
|
+
accessed by a driver; no automatic shard discovery or cross-shard transactions
|
|
134
|
+
are provided.
|
|
135
|
+
|
|
136
|
+
For Rails 8.x, disable after-commit deferral explicitly when relying on atomicity:
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
class ApplicationJob < ActiveJob::Base
|
|
140
|
+
self.enqueue_after_transaction_commit = false
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
ApplicationRecord.transaction do
|
|
144
|
+
order = Order.create!(status: "pending")
|
|
145
|
+
FulfillOrderJob.perform_later(order)
|
|
146
|
+
end
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The Rails 7.2 adapter default is immediate insertion; its explicit policies use
|
|
150
|
+
`:never` and `:always` instead of Rails 8's `false` and `true`. Explicit application
|
|
151
|
+
after-commit policies are respected on every supported version, but lose the
|
|
152
|
+
atomic-write guarantee. A returned provider ID does not prove an outer transaction
|
|
153
|
+
has committed. Job completion and business writes are not automatically atomic.
|
|
154
|
+
|
|
155
|
+
## Retries and observability
|
|
156
|
+
|
|
157
|
+
Active Job owns application retries. `retry_on` creates a new River row with the
|
|
158
|
+
same Active Job UUID, while `provider_job_id` becomes the new River row ID string.
|
|
159
|
+
Unhandled errors, including exhausted `retry_on`, discard the current row without
|
|
160
|
+
an additional River retry cycle. Native River workers retain their normal policy.
|
|
161
|
+
The 25-attempt River budget is retained for crash recovery; stuck claims are
|
|
162
|
+
rescued by normal River maintenance (currently after an hour).
|
|
163
|
+
|
|
164
|
+
River interruption, cancellation, and snooze exceptions bypass Active Job's
|
|
165
|
+
retry/discard handlers, including `retry_on StandardError`. Avoid swallowing
|
|
166
|
+
these exceptions in broad Ruby `rescue` blocks inside application code.
|
|
167
|
+
|
|
168
|
+
Handled retries/discards complete the current delivery row. Its metadata records
|
|
169
|
+
`active_job_outcome` (`retried` or `discarded`), and retried deliveries record
|
|
170
|
+
`active_job_retry_id`. A completed delivery need not mean the logical job succeeded.
|
|
171
|
+
Active Job logging and notifications remain available. Rows store a versioned
|
|
172
|
+
Active Job envelope under the reserved `active_job` kind; Go can share the schema,
|
|
173
|
+
but Ruby must execute these jobs. Use native River kinds for cross-language jobs.
|
|
174
|
+
|
|
175
|
+
Retry enqueueing and predecessor finalization are not atomic. Crashes can cause
|
|
176
|
+
duplicates; jobs must remain idempotent. Rails' executor/reloader wraps each work
|
|
177
|
+
attempt and periodic constructor, cleaning context and connections. Active Job
|
|
178
|
+
classes are resolved per execution. Restart workers when changing native River
|
|
179
|
+
worker/plugin registrations or periodic configuration; these retain Ruby objects.
|
|
180
|
+
|
|
181
|
+
## Testing
|
|
182
|
+
|
|
183
|
+
Keep Rails' `:test` adapter and `ActiveJob::TestHelper` for ordinary application
|
|
184
|
+
tests. Use `:river` and a migrated isolated database for persistence/runtime
|
|
185
|
+
integration tests. Disable transactional fixtures for threaded worker tests.
|
|
186
|
+
The repository's `make test` includes this gem's PostgreSQL and SQLite tests.
|
|
187
|
+
`RIVER_REQUIRE_DATABASES=1 make test` makes missing PostgreSQL an error.
|
|
188
|
+
|
|
189
|
+
Outside a Rails app, instantiate `ActiveJob::QueueAdapters::RiverAdapter.new(client:)`
|
|
190
|
+
and register `River::Rails::Worker` on your consuming River client. Rails-specific
|
|
191
|
+
boot, configuration, and execution wrapping are then your responsibility.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveJob
|
|
4
|
+
module QueueAdapters
|
|
5
|
+
# Persists Active Job envelopes using the application's River client.
|
|
6
|
+
class RiverAdapter < AbstractAdapter
|
|
7
|
+
# An explicit client also allows using the adapter without a Rails app.
|
|
8
|
+
def initialize(client: nil)
|
|
9
|
+
@client = client
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Inserts an immediate Active Job delivery.
|
|
13
|
+
def enqueue(job)
|
|
14
|
+
enqueue_at(job, nil)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Inserts the whole batch atomically and assigns provider IDs on success.
|
|
18
|
+
def enqueue_all(jobs)
|
|
19
|
+
results = client.insert_many(jobs.map { |job| params(job, job.scheduled_at) })
|
|
20
|
+
jobs.zip(results).each do |job, result|
|
|
21
|
+
job.provider_job_id = result.job.id.to_s
|
|
22
|
+
job.successfully_enqueued = true
|
|
23
|
+
record_retry(job)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
results.length
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Inserts a delivery scheduled at an epoch timestamp (nil means now).
|
|
30
|
+
def enqueue_at(job, timestamp)
|
|
31
|
+
insertion = params(job, timestamp && Time.at(timestamp).utc)
|
|
32
|
+
result = client.insert(insertion.args, insert_opts: insertion.insert_opts)
|
|
33
|
+
job.provider_job_id = result.job.id.to_s
|
|
34
|
+
record_retry(job)
|
|
35
|
+
result
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Rails 7.2 adapter default: preserve same-connection atomic insertion.
|
|
39
|
+
def enqueue_after_transaction_commit? = false
|
|
40
|
+
|
|
41
|
+
private def client
|
|
42
|
+
@client || ::Rails.application.config.river.client
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private def params(job, scheduled_at)
|
|
46
|
+
priority = job.priority
|
|
47
|
+
valid_priority = priority.nil? || (priority.is_a?(Integer) && (1..4).cover?(priority))
|
|
48
|
+
unless valid_priority
|
|
49
|
+
raise ArgumentError, "River Active Job priority must be nil or an integer from 1 to 4"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
River::InsertManyParams.new(
|
|
53
|
+
River::JobArgsHash.new("active_job", {"job" => job.serialize, "version" => 1}),
|
|
54
|
+
max_attempts: 25, priority: priority || 1,
|
|
55
|
+
queue: job.queue_name, scheduled_at: scheduled_at
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private def record_retry(job)
|
|
60
|
+
current = ActiveSupport::IsolatedExecutionState[:river_active_job]
|
|
61
|
+
if current && current.args.fetch("job").fetch("job_id") == job.job_id
|
|
62
|
+
current.update_metadata("active_job_outcome" => "retried", "active_job_retry_id" => job.provider_job_id)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module River
|
|
6
|
+
module Generators
|
|
7
|
+
# Installs configuration and the explicitly started worker entry point.
|
|
8
|
+
class InstallGenerator < ::Rails::Generators::Base
|
|
9
|
+
source_root File.expand_path("templates", __dir__)
|
|
10
|
+
|
|
11
|
+
def install
|
|
12
|
+
copy_file "river.rb", "config/initializers/river.rb"
|
|
13
|
+
copy_file "jobs", "bin/jobs"
|
|
14
|
+
chmod "bin/jobs", 0o755
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Rails.application.configure do
|
|
4
|
+
# Optional: select an abstract connection class by name, resolved after boot.
|
|
5
|
+
# config.river.connection_class = "ApplicationRecord"
|
|
6
|
+
config.active_job.queue_adapter = :river unless Rails.env.test?
|
|
7
|
+
|
|
8
|
+
config.river.configure do
|
|
9
|
+
River::Config.new(
|
|
10
|
+
job_timeout: 300,
|
|
11
|
+
logger: Rails.logger,
|
|
12
|
+
queues: {default: 10, mailers: 5}
|
|
13
|
+
)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# For atomic enqueueing on the same Active Record connection, set
|
|
18
|
+
# self.enqueue_after_transaction_commit = false in ApplicationJob (Rails 8.x).
|
|
19
|
+
# Explicit after-commit deferral is respected, but is not an atomic SQL write.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module River
|
|
4
|
+
module Rails
|
|
5
|
+
# Application-owned configuration and per-process insertion client.
|
|
6
|
+
class Configuration
|
|
7
|
+
# Seconds allowed for graceful termination before attempts are interrupted.
|
|
8
|
+
attr_accessor :stop_timeout
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@connection_class = "ActiveRecord::Base"
|
|
12
|
+
@factory = -> { River::Config.new(logger: ::Rails.logger, queues: {"default" => 10}) }
|
|
13
|
+
@mutex = Mutex.new
|
|
14
|
+
@pid = Process.pid
|
|
15
|
+
@stop_timeout = 30
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Builds a consumer. Called after Rails boot, never in a web initializer.
|
|
19
|
+
def build_client
|
|
20
|
+
core = @factory.call
|
|
21
|
+
workers = River::Workers.new
|
|
22
|
+
core.workers.kinds.each { |kind| workers.add(kind, core.workers.fetch(kind)) }
|
|
23
|
+
workers.add(Worker)
|
|
24
|
+
periodic = core.periodic_jobs.map do |job|
|
|
25
|
+
River::PeriodicJob.new(id: job.id,
|
|
26
|
+
constructor: -> { ::Rails.application.reloader.wrap { job.constructor.call } },
|
|
27
|
+
run_on_start: job.run_on_start, schedule: job.schedule)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
River::Client.new(build_driver,
|
|
31
|
+
config: core.with(periodic_jobs: periodic, plugins: [ExecutionPlugin.new] + core.plugins, workers: workers))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Builds a driver for the configured connection class, including migrations.
|
|
35
|
+
def build_driver
|
|
36
|
+
River::Driver::ActiveRecord.new(connection_class: connection_class)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns a lazy insertion-only client for this process.
|
|
40
|
+
def client
|
|
41
|
+
if @pid != Process.pid
|
|
42
|
+
@client = nil
|
|
43
|
+
@mutex = Mutex.new
|
|
44
|
+
@pid = Process.pid
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
@mutex.synchronize do
|
|
48
|
+
klass = connection_class
|
|
49
|
+
@client = nil if @client && !@client.driver.connection_class.equal?(klass)
|
|
50
|
+
|
|
51
|
+
@client ||= River::Client.new(River::Driver::ActiveRecord.new(connection_class: klass),
|
|
52
|
+
config: @factory.call.with(periodic_jobs: [], queues: {}))
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Resolves names lazily so Rails can reload application connection classes.
|
|
57
|
+
def connection_class
|
|
58
|
+
@connection_class.is_a?(String) ? @connection_class.constantize : @connection_class
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Selects an abstract Active Record class or its name. Prefer a name in
|
|
62
|
+
# initializers to avoid retaining a reloadable class object.
|
|
63
|
+
def connection_class=(value)
|
|
64
|
+
@mutex.synchronize do
|
|
65
|
+
@connection_class = value
|
|
66
|
+
@client = nil
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Supplies a block returning River::Config after application boot.
|
|
71
|
+
def configure(&block)
|
|
72
|
+
raise ArgumentError, "configuration block required" unless block
|
|
73
|
+
|
|
74
|
+
@factory = block
|
|
75
|
+
@client = nil
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Wraps each attempt in Rails' reload-safe execution boundary.
|
|
80
|
+
class ExecutionPlugin
|
|
81
|
+
def work(_job, operation)
|
|
82
|
+
::Rails.application.reloader.wrap { operation.call }
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/railtie"
|
|
4
|
+
|
|
5
|
+
module River
|
|
6
|
+
module Rails
|
|
7
|
+
class Railtie < ::Rails::Railtie
|
|
8
|
+
config.river = Configuration.new
|
|
9
|
+
|
|
10
|
+
rake_tasks do
|
|
11
|
+
namespace :river do
|
|
12
|
+
desc "Apply River's canonical database migrations"
|
|
13
|
+
task migrate: :environment do
|
|
14
|
+
River::Migrator.new(::Rails.application.config.river.build_driver).migrate
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
desc "Show River migration status"
|
|
18
|
+
task status: :environment do
|
|
19
|
+
River::Migrator.new(::Rails.application.config.river.build_driver).status.each do |migration|
|
|
20
|
+
puts "#{migration.applied ? "applied" : "pending"} #{migration.version} #{migration.name}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module River
|
|
4
|
+
module Rails
|
|
5
|
+
# Runs one worker process; an external supervisor owns process restarts.
|
|
6
|
+
class Runner
|
|
7
|
+
# Boot Rails before calling. TERM/INT stop fetching and drain active jobs.
|
|
8
|
+
def self.start(out: $stdout, stop_timeout: nil)
|
|
9
|
+
config = ::Rails.application.config.river
|
|
10
|
+
River::WorkerRunner.new(config.build_client, out: out,
|
|
11
|
+
stop_timeout: stop_timeout || config.stop_timeout).run
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module River
|
|
4
|
+
module Rails
|
|
5
|
+
# Keep River's control-flow exceptions out of Active Job retry/discard
|
|
6
|
+
# handlers, including broad retry_on StandardError declarations.
|
|
7
|
+
module ExecutionControl
|
|
8
|
+
def rescue_with_handler(exception)
|
|
9
|
+
if ActiveSupport::IsolatedExecutionState[:river_active_job] &&
|
|
10
|
+
[River::ClientRuntime::Interrupted, River::JobCancelError, River::JobSnoozeError].any? { |type| exception.is_a?(type) }
|
|
11
|
+
raise exception
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
super
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
ActiveJob::Base.prepend(ExecutionControl)
|
|
19
|
+
|
|
20
|
+
# Dispatches serialized jobs through Active Job's own execution machinery.
|
|
21
|
+
class Worker
|
|
22
|
+
def self.kind = "active_job"
|
|
23
|
+
|
|
24
|
+
# Active Job owns retries for reported errors; River still rescues crashes.
|
|
25
|
+
def retry?(_job, _error) = false
|
|
26
|
+
|
|
27
|
+
def work(job)
|
|
28
|
+
previous = ActiveSupport::IsolatedExecutionState[:river_active_job]
|
|
29
|
+
ActiveSupport::IsolatedExecutionState[:river_active_job] = job
|
|
30
|
+
raise ArgumentError, "Unsupported Active Job envelope version" unless job.args.fetch("version") == 1
|
|
31
|
+
|
|
32
|
+
payload = job.args.fetch("job").merge("provider_job_id" => job.id.to_s)
|
|
33
|
+
ActiveJob::Base.execute(payload)
|
|
34
|
+
ensure
|
|
35
|
+
ActiveSupport::IsolatedExecutionState[:river_active_job] = previous
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
ActiveSupport::Notifications.subscribe("discard.active_job") do |*, payload|
|
|
40
|
+
job = ActiveSupport::IsolatedExecutionState[:river_active_job]
|
|
41
|
+
if job && payload.fetch(:job).job_id == job.args.fetch("job").fetch("job_id")
|
|
42
|
+
job.update_metadata("active_job_outcome" => "discarded")
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_job"
|
|
4
|
+
require "riverqueue-activerecord"
|
|
5
|
+
require_relative "river/rails/configuration"
|
|
6
|
+
require_relative "river/rails/worker"
|
|
7
|
+
require_relative "river/rails/runner"
|
|
8
|
+
require_relative "active_job/queue_adapters/river_adapter"
|
|
9
|
+
require_relative "river/rails/railtie"
|
metadata
CHANGED
|
@@ -1,28 +1,87 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: riverqueue-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.13.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
7
|
+
- Blake Gentry
|
|
8
|
+
- Brandur Leach
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
11
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
-
dependencies:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activejob
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '7.2'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '8.2'
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '7.2'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '8.2'
|
|
33
|
+
- !ruby/object:Gem::Dependency
|
|
34
|
+
name: railties
|
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '7.2'
|
|
40
|
+
- - "<"
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '8.2'
|
|
43
|
+
type: :runtime
|
|
44
|
+
prerelease: false
|
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
46
|
+
requirements:
|
|
47
|
+
- - ">="
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: '7.2'
|
|
50
|
+
- - "<"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '8.2'
|
|
53
|
+
- !ruby/object:Gem::Dependency
|
|
54
|
+
name: riverqueue-activerecord
|
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - '='
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: 0.13.0
|
|
60
|
+
type: :runtime
|
|
61
|
+
prerelease: false
|
|
62
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - '='
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: 0.13.0
|
|
15
67
|
executables: []
|
|
16
68
|
extensions: []
|
|
17
69
|
extra_rdoc_files: []
|
|
18
70
|
files:
|
|
19
71
|
- README.md
|
|
72
|
+
- lib/active_job/queue_adapters/river_adapter.rb
|
|
73
|
+
- lib/generators/river/install_generator.rb
|
|
74
|
+
- lib/generators/river/templates/jobs
|
|
75
|
+
- lib/generators/river/templates/river.rb
|
|
76
|
+
- lib/river/rails/configuration.rb
|
|
77
|
+
- lib/river/rails/railtie.rb
|
|
78
|
+
- lib/river/rails/runner.rb
|
|
79
|
+
- lib/river/rails/worker.rb
|
|
80
|
+
- lib/riverqueue-rails.rb
|
|
20
81
|
homepage: https://riverqueue.com
|
|
21
82
|
licenses:
|
|
22
83
|
- MPL-2.0
|
|
23
|
-
metadata:
|
|
24
|
-
allowed_push_host: https://rubygems.org
|
|
25
|
-
rubygems_mfa_required: 'true'
|
|
84
|
+
metadata: {}
|
|
26
85
|
rdoc_options: []
|
|
27
86
|
require_paths:
|
|
28
87
|
- lib
|
|
@@ -30,7 +89,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
30
89
|
requirements:
|
|
31
90
|
- - ">="
|
|
32
91
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
92
|
+
version: '3.2'
|
|
34
93
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
35
94
|
requirements:
|
|
36
95
|
- - ">="
|
|
@@ -39,5 +98,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
39
98
|
requirements: []
|
|
40
99
|
rubygems_version: 4.0.9
|
|
41
100
|
specification_version: 4
|
|
42
|
-
summary:
|
|
101
|
+
summary: Active Job and Rails integration for River.
|
|
43
102
|
test_files: []
|