riverqueue 0.13.0 → 0.14.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/CHANGELOG.md +37 -11
- data/README.md +83 -14
- data/docs/README.md +160 -30
- data/docs/conformance.md +11 -11
- data/docs/migrations.md +16 -16
- data/docs/testing.md +1 -1
- data/lib/client.rb +61 -6
- data/lib/client_runtime.rb +273 -35
- data/lib/config.rb +61 -9
- data/lib/driver/job_row_decoder.rb +2 -2
- data/lib/driver/notification_listener.rb +61 -0
- data/lib/driver/postgres_capabilities.rb +1 -1
- data/lib/driver/runtime.rb +214 -52
- data/lib/driver.rb +1 -0
- data/lib/insert_opts.rb +1 -1
- data/lib/job.rb +4 -4
- data/lib/job_persisted_logging/plugin.rb +3 -2
- data/lib/migrator.rb +1 -1
- data/lib/periodic_cron.rb +5 -4
- data/lib/periodic_job.rb +14 -3
- data/lib/resumable.rb +24 -1
- data/lib/worker.rb +32 -2
- data/migration/manifest.json +5 -5
- data/migration/postgresql/main/007_notification_outbox_sqlite_jsonb_and_sql_cleanup.down.sql +1 -1
- data/migration/postgresql/main/007_notification_outbox_sqlite_jsonb_and_sql_cleanup.up.sql +1 -1
- data/migration/postgresql/main/008_job_id_autoincrement.down.sql +1 -1
- data/migration/postgresql/main/008_job_id_autoincrement.up.sql +1 -1
- data/migration/sqlite/main/008_job_id_autoincrement.up.sql +1 -1
- data/sig/client.rbs +4 -1
- data/sig/driver.rbs +40 -5
- data/sig/runtime.rbs +52 -4
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1ce8622d0f151131d632d673e694e8ea90ded0502ba912274c7d044076063ee9
|
|
4
|
+
data.tar.gz: 1df7e4f2f10a3cfe36d3d08793a38b5400bf12dd32c3db3d3fd248184523d961
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c09ee3fefb3b0d9bdb4bd969761117470a84b7bcfa1278819446471c53348356ddffea3fa424d0cf086f57b1134ec49c1d470d03314286d21d50c9d1670568a4
|
|
7
|
+
data.tar.gz: a79a913f6f455f8a6a14e989ae72c415adb39248d97c1457b2cfd077b74ce41676e1f3bc79239d4a33d2653485074de7a8a6fd414699fec2e30b97c1fedb3d11
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.14.0] - 2026-10-26
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Receive Postgres and SQLite notifications for insertions, queue controls, cancellation, and leadership handoffs, with reconnect recovery and a `poll_only` option. [PR #1496](https://github.com/riverqueue/river/pull/1496).
|
|
15
|
+
- Add `Client#request_resign` to request that the maintenance leader relinquish leadership after the caller's transaction commits. [PR #1496](https://github.com/riverqueue/river/pull/1496).
|
|
16
|
+
- Allow `River::Workers#add` to register a kind and a work block, using the client's default retry and timeout policies. [PR #1486](https://github.com/riverqueue/river/pull/1486).
|
|
17
|
+
- Added `Client#job_complete_tx(job)` to complete a running job in the caller's Active Record or Sequel transaction, including pending metadata and output. An active transaction on the driver's connection is required. Completion rolls back with the transaction; once committed, it takes precedence over subsequent worker errors and emits a completion event when the worker finishes. [PR #1490](https://github.com/riverqueue/river/pull/1490).
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- River operations called inside an open Active Record or Sequel transaction now run in that transaction and open no savepoint of their own. If an operation raises, roll back the transaction, or wrap the call in your own savepoint to recover and continue. A `driver.rollback_exception` raised inside `Driver#transaction` now reaches the enclosing transaction block, as a rollback raised in your own code would. Before, it rolled back only River's savepoint. Set `RIVER_USE_LEGACY_SUBTRANSACTIONS=1` (or `true`) to restore the savepoints. This temporary fallback will be removed in a future release. [PR #1544](https://github.com/riverqueue/river/pull/1544).
|
|
22
|
+
- `work_end(job, error)` hooks now return the attempt's error, matching Go. Return the incoming error to preserve it, a replacement exception to change it, or `nil` to clear a failure. The return value feeds the next hook in registration order. Existing observing hooks must explicitly return `error`. End hooks are skipped if `work_begin` raises or the worker raises an exception outside `StandardError`. [PR #1520](https://github.com/riverqueue/river/pull/1520).
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- The scheduler no longer opens a savepoint for each due job to catch unique key conflicts. A pass that made more than 64 jobs available overflowed the Postgres subtransaction cache, which can slow queries across the server. The scheduler now finds conflicts with one statement on Postgres and checks before each write on SQLite. Conflicting jobs are still discarded with `unique_key_conflict: scheduler_discarded` metadata. On Postgres, the scheduler now waits for a due job that another transaction has locked, instead of skipping it. [PR #1544](https://github.com/riverqueue/river/pull/1544).
|
|
27
|
+
- Default periodic jobs to their occurrence time, preserving constructor and argument-level schedules. [PR #1537](https://github.com/riverqueue/river/pull/1537).
|
|
28
|
+
- Fail retries that omit a saved cursor-only step, while preserving their checkpoints for recovery. Jobs with an existing step marker retain their behavior, including legacy stale cursor entries. [PR #1534](https://github.com/riverqueue/river/pull/1534).
|
|
29
|
+
- Support large SQLite metadata updates without reaching the expression-depth limit, preserving top-level replacement and JSON null values. [PR #1533](https://github.com/riverqueue/river/pull/1533).
|
|
30
|
+
- A `River::Config` without an explicit `id:` generates its default client ID on first use in each process, so clients built before a fork (as with preloading Puma, Unicorn, or Pitchfork servers) no longer share one ID across forked workers. Explicit IDs are unchanged, and `Config#with` copies share the original's ID. [PR #1498](https://github.com/riverqueue/river/pull/1498).
|
|
31
|
+
- Inserting a job stores its encoded args as given instead of decoding and re-encoding them in Ruby, which rewrote numbers like `1.10` as `1.1` and `1e3` as `1000.0` and rounded digits beyond `Float` precision. Postgres applies its usual `jsonb` normalization, and SQLite keeps the encoded bytes, matching River's Go drivers. Affects Active Record on Postgres and SQLite, and Sequel on SQLite. [PR #1503](https://github.com/riverqueue/river/pull/1503).
|
|
32
|
+
- Wake workers when scheduled jobs become available or jobs are manually retried, including workers in other clients. [PR #1496](https://github.com/riverqueue/river/pull/1496).
|
|
33
|
+
- Honor cancellations that arrive between claiming a job and starting its worker. [PR #1496](https://github.com/riverqueue/river/pull/1496).
|
|
34
|
+
- Preserve worker wakeups that arrive between a fetch and the producer going to sleep. [PR #1496](https://github.com/riverqueue/river/pull/1496).
|
|
35
|
+
|
|
10
36
|
## [0.13.0] - 2026-10-08
|
|
11
37
|
|
|
12
38
|
### Added
|
|
@@ -17,10 +43,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
17
43
|
### Changed
|
|
18
44
|
|
|
19
45
|
- Ruby is now developed in the main [River repository](https://github.com/riverqueue/river/tree/master/ruby). Gem names and require paths are unchanged. The four public gems (`riverqueue`, `riverqueue-activerecord`, `riverqueue-sequel`, and `riverqueue-rails`) are released together. [PR #1463](https://github.com/riverqueue/river/pull/1463).
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
46
|
+
- Job insertion accepts only `available`, `pending`, and `scheduled` states. `running`, `retryable`, and terminal states are rejected, including when set by insertion hooks. [PR #1463](https://github.com/riverqueue/river/pull/1463).
|
|
47
|
+
- Insertion rejects queue names containing characters other than ASCII letters, digits, underscores, hyphens, colons, or periods. Nonzero `UniqueOpts#by_period` values must be at least one second. [PR #1463](https://github.com/riverqueue/river/pull/1463).
|
|
48
|
+
- Attempt counts must be between 0 and 32,767, and `max_attempts` must be between 1 and 32,767. These bounds also apply on SQLite. `job_retry` raises `ArgumentError` when another attempt would exceed the limit. [PR #1463](https://github.com/riverqueue/river/pull/1463).
|
|
49
|
+
- Job and queue metadata updates require a Hash; JSON-encoded strings are no longer accepted. Pass an empty Hash to clear metadata. Updates to `attempted_by` require string entries. [PR #1463](https://github.com/riverqueue/river/pull/1463).
|
|
24
50
|
|
|
25
51
|
### Fixed
|
|
26
52
|
|
|
@@ -36,7 +62,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
36
62
|
- Cancellation wins atomically over discarded attempts and other state transitions, including the last attempt or a worker that disables retries. [PR #1463](https://github.com/riverqueue/river/pull/1463).
|
|
37
63
|
- Stuck-job rescue falls back to the default retry policy per job when an application policy fails or returns an invalid time, allowing recovery and cleanup to continue. [PR #1463](https://github.com/riverqueue/river/pull/1463).
|
|
38
64
|
- Clients without consumer queues run configured periodic jobs and maintenance services. Adding periodic jobs starts maintenance when needed; registration is rejected when leader election is disabled. [PR #1463](https://github.com/riverqueue/river/pull/1463).
|
|
39
|
-
- Bulk deletion excludes running jobs before applying its limit and locks eligible
|
|
65
|
+
- Bulk deletion excludes running jobs before applying its limit and locks eligible Postgres rows so concurrent retries cannot cause deletion of jobs that no longer match the filters. [PR #1463](https://github.com/riverqueue/river/pull/1463).
|
|
40
66
|
- Stuck-job rescue respects longer or disabled client and worker timeouts and scans past protected attempts without consuming the rescue limit. [PR #1463](https://github.com/riverqueue/river/pull/1463).
|
|
41
67
|
- Snoozing and rescue tolerate JSON counters that overflow SQLite's floating-point representation. [PR #1463](https://github.com/riverqueue/river/pull/1463).
|
|
42
68
|
- Snoozing tolerates nonnumeric metadata counters instead of failing on booleans or collections. Ruby-specific recovery cases are tested independently of the shared Go fixtures. [PR #1463](https://github.com/riverqueue/river/pull/1463).
|
|
@@ -47,21 +73,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
47
73
|
- Custom maintenance service failures are logged without blocking other services, stuck-job rescue, or cleanup. [PR #1463](https://github.com/riverqueue/river/pull/1463).
|
|
48
74
|
- Job updates reject non-object metadata before writing to the database, preserving the existing job when invalid metadata is supplied. [PR #1463](https://github.com/riverqueue/river/pull/1463).
|
|
49
75
|
- Job completion atomically checks for cancellation so a concurrent cancellation cannot be overwritten by completion, including when finalization hooks are configured. [PR #1463](https://github.com/riverqueue/river/pull/1463).
|
|
50
|
-
- Job updates reject nonpositive attempt limits consistently on
|
|
76
|
+
- Job updates reject nonpositive attempt limits consistently on Postgres and SQLite. [PR #1463](https://github.com/riverqueue/river/pull/1463).
|
|
51
77
|
- Job lifecycle events report worker execution and completion durations separately using a monotonic clock. [PR #1463](https://github.com/riverqueue/river/pull/1463).
|
|
52
78
|
- Finalization hooks that delete jobs honor concurrent cancellation and record the cancelled attempt instead of deleting it. [PR #1463](https://github.com/riverqueue/river/pull/1463).
|
|
53
79
|
- Queue metadata writes and job metadata merges reject non-object values before changing persisted data. Job updates also reject non-string worker IDs in attempt histories. [PR #1463](https://github.com/riverqueue/river/pull/1463).
|
|
54
80
|
- Unique inserts with `exclude_kind` preserve the existing job's kind when a different kind conflicts, on both databases and through both drivers. [PR #1463](https://github.com/riverqueue/river/pull/1463).
|
|
55
81
|
- Retrying, snoozing, and interrupting jobs preserve the original queue-wait duration in lifecycle events. [PR #1463](https://github.com/riverqueue/river/pull/1463).
|
|
56
82
|
- Invalid rescue counters no longer prevent other stuck jobs in the batch from being rescued. [PR #1463](https://github.com/riverqueue/river/pull/1463).
|
|
57
|
-
- SQLite retains only the newest 100 worker IDs when claiming a job, matching
|
|
83
|
+
- SQLite retains only the newest 100 worker IDs when claiming a job, matching Postgres. [PR #1463](https://github.com/riverqueue/river/pull/1463).
|
|
58
84
|
- Attempt-error serialization supports frozen timestamps and preserves the caller's timezone. [PR #1463](https://github.com/riverqueue/river/pull/1463).
|
|
59
85
|
|
|
60
86
|
## [0.12.0] - 2026-10-01
|
|
61
87
|
|
|
62
88
|
### Added
|
|
63
89
|
|
|
64
|
-
- Add a full Ruby client for River with Go-compatible job insertion and execution on
|
|
90
|
+
- Add a full Ruby client for River with Go-compatible job insertion and execution on Postgres, SQLite, and YugabyteDB through ActiveRecord or Sequel. Includes workers, retries, cancellation, periodic and resumable jobs, job-persisted logging, job administration, migration and worker CLIs, and testing helpers. Rails and Active Job integration is available through `riverqueue-rails`, with workflows, batches, sequences, concurrency controls, and other advanced features in the separately distributed `riverqueue-pro` gem. [PR #70](https://github.com/riverqueue/riverqueue-ruby/pull/70).
|
|
65
91
|
|
|
66
92
|
## [0.11.0] - 2026-09-02
|
|
67
93
|
|
|
@@ -71,7 +97,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
71
97
|
|
|
72
98
|
### Changed
|
|
73
99
|
|
|
74
|
-
- Stop pulling in `pg` as a hard dependency of either driver. Applications now select their database adapter by including `pg` for
|
|
100
|
+
- Stop pulling in `pg` as a hard dependency of either driver. Applications now select their database adapter by including `pg` for Postgres or `sqlite3` for SQLite. [PR #67](https://github.com/riverqueue/riverqueue-ruby/pull/67).
|
|
75
101
|
|
|
76
102
|
## [0.10.1] - 2026-04-09
|
|
77
103
|
|
|
@@ -130,7 +156,7 @@ Users should upgrade backends to River v0.12.0 before upgrading this library in
|
|
|
130
156
|
|
|
131
157
|
This updated implementation is significantly faster due to the removal of advisory locks in favor of an index-backed uniqueness system, while allowing some flexibility in which job states are considered. However, not all states may be removed from consideration when using the `by_state` option; pending, scheduled, available, and running states are required whenever customizing this list. [PR #32](https://github.com/riverqueue/riverqueue-ruby/pull/32).
|
|
132
158
|
|
|
133
|
-
- Update REXML dependency. [PR #
|
|
159
|
+
- Update REXML dependency. [PR #36](https://github.com/riverqueue/riverqueue-ruby/pull/36).
|
|
134
160
|
|
|
135
161
|
## [0.7.0] - 2024-08-30
|
|
136
162
|
|
|
@@ -155,7 +181,7 @@ Users should upgrade backends to River v0.12.0 before upgrading this library in
|
|
|
155
181
|
### Changed
|
|
156
182
|
|
|
157
183
|
- Tag format is now checked on insert. Tags should be no more than 255 characters and match the regex `/\A[\w][\w\-]+[\w]\z/`. [PR #22](https://github.com/riverqueue/riverqueue-ruby/pull/22).
|
|
158
|
-
- Returned jobs now have a `metadata` property. [PR #
|
|
184
|
+
- Returned jobs now have a `metadata` property. [PR #22](https://github.com/riverqueue/riverqueue-ruby/pull/22).
|
|
159
185
|
|
|
160
186
|
## [0.4.0] - 2024-04-28
|
|
161
187
|
|
data/README.md
CHANGED
|
@@ -1,22 +1,91 @@
|
|
|
1
1
|
# River for Ruby
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
and JavaScript clients. Includes Active Record and Sequel drivers, plus Rails
|
|
5
|
-
and Active Job integration.
|
|
3
|
+
River is a fast, reliable background job system backed by Postgres or SQLite. The Ruby client inserts and works jobs using the same database schema and job protocol as River's other languages. It includes Active Record and Sequel drivers, plus Rails and Active Job integration.
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
- [Development and releases](docs/development.md)
|
|
9
|
-
- [Conformance coverage](docs/conformance.md)
|
|
10
|
-
- [Migrations](docs/migrations.md)
|
|
5
|
+
## Installation
|
|
11
6
|
|
|
12
|
-
|
|
7
|
+
Ruby 3.2 or newer is required. Add a River driver and its database adapter to your application's `Gemfile`; the driver brings in the core `riverqueue` gem:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem "riverqueue-sequel"
|
|
11
|
+
gem "pg"
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Use `riverqueue-activerecord` for Active Record, or `sqlite3` instead of `pg` for SQLite. Install the gems with `bundle install`. Rails applications can use [`riverqueue-rails`](rails/riverqueue-rails/README.md) to configure River as their Active Job backend.
|
|
15
|
+
|
|
16
|
+
## Quick start
|
|
17
|
+
|
|
18
|
+
Set `DATABASE_URL` to an existing application database and apply River's migrations before inserting or working jobs:
|
|
13
19
|
|
|
14
20
|
```sh
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
export DATABASE_URL=postgres://localhost/my_app
|
|
22
|
+
bundle exec river migrate-up
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Define job arguments and a worker with the same stable kind, register the worker, and insert a job. Save this as `worker.rb`:
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
require "riverqueue-sequel"
|
|
29
|
+
|
|
30
|
+
class SendEmailArgs
|
|
31
|
+
def initialize(address:)
|
|
32
|
+
@address = address
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def kind = "send_email"
|
|
36
|
+
def to_json = JSON.generate(address: @address)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class SendEmailWorker
|
|
40
|
+
def self.kind = "send_email"
|
|
41
|
+
|
|
42
|
+
def work(job)
|
|
43
|
+
puts "Sending email to #{job.args.fetch("address")}"
|
|
44
|
+
job.output = {delivered: true}
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
db = Sequel.connect(ENV.fetch("DATABASE_URL"), max_connections: 12)
|
|
49
|
+
client = River::Client.new(
|
|
50
|
+
River::Driver::Sequel.new(db),
|
|
51
|
+
config: River::Config.new(
|
|
52
|
+
queues: {default: 10},
|
|
53
|
+
workers: River::Workers.new.add(SendEmailWorker)
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
begin
|
|
58
|
+
client.start
|
|
59
|
+
result = client.insert(SendEmailArgs.new(address: "person@example.com"))
|
|
60
|
+
puts "Inserted job #{result.job.id}; press Ctrl-C to stop"
|
|
61
|
+
sleep
|
|
62
|
+
rescue Interrupt
|
|
63
|
+
# Stop fetching and wait for active jobs before closing the database pool.
|
|
64
|
+
ensure
|
|
65
|
+
client.stop
|
|
66
|
+
db.disconnect
|
|
67
|
+
end
|
|
19
68
|
```
|
|
20
69
|
|
|
21
|
-
|
|
22
|
-
|
|
70
|
+
Run `bundle exec ruby worker.rb`. Replace the worker body with your mail service. Job arguments implement `kind` and `to_json`; workers read decoded JSON keys as strings. Services sharing a job must agree on its kind and JSON fields. For SQLite and alternate Postgres schemas, see [migrations](docs/migrations.md).
|
|
71
|
+
|
|
72
|
+
A client without configured queues can insert jobs without starting worker threads. Applications own their database pools and should close them only after stopping River.
|
|
73
|
+
|
|
74
|
+
## Transactions and other features
|
|
75
|
+
|
|
76
|
+
Inserts join a transaction opened on the same Sequel database or Active Record connection, so jobs commit or roll back with the application writes that caused them. See [transactional enqueueing](docs/README.md#transactional-enqueueing) for examples.
|
|
77
|
+
|
|
78
|
+
- [Bulk insertion](docs/README.md#bulk-insertion), [scheduled jobs](docs/README.md#scheduled-jobs), and [unique jobs](docs/README.md#unique-jobs).
|
|
79
|
+
- [Retries](docs/README.md#job-retries), [cancellation](docs/README.md#cancelling-jobs), and [snoozing](docs/README.md#snoozing-jobs).
|
|
80
|
+
- [Multiple queues](docs/README.md#multiple-queues), [periodic jobs](docs/README.md#periodic-jobs), and [resumable jobs](docs/README.md#resumable-jobs).
|
|
81
|
+
- [Event subscriptions](docs/README.md#subscriptions) and [job administration](docs/README.md#job-administration).
|
|
82
|
+
- [Graceful stopping](docs/README.md#stopping-gracefully) and a foreground [worker command](docs/workers.md).
|
|
83
|
+
- [Testing helpers](docs/testing.md) for application jobs and workers.
|
|
84
|
+
|
|
85
|
+
## Documentation
|
|
86
|
+
|
|
87
|
+
See the [usage and configuration guide](docs/README.md), [migration guide](docs/migrations.md), and [Rails and Active Job integration](rails/riverqueue-rails/README.md). The [Sidekiq migration guide](docs/migrating_from_sidekiq.md) covers moving an existing application.
|
|
88
|
+
|
|
89
|
+
## Development
|
|
90
|
+
|
|
91
|
+
See [developing River for Ruby](docs/development.md).
|
data/docs/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# River client for Ruby [](https://github.com/riverqueue/river/actions/workflows/ruby.yaml) [](https://badge.fury.io/rb/riverqueue)
|
|
2
2
|
|
|
3
|
-
A Ruby client for [River](https://github.com/riverqueue/river), packaged in the [`riverqueue` gem](https://rubygems.org/gems/riverqueue). It inserts and works jobs using River's canonical database schema and state machine, so Ruby and
|
|
3
|
+
A Ruby client for [River](https://github.com/riverqueue/river), packaged in the [`riverqueue` gem](https://rubygems.org/gems/riverqueue). It inserts and works jobs using River's canonical database schema and state machine, so Ruby and River clients in other languages can safely share a River database. Separate queues are recommended when each language recognizes different job kinds.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -26,9 +26,9 @@ its uniqueness and notification capabilities.
|
|
|
26
26
|
## Schema and migrations
|
|
27
27
|
|
|
28
28
|
Run migrations before inserting jobs or starting workers. The bundled `river`
|
|
29
|
-
command uses
|
|
30
|
-
|
|
31
|
-
application's
|
|
29
|
+
command uses River's canonical Postgres and SQLite migrations, bundled with the
|
|
30
|
+
gem. After installing a River driver and its database adapter, create your
|
|
31
|
+
application's Postgres database and run:
|
|
32
32
|
|
|
33
33
|
```sh
|
|
34
34
|
export DATABASE_URL=postgres://localhost/my_app
|
|
@@ -39,8 +39,8 @@ bundle exec river migrate-up
|
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
`migrate-up` applies all pending migrations and is safe to rerun: already-applied
|
|
42
|
-
versions are skipped, including migrations applied by
|
|
43
|
-
the plan without changing the database. Pass `--database-url URL` to override
|
|
42
|
+
versions are skipped, including migrations applied by other River clients.
|
|
43
|
+
`--dry-run` previews the plan without changing the database. Pass `--database-url URL` to override
|
|
44
44
|
`DATABASE_URL`.
|
|
45
45
|
|
|
46
46
|
For SQLite, ensure the parent directory exists, then use a SQLite URL:
|
|
@@ -57,7 +57,7 @@ both are in your bundle.
|
|
|
57
57
|
For River Pro, install `riverqueue-pro`, apply the main migrations above, then
|
|
58
58
|
run `bundle exec river migrate-up --line pro` against the same database.
|
|
59
59
|
|
|
60
|
-
See the [migration guide](migrations.md) for the Ruby API,
|
|
60
|
+
See the [migration guide](migrations.md) for the Ruby API, Postgres schemas,
|
|
61
61
|
target versions, downgrade precautions, and existing SQLite Pro installations.
|
|
62
62
|
Test schema snapshots under `spec/support` are test-only and must not be used
|
|
63
63
|
to provision production databases.
|
|
@@ -110,12 +110,37 @@ Job arguments must respond to `#kind` and `#to_json`. They may also return defau
|
|
|
110
110
|
Use strings for `#kind` definitions, and symbols for identifiers such as queues,
|
|
111
111
|
states, periodic job IDs, and resumable step names. Both forms are accepted.
|
|
112
112
|
Persisted job attributes and JSON object keys remain strings, preserving
|
|
113
|
-
compatibility with
|
|
113
|
+
compatibility with River clients in other languages.
|
|
114
114
|
Examples omit parentheses on simple calls and `do...end` blocks, retaining them
|
|
115
115
|
for nested expressions and `{ ... }` blocks where they make binding clear.
|
|
116
116
|
|
|
117
117
|
## Core features
|
|
118
118
|
|
|
119
|
+
### Block workers
|
|
120
|
+
|
|
121
|
+
Pass a kind and a block to `Workers#add` to define a worker inline:
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
workers = River::Workers.new
|
|
125
|
+
workers.add(:message) do |job|
|
|
126
|
+
puts "Message: #{job.args.fetch("message")}"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# With client configured to use this registry and the ruby queue:
|
|
130
|
+
client.insert River::JobArgsHash.new(:message, message: "Hello!"), queue: :ruby
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Pass this registry as `workers:` in `River::Config`. The kind accepts a string
|
|
134
|
+
or symbol and must match the inserted job's kind. The block receives a
|
|
135
|
+
`River::Job` and uses the client's retry and timeout policies. Returning normally
|
|
136
|
+
completes the job; the block's return value is ignored. Raise exceptions to fail,
|
|
137
|
+
cancel, or snooze work as with a worker class. Optionally use `job.output =` to
|
|
138
|
+
persist a result. Aliases are supported through `aliases:` as for other workers.
|
|
139
|
+
Supply a worker or a block, not both. Existing procs or methods can be passed as
|
|
140
|
+
the block with `&`.
|
|
141
|
+
The same block may run concurrently for multiple jobs, so any captured mutable
|
|
142
|
+
state must be safe to share across threads.
|
|
143
|
+
|
|
119
144
|
### [Accessing the client from workers](https://riverqueue.com/docs/context-client)
|
|
120
145
|
|
|
121
146
|
Every running `River::Job` exposes the client that claimed it. Workers can use
|
|
@@ -159,8 +184,8 @@ For simple jobs, `River::JobArgsHash.new(:kind, hash)` avoids defining an argume
|
|
|
159
184
|
|
|
160
185
|
### [Transactional enqueueing](https://riverqueue.com/docs/transactional-enqueueing)
|
|
161
186
|
|
|
162
|
-
Insertion hooks and middleware execute inside the insertion transaction
|
|
163
|
-
|
|
187
|
+
Insertion hooks and middleware execute inside the insertion transaction, so
|
|
188
|
+
their database writes and the enqueue commit or roll back together. Postgres
|
|
164
189
|
insert notifications are delivered only when the surrounding transaction commits.
|
|
165
190
|
|
|
166
191
|
Inserts automatically join a transaction opened through the same Active Record connection or Sequel database object. A rollback also rolls back the job:
|
|
@@ -181,6 +206,62 @@ end
|
|
|
181
206
|
|
|
182
207
|
The equivalent works inside `ActiveRecord::Base.transaction` with the Active Record driver.
|
|
183
208
|
|
|
209
|
+
River calls inside an open transaction run in that transaction and open no
|
|
210
|
+
savepoint of their own. If a River call raises, it may leave partial changes,
|
|
211
|
+
so roll back the transaction. To recover from the error and continue the
|
|
212
|
+
transaction, wrap the call in your own savepoint, such as
|
|
213
|
+
`DB.transaction(savepoint: true)` or
|
|
214
|
+
`ApplicationRecord.transaction(requires_new: true)`.
|
|
215
|
+
|
|
216
|
+
### Transactional completion
|
|
217
|
+
|
|
218
|
+
Call `job.client.job_complete_tx(job)` inside a transaction to commit application
|
|
219
|
+
writes and job completion together (the equivalent of Go's `JobCompleteTx`):
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
class FulfillOrderWorker
|
|
223
|
+
def work(job)
|
|
224
|
+
DB.transaction do
|
|
225
|
+
DB[:orders].where(id: job.args.fetch("order_id")).update(fulfilled: true)
|
|
226
|
+
job.output = {fulfilled: true}
|
|
227
|
+
job.client.job_complete_tx(job)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
With Active Record 7.2 or later, use it inside an `ApplicationRecord.transaction` block. As
|
|
234
|
+
with insertion, the transaction must use the driver's connection on the same
|
|
235
|
+
thread. No explicit transaction argument is needed. Calling `job_complete_tx`
|
|
236
|
+
without an active transaction on that connection raises `River::Error` without
|
|
237
|
+
changing the job.
|
|
238
|
+
|
|
239
|
+
Sequel 5.20 or later is required so transaction callbacks respect savepoints.
|
|
240
|
+
|
|
241
|
+
`job_complete_tx` accepts the running `River::Job` passed to the worker and returns
|
|
242
|
+
an updated `River::JobRow`, leaving the original job unchanged. Set output and
|
|
243
|
+
metadata before calling it so they're included in the transaction. A missing
|
|
244
|
+
job raises `River::NotFoundError`; a pending cancellation raises
|
|
245
|
+
`River::JobCancelError`, allowing the transaction to roll back.
|
|
246
|
+
The supplied job must still belong to the current attempt; a stale worker cannot
|
|
247
|
+
complete a replacement attempt.
|
|
248
|
+
Metadata added afterward, including persisted logs, is saved separately when
|
|
249
|
+
the worker finishes, provided the completed attempt has not been retried.
|
|
250
|
+
|
|
251
|
+
Make completion the last operation in the transaction and let the transaction
|
|
252
|
+
commit before returning from `work`. Propagate errors that roll back the
|
|
253
|
+
transaction so River retries the job. If you swallow a rollback and return
|
|
254
|
+
successfully, River completes the job normally. Once completion commits, later
|
|
255
|
+
worker errors cannot retry or cancel the job. River emits `:job_completed` when
|
|
256
|
+
the worker finishes, and skips its usual finalization hooks for the already
|
|
257
|
+
completed job.
|
|
258
|
+
If the completed row is deleted before the worker finishes, River emits the
|
|
259
|
+
completion event using the snapshot retained when the transaction committed.
|
|
260
|
+
|
|
261
|
+
If the job is manually retried before the original worker finishes, that worker
|
|
262
|
+
leaves the replacement attempt untouched and does not emit a completion event
|
|
263
|
+
for it.
|
|
264
|
+
|
|
184
265
|
### [Bulk insertion](https://riverqueue.com/docs/inserting-many-jobs)
|
|
185
266
|
|
|
186
267
|
`#insert_many` inserts a batch atomically and returns one `River::JobInsertResult` per input. Use `River::InsertManyParams` when jobs need different options:
|
|
@@ -230,7 +311,7 @@ order affect the hash even when the decoded JSON is equivalent.
|
|
|
230
311
|
|
|
231
312
|
### [Reliable execution and stuck jobs](https://riverqueue.com/docs/reliable-workers)
|
|
232
313
|
|
|
233
|
-
Claims and state transitions are atomic in the database. If a process disappears while working, the elected maintenance client checks for stale running jobs after an hour, retrying or discarding them according to their attempt count. Rescue respects longer client and worker timeouts; disabling a job's timeout also disables automatic timeout-based rescue for it. Cancellation requests still take precedence. Keep worker registrations and timeout configuration consistent across maintenance clients. `attempted_by`, attempt errors, and final state remain in the canonical River row for inspection by
|
|
314
|
+
Claims and state transitions are atomic in the database. If a process disappears while working, the elected maintenance client checks for stale running jobs after an hour, retrying or discarding them according to their attempt count. Rescue respects longer client and worker timeouts; disabling a job's timeout also disables automatic timeout-based rescue for it. Cancellation requests still take precedence. Keep worker registrations and timeout configuration consistent across maintenance clients. `attempted_by`, attempt errors, and final state remain in the canonical River row for inspection by any River client or River UI.
|
|
234
315
|
|
|
235
316
|
### [Job retries](https://riverqueue.com/docs/job-retries)
|
|
236
317
|
|
|
@@ -405,9 +486,9 @@ cleanup = River::PeriodicJob.new(
|
|
|
405
486
|
a runtime dependency of the River gem. The timezone defaults explicitly to UTC;
|
|
406
487
|
set `timezone:` or use a `CRON_TZ=` / `TZ=` prefix, which takes precedence.
|
|
407
488
|
Five-field cron, optional seconds, and aliases such as `@daily` use Fugit's
|
|
408
|
-
syntax; `?` is also accepted as a wildcard. `@every
|
|
409
|
-
|
|
410
|
-
`PeriodicInterval`, these intervals align to whole-second boundaries.
|
|
489
|
+
syntax; `?` is also accepted as a wildcard. `@every` accepts durations such
|
|
490
|
+
as `1h30m`, built from `ns`, `us`, `ms`, `s`, `m`, and `h` units, rounded down
|
|
491
|
+
to whole seconds with a one-second minimum. Unlike `PeriodicInterval`, these intervals align to whole-second boundaries.
|
|
411
492
|
Results are UTC `Time` objects. Nonexistent spring-forward times are skipped;
|
|
412
493
|
both occurrences of a repeated fall-back time are scheduled. See
|
|
413
494
|
[conformance differences](conformance.md) before sharing schedules across
|
|
@@ -419,7 +500,7 @@ a periodic job. This stores the scheduled job immediately in the database.
|
|
|
419
500
|
|
|
420
501
|
### [Resumable jobs](https://riverqueue.com/docs/resumable-jobs)
|
|
421
502
|
|
|
422
|
-
Long jobs can checkpoint idempotent steps and cursor progress. On retry, River skips completed steps and resumes a cursor step from its last recorded value using
|
|
503
|
+
Long jobs can checkpoint idempotent steps and cursor progress. On retry, River skips completed steps and resumes a cursor step from its last recorded value using River's shared metadata format.
|
|
423
504
|
|
|
424
505
|
```ruby
|
|
425
506
|
def work(job)
|
|
@@ -481,7 +562,7 @@ end
|
|
|
481
562
|
### Job-persisted logging
|
|
482
563
|
|
|
483
564
|
Add `River::JobPersistedLogging::Plugin` to save worker logs with each job attempt,
|
|
484
|
-
using the
|
|
565
|
+
using the shared `metadata["river:log"]` format that River UI displays. This
|
|
485
566
|
is a core feature; it does not require Pro or additional migrations.
|
|
486
567
|
|
|
487
568
|
```ruby
|
|
@@ -557,7 +638,7 @@ A plugin may implement any combination of these methods:
|
|
|
557
638
|
| Hook | `insert_begin(params)` | Before each job is inserted; `params` may be modified. |
|
|
558
639
|
| Hook | `insert_end(result)` | After each job is inserted. |
|
|
559
640
|
| Hook | `work_begin(job)` | After a job is claimed, immediately before its worker runs. |
|
|
560
|
-
| Hook | `work_end(job, error)` | After the worker returns or raises
|
|
641
|
+
| Hook | `work_end(job, error)` | After the worker returns or raises a `StandardError`; returns the attempt's error or `nil`. |
|
|
561
642
|
| Hook | `job_finalize(job, state)` | Before successful finalization; returning `:delete` deletes the job instead of retaining it. |
|
|
562
643
|
| Middleware | `insert_many(params, operation)` | Around one insertion call; `params` is an array even for `Client#insert`. |
|
|
563
644
|
| Middleware | `work(job, operation)` | Around the work hooks and worker for one claimed job. |
|
|
@@ -584,10 +665,33 @@ end
|
|
|
584
665
|
config = River::Config.new(plugins: [AuditPlugin.new, TimingPlugin.new])
|
|
585
666
|
```
|
|
586
667
|
|
|
587
|
-
Plugins earlier in the list are the outermost wrappers. Begin callbacks
|
|
588
|
-
configuration order, while `insert_end` callbacks
|
|
589
|
-
effect, work execution is nested as: middleware before,
|
|
590
|
-
`work_end`, middleware after.
|
|
668
|
+
Plugins earlier in the list are the outermost wrappers. Begin callbacks and
|
|
669
|
+
`work_end` callbacks run in configuration order, while `insert_end` callbacks
|
|
670
|
+
run in reverse order. In effect, work execution is nested as: middleware before,
|
|
671
|
+
`work_begin`, worker, `work_end`, middleware after.
|
|
672
|
+
|
|
673
|
+
Each `work_end(job, error)` receives `nil` on success or the worker's exception
|
|
674
|
+
on failure. Return `error` to preserve that outcome, a different exception to
|
|
675
|
+
replace it, or `nil` to clear a failure. Each subsequent end hook receives the
|
|
676
|
+
previous hook's return value. After all end hooks run, River raises the final
|
|
677
|
+
error through the middleware, or completes the operation if it is `nil`.
|
|
678
|
+
Returning `River.job_cancel(...)` or `River.job_snooze(...)` selects the
|
|
679
|
+
corresponding cancellation or snooze behavior.
|
|
680
|
+
|
|
681
|
+
An observing hook must explicitly return its incoming error:
|
|
682
|
+
|
|
683
|
+
```ruby
|
|
684
|
+
def work_end(job, error)
|
|
685
|
+
Metrics.record_attempt(job.kind, error: error)
|
|
686
|
+
error
|
|
687
|
+
end
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
If a `work_begin` hook raises, River skips the worker and all end hooks. If an
|
|
691
|
+
end hook raises instead of returning an error, it stops the end-hook chain and
|
|
692
|
+
propagates that exception through the middleware. Fatal exceptions outside
|
|
693
|
+
`StandardError` also bypass end hooks. Use middleware with `ensure` for cleanup
|
|
694
|
+
that must run when hooks or work raise.
|
|
591
695
|
|
|
592
696
|
### [Subscriptions](https://riverqueue.com/docs/subscriptions)
|
|
593
697
|
|
|
@@ -687,10 +791,32 @@ A custom service implements `run(client, driver, now)` and runs only while this
|
|
|
687
791
|
Service errors are logged without interrupting other services, stuck-job rescue,
|
|
688
792
|
or cleanup. A failed service is retried on the next scheduled maintenance pass.
|
|
689
793
|
|
|
794
|
+
Call `client.request_resign` to ask the current maintenance leader to resign.
|
|
795
|
+
The leader releases its lease and briefly delays reelection so another client
|
|
796
|
+
can take over. The request joins the caller's transaction and is delivered only
|
|
797
|
+
on commit.
|
|
798
|
+
|
|
690
799
|
On SQLite, the leader also removes notification outbox entries older than five
|
|
691
|
-
minutes in bounded batches.
|
|
692
|
-
|
|
693
|
-
|
|
800
|
+
minutes in bounded batches.
|
|
801
|
+
|
|
802
|
+
### Notifications
|
|
803
|
+
|
|
804
|
+
Started clients receive job insertion, queue control, cancellation, and
|
|
805
|
+
leadership notifications. Postgres uses one dedicated LISTEN connection per
|
|
806
|
+
started client, separate from the application's connection pool. SQLite polls
|
|
807
|
+
the notification outbox with an independent cursor for each client. Inserts,
|
|
808
|
+
scheduled jobs becoming available, and manual retries wake workers promptly.
|
|
809
|
+
|
|
810
|
+
The receiver starts before workers and stays active during graceful shutdown.
|
|
811
|
+
Startup fails if the initial subscription fails; an established receiver retries
|
|
812
|
+
after connection errors and refreshes persisted state on reconnect. Normal job
|
|
813
|
+
and queue polling also remains active.
|
|
814
|
+
|
|
815
|
+
Set `poll_only: true` in `River::Config` to disable receiving notifications, for
|
|
816
|
+
example when connecting through a proxy that cannot support LISTEN. Job, queue,
|
|
817
|
+
and cancellation changes are then observed at the polling interval, and
|
|
818
|
+
`request_resign` is not received. Backends without native notification support,
|
|
819
|
+
such as Yugabyte with LISTEN/NOTIFY disabled, use polling automatically.
|
|
694
820
|
|
|
695
821
|
### [Renaming job kinds](https://riverqueue.com/docs/renaming-jobs)
|
|
696
822
|
|
|
@@ -721,7 +847,7 @@ The Ruby configuration file must return an unstarted client. The following clien
|
|
|
721
847
|
methods are for applications managing their own runtime lifecycle:
|
|
722
848
|
|
|
723
849
|
`client.stop` stops fetching and waits for active jobs to finish. `client.stop_and_cancel` interrupts active worker threads and returns their jobs to `available` without consuming the interrupted attempt.
|
|
724
|
-
While draining, the client continues polling for cancellation requests from
|
|
850
|
+
While draining, the client continues receiving and polling for cancellation requests from
|
|
725
851
|
other clients, including for workers with no timeout. Transient polling errors
|
|
726
852
|
are retried until the active attempts finish.
|
|
727
853
|
|
|
@@ -751,6 +877,11 @@ Load River and worker definitions in the main Ractor before spawning others. Eac
|
|
|
751
877
|
|
|
752
878
|
Database drivers, Active Record, Sequel, and optional dependencies such as Fugit still need their own Ractor compatibility. Job timeouts also depend on Ruby and the `timeout` gem: the test suite exercises them on Ruby 4 with `timeout` 0.6.1; tests on older Rubies disable job timeouts. `WorkerRunner` handles process-wide signals and must run on the main thread of the main Ractor.
|
|
753
879
|
|
|
880
|
+
Ruby 3.3 and 3.4 can deadlock while scheduling threads inside Ractors, even
|
|
881
|
+
without River ([Ruby bug #20905](https://bugs.ruby-lang.org/issues/20905), fixed
|
|
882
|
+
in Ruby 4). The test suite skips the producer/maintenance lifecycle test on
|
|
883
|
+
those versions; insertion and synchronous worker checks still run.
|
|
884
|
+
|
|
754
885
|
Ruby 4.0.2 with `timeout` 0.6.1 can intermittently deadlock during VM shutdown
|
|
755
886
|
while terminating Ractors and their timeout helper threads, even after work
|
|
756
887
|
finishes successfully. This reproduces without River. Tests bypass that shutdown
|
|
@@ -794,18 +925,17 @@ Minitest integrations are optional and explicitly loaded.
|
|
|
794
925
|
|
|
795
926
|
River Pro is kept in the separate, privately distributed `riverqueue-pro` gem, so possession of that package is the access boundary. It is not included in the MPL-2.0 core gem. Its implementation and documentation live in the private `riverqueue-ruby-pro` repository; see that repository's README for configuration and feature examples.
|
|
796
927
|
|
|
797
|
-
## Current
|
|
928
|
+
## Current limitations
|
|
798
929
|
|
|
799
|
-
Ruby checks
|
|
800
|
-
|
|
930
|
+
Ruby checks shared fixtures for unique keys, cron, snooze counters, and
|
|
931
|
+
protocol values.
|
|
801
932
|
See [conformance coverage and differences](conformance.md); this is not yet a
|
|
802
933
|
full mixed-language runtime suite.
|
|
803
934
|
|
|
804
935
|
The Ruby client does not currently provide dedicated OpenTelemetry/metrics
|
|
805
936
|
integrations or
|
|
806
937
|
transactional job completion alongside application writes. Plugins and
|
|
807
|
-
subscriptions provide integration points for telemetry.
|
|
808
|
-
worker objects rather than Go's work-function API.
|
|
938
|
+
subscriptions provide integration points for telemetry.
|
|
809
939
|
|
|
810
940
|
## Development
|
|
811
941
|
|
data/docs/conformance.md
CHANGED
|
@@ -9,7 +9,7 @@ make test/ruby/conformance
|
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
This generates `conformance/testdata/` and runs `ruby/spec/conformance_spec.rb`.
|
|
12
|
-
No
|
|
12
|
+
No Postgres server, pinned upstream checkout, protocol adapter, or separately
|
|
13
13
|
maintained golden files are needed. Missing fixtures fail with instructions to
|
|
14
14
|
regenerate them. The regular Ruby suite includes these checks too.
|
|
15
15
|
|
|
@@ -23,13 +23,13 @@ The fixture checks cover:
|
|
|
23
23
|
- All snooze-counter fixtures through real worker attempts and both SQL drivers.
|
|
24
24
|
- Cron occurrences, named zones, daylight-saving transitions, interval schedules,
|
|
25
25
|
and invalid expressions, with the Ruby API differences below checked explicitly.
|
|
26
|
-
- Insertion, cancellation, queue pause/resume/metadata, and leadership resignation
|
|
26
|
+
- Insertion, cancellation, queue pause/resume/metadata, and leadership resignation/request
|
|
27
27
|
notification encoding through both SQL drivers, using in-memory SQLite and
|
|
28
28
|
the bundled canonical migrations.
|
|
29
29
|
|
|
30
30
|
The Ruby workflow runs on Ruby changes. The shared Conformance workflow also
|
|
31
31
|
runs these tests when Go code changes. The ordinary driver suites retain
|
|
32
|
-
|
|
32
|
+
Postgres notification delivery/commit-ordering tests and shared Postgres /
|
|
33
33
|
SQLite insertion, transaction, uniqueness, and worker tests.
|
|
34
34
|
|
|
35
35
|
This replaces the old `insert-only-v1` adapter and its pinned external harness.
|
|
@@ -39,17 +39,17 @@ protocol is not part of the Ruby API.
|
|
|
39
39
|
|
|
40
40
|
## Coverage gaps and API differences
|
|
41
41
|
|
|
42
|
-
Ruby
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
The
|
|
48
|
-
|
|
42
|
+
Ruby consumes Postgres LISTEN events and the SQLite notification outbox. The
|
|
43
|
+
ordinary driver suites exercise insert wakeups, cancellation while draining,
|
|
44
|
+
queue pause/resume, and leadership handoff on `request_resign` through both
|
|
45
|
+
backends and both SQL drivers. They also check commit ordering, rollback,
|
|
46
|
+
independent listeners, startup failures, and recovery after listener failures.
|
|
47
|
+
The fixture target checks wire formats; live mixed-language worker execution
|
|
48
|
+
remains outside its scope.
|
|
49
49
|
|
|
50
50
|
Shared snooze-counter fixtures cover non-negative integers and absent counters.
|
|
51
51
|
Recovery from other JSON values is implementation-specific. Ruby's shared driver
|
|
52
|
-
tests cover its lenient conversion rules separately on
|
|
52
|
+
tests cover its lenient conversion rules separately on Postgres and SQLite.
|
|
53
53
|
|
|
54
54
|
Cron retains Ruby's existing Fugit extensions: six fields with seconds, Sunday
|
|
55
55
|
as 7, hour 24, and descending ranges. These four expressions are accepted in
|