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
data/docs/migrations.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Schema migrations
|
|
2
2
|
|
|
3
|
-
River Ruby includes a migration API and the `river` command.
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
River Ruby includes a migration API and the `river` command. The gems bundle
|
|
4
|
+
byte-for-byte copies of River's canonical Postgres and SQLite migration SQL,
|
|
5
|
+
pinned to release v0.48.0 through migration 008;
|
|
6
6
|
only the upstream schema template placeholders are substituted when executing
|
|
7
7
|
them.
|
|
8
8
|
|
|
@@ -24,7 +24,7 @@ bundle exec river migrate-up --database-url sqlite://storage/river.sqlite3
|
|
|
24
24
|
|
|
25
25
|
`DATABASE_URL` supplies the URL when `--database-url` is omitted. ActiveRecord
|
|
26
26
|
uses `sqlite3:` URLs instead of Sequel's `sqlite:` URLs. To target an existing
|
|
27
|
-
|
|
27
|
+
Postgres schema, pass `--schema jobs`; otherwise the connection's current
|
|
28
28
|
schema is used. Schema names must be simple SQL identifiers. SQLite always
|
|
29
29
|
uses its main schema.
|
|
30
30
|
|
|
@@ -52,22 +52,22 @@ migrator.migrate(dry_run: true, target: 8)
|
|
|
52
52
|
migrator.migrate(direction: :down) # One version; no interactive confirmation.
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
The API accepts either driver and optionally `schema:` for
|
|
55
|
+
The API accepts either driver and optionally `schema:` for Postgres. `migrate`
|
|
56
56
|
returns the migration records applied (or planned in dry-run mode), with their
|
|
57
57
|
version, name, and original up/down SQL. Each version's SQL and history update
|
|
58
58
|
commit in their own transaction. A failing version rolls back while earlier
|
|
59
59
|
versions remain committed, so rerunning resumes safely. Do not wrap the migrator
|
|
60
|
-
in an application transaction: some
|
|
60
|
+
in an application transaction: some Postgres changes require a real commit
|
|
61
61
|
before the next version can run.
|
|
62
62
|
|
|
63
63
|
An up migration targeting an already-applied version does nothing, even when
|
|
64
64
|
newer migrations are pending.
|
|
65
65
|
|
|
66
|
-
##
|
|
66
|
+
## Compatibility and Pro
|
|
67
67
|
|
|
68
|
-
The migrator reads and writes
|
|
69
|
-
legacy pre-version-5 format.
|
|
70
|
-
applied
|
|
68
|
+
The migrator reads and writes the `river_migration` history shared by all River
|
|
69
|
+
clients, including the legacy pre-version-5 format. Any River client can
|
|
70
|
+
continue from versions applied by another. Unknown newer versions and gaps in history cause an error
|
|
71
71
|
rather than guessing what SQL to run. It does not baseline an existing schema
|
|
72
72
|
that has no River migration history.
|
|
73
73
|
|
|
@@ -75,7 +75,7 @@ Pro SQL is distributed only inside `riverqueue-pro`, not the public core gem.
|
|
|
75
75
|
SQLite main migration 008 rebuilds `river_job` with `AUTOINCREMENT` so deleted
|
|
76
76
|
job IDs are not reused. Its upgrade and downgrade refuse to run while Pro's
|
|
77
77
|
workflow/sequence schema is installed; coordinate that upgrade with Pro before
|
|
78
|
-
running it on an existing Pro database.
|
|
78
|
+
running it on an existing Pro database. Postgres migration 008 is a no-op.
|
|
79
79
|
|
|
80
80
|
Migrate main first, then Pro:
|
|
81
81
|
|
|
@@ -92,14 +92,14 @@ River::Pro::Migrator.new(driver).migrate
|
|
|
92
92
|
```
|
|
93
93
|
|
|
94
94
|
Remove other migration lines before downgrading main. Run only one migration
|
|
95
|
-
process at a time across languages. Ruby migrators take a
|
|
95
|
+
process at a time across languages. Ruby migrators take a Postgres advisory
|
|
96
96
|
lock per schema; SQLite takes a write lock per version and checks for concurrent
|
|
97
|
-
history changes. These are not shared
|
|
97
|
+
history changes. These locks are not shared with other River clients' migration runners.
|
|
98
98
|
|
|
99
99
|
## Updating the bundled SQL
|
|
100
100
|
|
|
101
101
|
`migration/manifest.json` records each canonical file's SHA-256. From `ruby/`,
|
|
102
|
-
synchronize or verify against the
|
|
102
|
+
synchronize or verify against the canonical migrations in this same checkout:
|
|
103
103
|
|
|
104
104
|
```sh
|
|
105
105
|
ruby scripts/sync_migrations.rb
|
|
@@ -111,8 +111,8 @@ CI uses the same working-tree comparison; no separate checkout is needed.
|
|
|
111
111
|
|
|
112
112
|
Pro migrations have their own manifest and sync script in the private
|
|
113
113
|
`riverqueue-ruby-pro` repository. Run that repository's `make verify` against
|
|
114
|
-
the
|
|
114
|
+
the River Pro checkout; the public sync script handles only the main migrations.
|
|
115
115
|
|
|
116
116
|
Review the upstream changes and run the complete test matrix before publishing.
|
|
117
117
|
Do not edit the copied SQL independently. Upstream changes are bundled in gem
|
|
118
|
-
releases; users do not need the
|
|
118
|
+
releases; users do not need the upstream repositories or network access at runtime.
|
data/docs/testing.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Testing River jobs
|
|
2
2
|
|
|
3
|
-
Testing helpers ship in `riverqueue`. They use your real
|
|
3
|
+
Testing helpers ship in `riverqueue`. They use your real Postgres or SQLite
|
|
4
4
|
database through either driver; there is no fake queue, global testing mode,
|
|
5
5
|
implicit migration, or automatic cleanup. Require them explicitly:
|
|
6
6
|
|
data/lib/client.rb
CHANGED
|
@@ -49,6 +49,13 @@ module River
|
|
|
49
49
|
@runtime.finish_claimed(row, error)
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
+
# Internal scheduler entry point. Prepare ordinary insertion options and
|
|
53
|
+
# uniqueness before applying an occurrence fallback to the stored schedule.
|
|
54
|
+
def __insert_with_default_scheduled_at(args, insert_opts, default_scheduled_at)
|
|
55
|
+
insert_params = make_insert_params(args, insert_opts, default_scheduled_at)
|
|
56
|
+
run_insert_plugins([insert_params]) { [insert_and_check_unique_job(insert_params)] }.first
|
|
57
|
+
end
|
|
58
|
+
|
|
52
59
|
def __perform_job(id, allow_scheduled: false)
|
|
53
60
|
@runtime.perform_job(id, allow_scheduled: allow_scheduled)
|
|
54
61
|
end
|
|
@@ -77,7 +84,8 @@ module River
|
|
|
77
84
|
#
|
|
78
85
|
# * `#kind`: A symbol or string identifying the job kind in the database.
|
|
79
86
|
# * `#to_json`: Encodes the args to JSON for persistence in the database.
|
|
80
|
-
# Must match
|
|
87
|
+
# Must match the JSON expected by whichever River client works the
|
|
88
|
+
# job.
|
|
81
89
|
#
|
|
82
90
|
# They may also respond to `#insert_opts` which is expected to return an
|
|
83
91
|
# `InsertOpts` that contains options that will apply to all jobs of this
|
|
@@ -106,8 +114,7 @@ module River
|
|
|
106
114
|
def insert(args, insert_opts: nil, **options)
|
|
107
115
|
raise ArgumentError, "use insert_opts or keyword options, not both" if insert_opts && !options.empty?
|
|
108
116
|
insert_opts ||= options.empty? ? EMPTY_INSERT_OPTS : InsertOpts.new(**options)
|
|
109
|
-
|
|
110
|
-
run_insert_plugins([insert_params]) { [insert_and_check_unique_job(insert_params)] }.first
|
|
117
|
+
__insert_with_default_scheduled_at(args, insert_opts, nil)
|
|
111
118
|
end
|
|
112
119
|
|
|
113
120
|
# Inserts many new jobs as part of a single batch operation for improved
|
|
@@ -134,7 +141,8 @@ module River
|
|
|
134
141
|
#
|
|
135
142
|
# * `#kind`: A symbol or string identifying the job kind in the database.
|
|
136
143
|
# * `#to_json`: Encodes the args to JSON for persistence in the database.
|
|
137
|
-
# Must match
|
|
144
|
+
# Must match the JSON expected by whichever River client works the
|
|
145
|
+
# job.
|
|
138
146
|
#
|
|
139
147
|
# For example:
|
|
140
148
|
#
|
|
@@ -182,6 +190,44 @@ module River
|
|
|
182
190
|
@driver.job_cancel(id) || raise(NotFoundError, "job not found: #{id}")
|
|
183
191
|
end
|
|
184
192
|
|
|
193
|
+
# Completes a running Job and returns its updated JobRow, including pending
|
|
194
|
+
# metadata and output. The original JobRow is not modified.
|
|
195
|
+
#
|
|
196
|
+
# Requires and joins the caller's transaction on the same Active Record
|
|
197
|
+
# connection or Sequel database. Commit before returning from work;
|
|
198
|
+
# propagate rollback errors so the runtime can retry the job. A committed
|
|
199
|
+
# completion takes precedence over any subsequent worker error.
|
|
200
|
+
#
|
|
201
|
+
# Raises NotFoundError for a missing job, JobCancelError for a pending
|
|
202
|
+
# cancellation, and Error if there is no active transaction or the job is
|
|
203
|
+
# no longer running or completed.
|
|
204
|
+
def job_complete_tx(job)
|
|
205
|
+
raise ArgumentError, "job must be a River::Job" unless job.is_a?(Job)
|
|
206
|
+
raise ArgumentError, "job must belong to this client" unless job.client.equal?(self)
|
|
207
|
+
raise Error, "job must be running" unless job.row.state == JOB_STATE_RUNNING
|
|
208
|
+
raise Error, "job_complete_tx requires an active transaction on the driver's connection" unless @driver.in_transaction?
|
|
209
|
+
|
|
210
|
+
# Mark the attempt before the write: an interrupt can arrive after the
|
|
211
|
+
# database commits but before the driver returns. The runtime checks the
|
|
212
|
+
# persisted state because the caller may still roll back this transaction.
|
|
213
|
+
job.__completion_attempted = true
|
|
214
|
+
now = @time_now_utc.call
|
|
215
|
+
completed = @driver.job_complete(id: job.row.id, finalized_at: now, expected_job: job.row, metadata: job.metadata_updates, now: now)
|
|
216
|
+
raise JobCancelError if completed == :cancelled
|
|
217
|
+
|
|
218
|
+
completed ||= job_get(job.row.id)
|
|
219
|
+
unless completed.attempt == job.row.attempt && completed.attempted_at == job.row.attempted_at
|
|
220
|
+
raise Error, "job attempt is no longer current"
|
|
221
|
+
end
|
|
222
|
+
raise Error, "job must be running" unless completed.state == JOB_STATE_COMPLETED
|
|
223
|
+
|
|
224
|
+
# Retention or application code may delete the row before work returns.
|
|
225
|
+
# Only retain a snapshot after the caller's outer transaction commits;
|
|
226
|
+
# rolled-back transactions and savepoints must not report completion.
|
|
227
|
+
@driver.after_commit { job.__completion_snapshot = completed }
|
|
228
|
+
completed
|
|
229
|
+
end
|
|
230
|
+
|
|
185
231
|
# Deletes a job by ID and returns its former JobRow.
|
|
186
232
|
#
|
|
187
233
|
# Raises NotFoundError if the job does not exist and JobRunningError if it
|
|
@@ -310,7 +356,13 @@ module River
|
|
|
310
356
|
@driver.queue_update(name.to_s, metadata: metadata) || raise(NotFoundError, "queue not found: #{name}")
|
|
311
357
|
end
|
|
312
358
|
|
|
313
|
-
#
|
|
359
|
+
# Requests that the current leader resign. Delivery waits for the caller's
|
|
360
|
+
# Active Record or Sequel transaction to commit, if one is open.
|
|
361
|
+
def request_resign
|
|
362
|
+
@driver.request_resign
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# Starts notification receiving, polling, and working configured queues.
|
|
314
366
|
# Returns self.
|
|
315
367
|
def start
|
|
316
368
|
@runtime.start
|
|
@@ -379,7 +431,7 @@ module River
|
|
|
379
431
|
JobInsertResult.new(job, unique_skipped_as_duplicated: unique_skipped_as_duplicate)
|
|
380
432
|
end
|
|
381
433
|
|
|
382
|
-
private def make_insert_params(args, insert_opts)
|
|
434
|
+
private def make_insert_params(args, insert_opts, default_scheduled_at = nil)
|
|
383
435
|
raise ArgumentError, "args should respond to `#kind`" if !args.respond_to?(:kind)
|
|
384
436
|
|
|
385
437
|
# ~all objects in Ruby respond to `#to_json`, so check non-nil instead.
|
|
@@ -424,6 +476,9 @@ module River
|
|
|
424
476
|
insert_params.unique_states = unique_states
|
|
425
477
|
end
|
|
426
478
|
|
|
479
|
+
if scheduled_at.nil? && default_scheduled_at
|
|
480
|
+
insert_params.scheduled_at = default_scheduled_at.getutc
|
|
481
|
+
end
|
|
427
482
|
insert_params
|
|
428
483
|
end
|
|
429
484
|
|