⚠️ This release contains a new database migration, version 8, but it only affects SQLite:
- If you're on Postgres, you can ignore it with no adverse effect.
- If you're on SQLite, it rebuilds
river_jobto add anAUTOINCREMENTkeyword to the primary key, preventing a possible edge case where generated job IDs could be reused after deletion. It's not necessary to run the migration for River to work, but it's a good idea to get it in when convenient. PR #1390.
Added
- Added support for YugabyteDB. When
LISTEN/NOTIFYis unavailable or disabled, clients automatically poll for running job cancellations and queue pause, resume, and metadata changes, and skip unsupported notification broadcasts. This works with the defaultPollOnly: false. Native notifications require YugabyteDB 2025.2.3 or later withysql_yb_enable_listen_notify=trueon both Masters and TServers. PR #1347. - Added
Config.LeaderElectionDisabledto let a client work jobs without participating in leader election or running maintenance services. Other eligible clients in the same database and schema continue handling scheduling, retries, periodic enqueueing, rescue, and cleanup. PR #1382. - Added
Config.FetchOnlyKnownKindsto restrict job fetching to registered worker kinds, including aliases. Clients with different workers can share a queue while leaving unknown jobs available without consuming attempts. Disabled by default; leader election and stuck-job rescue behavior are unchanged. PR #1396.
Changed
UniqueOpts.ByPeriodnow derives a job's period from its effective scheduled time (InsertOpts.ScheduledAtwhen set, otherwise the insertion time), so scheduled jobs are deduplicated against other jobs scheduled in the same period rather than against jobs inserted in the same period. Periods are also now always measured in UTC, so processes andScheduledAtvalues in different time zones produce the same unique key for the same period. Unique keys for scheduledByPeriodjobs, and for anyByPeriodjob inserted from a process whose local time zone isn't UTC, differ from those produced by previous versions. During a rolling upgrade, old and new clients may therefore each insert one job for such a period; jobs that aren't scheduled and are inserted from UTC processes are unaffected. PR #1377.UniqueOpts{ExcludeKind: true}alone is now rejected at insert time instead of being silently ignored.UniqueOpts.isEmpty()now considersExcludeKind, in line with the equivalent handling in internal/dbunique. Warning: This new rejection can be considered a minor breaking change. PR #1404.
Fixed
- Fixed cancelled transaction starts leaving Turso connections unusable, which could prevent maintenance from recovering after a startup failure. PR #1347.
- Fixed maintenance startup failures leaving a client renewing leadership with maintenance stopped in poll-only mode. After exhausting startup retries, clients now request local resignation without depending on database notifications. PR #1347.
- Fixed
JobRescueroverwriting jobs that complete, leave the running state, or are claimed again by another worker after being fetched for rescue, preserving their state, errors, metadata, and timestamps across PostgreSQL and SQLite drivers. Fixes #1302. PR #1373. - Fixed SQLite job list pagination skipping or repeating jobs by formatting cursor timestamps consistently with stored timestamps. PR #1374.
- Improved PostgreSQL job listing performance when filtering by one finalized state (
completed,cancelled, ordiscarded) and sorting by finalized time, including in River UI. PR #1374. - Fixed
rivermigrateleavingriver_migrationrows behind after migrating a non-main migration line down through its version 1, which caused a later up migration of that line to skip version 1. WithMigrateTx, rows for every removed version were left behind. PR #1378. - Fixed
river benchinserting every benchmark job with anumarg of0instead of numbering jobs sequentially. PR #1379. - Attempt errors that are valid JSON but don't have the shape River writes (for example an
attimestamp in another format, anattemptstored as a string, or anerrorortracethat isn't a string) are now decoded on a best effort basis when reading jobs from the database. Timestamps outside RFC 3339 are left zero; stored values are unchanged. Previously a single such element made its job unreadable, and if that happened while fetching jobs, every job locked in the same fetch was leftrunningindefinitely. PR #1380. - A fetched job whose row can't be decoded (for example a SQLite job whose
tagswere changed to something other than an array of strings) no longer leaves every job locked in the same fetch stuckrunning. The other jobs are worked normally, while the undecodable job's attempt fails with an error describing the decode failure, and it's retried or discarded like any other failed job. Its original values are preserved, with non-arrayerrorsvalues wrapped to append the failure or rescue error. Neither the rescuer nor the SQLite scheduler fails on such a job, so its retry doesn't stop other jobs from being rescued or scheduled. PR #1380. - Fixed SQLite notification listeners delivering notifications from before a subscription or from an unsubscribe gap. Notification reads now fetch subscribed topics in bounded batches, and cleanup deletes expired notifications in batches of 10,000 rows (reduced to 1,000 after repeated timeouts), with pauses between batches to reduce write lock contention. PR #1381.
- Fixed the job completer panicking when a job it was finalizing had its state changed concurrently, like being moved to
pendingout of band, or being rescued while the completer's update waited on the row lock (in which case PostgreSQL returns the job's pre-updaterunningrow). Such jobs are now skipped without emitting a completion event. PR #1383. - Fixed
JobListpagination skipping or repeating jobs when ordering byJobListOrderByTimewith multiple states. Cursors now use the same time field as the list's ordering (the one for the first listed state) rather than the one for each job's own state. Jobs where that field is null, likefinalized_atfor unfinalized jobs, are paginated correctly and consistently sort last in ascending order and first in descending order on all drivers. Ordering byJobListOrderByTimewith an emptyStates()filter now usesscheduled_atinstead of returning an error. PR #1384. - A SQLite job whose
args,attempted_by,errors,metadata, ortagswere changed to text that isn't valid JSON no longer makes every fetch from its queue fail with a "malformed JSON" error. The job's attempt fails like that of any other job that can't be decoded, and completing, rescuing, or scheduling it no longer fails either. Invalid values are left in place, except that an invaliderrorsvalue is kept as a string in a new array so that attempt errors can still be appended. PR #1386. - Fixed
UniqueOpts.ByArgsskipping distinct jobs or failing inserts when JSON keys contain path syntax (likeuser.id), are empty, or come from unnamed tags likejson:",omitempty". Unaffected unique keys remain unchanged; affected jobs may be inserted again after upgrading or by old and new clients during a rolling upgrade. PR #1387. - Fixed
JobListCursor.UnmarshalTextrejecting valid cursors whose URL-safe base64 encoding contains-or_, so cursors produced byMarshalTextalways round-trip. PR #1388. - Fixed SQLite drivers deleting jobs in a finalized state whose retention period was set to -1 (keep forever), like
Config.DiscardedJobRetentionPeriod: -1, whenever another state's retention period was finite. PR #1389. - Fixed SQLite reusing the ID of a deleted job when that job held the largest ID, which could cause an ID observed earlier to refer to an unrelated job later. PR #1390.
- Fixed the
Job appears to be stucklog line reporting the client-levelJobTimeoutinstead of the worker-level timeout when a worker overridesTimeout. PR #1394. - Fixed job cancellations received during a fetch being lost before the fetched jobs started. Matching jobs now receive cancellation before work begins. PR #1397.
- Fixed SQLite
JobCancelandJobCancelTxnotifying running workers through the shared control outbox, so their contexts are cancelled when the transaction commits. PR #1398. - Fixed SQLite
InsertManyandInsertManyTxreporting multiple inserted jobs when a batch contains the same active unique key more than once. Such batches now fail atomically, matching PostgreSQL. PR #1399. - Fixed error and panic handlers receiving the wrong job row when a single execution reports errors for multiple jobs. PR #1401.
- Fixed the default retry policy scheduling a job's retry about 292 years in the past on amd64 once the job had errored 310 or more times, which made it run again immediately. The capped retry delay is now exactly the maximum duration on every architecture. PR #1402.
- Fixed up migrations targeting an already-applied version to do nothing instead of applying later pending migrations. PR #1403.
- Fixed remote cancellation leaving peer jobs running until rescue when a worker returns per-job results for several jobs. The cancelled job now settles as cancelled, and peers settle according to their own results. PR #1408.
- Fixed
JobCancelreturning a stale pre-commit row to the loser of a concurrent-cancel race. The query's fallback read now takes a row lock (FOR UPDATE), matching the documented "returns the up-to-dateJobRow" contract. The analogous shape inJobRetryis known and will follow separately. PR #1409. - Fixed
JobRetryreturning a stale pre-commit row to the loser of a concurrent retry race. The CTE's fallback read now takes a row lock (FOR UPDATE), the same shape as theJobCancelfix. PR #1410.