Skip to content

fix: forward controlActionCancel to cancelCh in poll-mode fetchAndRunLoop#1245

Open
ata-the-legend wants to merge 1 commit intoriverqueue:masterfrom
ata-the-legend:fix/poll-mode-cancel-notify
Open

fix: forward controlActionCancel to cancelCh in poll-mode fetchAndRunLoop#1245
ata-the-legend wants to merge 1 commit intoriverqueue:masterfrom
ata-the-legend:fix/poll-mode-cancel-notify

Conversation

@ata-the-legend
Copy link
Copy Markdown

Problem

When using River with a driver that does not support LISTEN/NOTIFY (e.g. riverdatabasesql), calling JobCancel on a running job has no effect: the job's context is never cancelled and ctx.Done() never fires inside Work().

Root Cause

JobCancel (non-tx) calls notifyProducerWithoutListenerQueueControlEvent, which sends a controlEventPayload{Action: "cancel"} to queueControlCh. However, in fetchAndRunLoop, the queueControlCh receive block only handled controlActionPause, controlActionResume, and controlActionMetadataChanged. The controlActionCancel case was absent and fell through to default, silently discarding the event.

The controlActionCancel case exists correctly in handleControlNotification (the LISTEN path), but was never added to the parallel poll-mode path.

Fix

Add controlActionCancel to fetchAndRunLoop's queueControlCh switch, forwarding msg.JobID to cancelCh. This is consumed by the existing maybeCancelJob call, which cancels the running job's context via context.WithCancelCause.

Testing

Added a test that verifies ctx.Done() fires inside Work() after JobCancel is called when the driver is in poll mode.

…Loop

When using drivers that don't support LISTEN/NOTIFY (e.g. riverdatabasesql),
job cancel events are routed in-process via queueControlCh. The
controlActionCancel case was missing from fetchAndRunLoop's queueControlCh
handler, causing cancel events to be silently dropped and ctx.Done() to never
fire inside a running Work() call.

Forward the job ID to cancelCh so the existing maybeCancelJob call handles it,
matching the behaviour of the LISTEN/NOTIFY path in handleControlNotification.

Adds a test that verifies ctx.Done() fires in a running job after JobCancel is
called when using a poll-only driver (SupportsListener() == false).
@bgentry
Copy link
Copy Markdown
Contributor

bgentry commented May 10, 2026

I see two issues here:

  1. I don't think this is actually going to do what you think it will, because there's no way for the producer to get these job cancel messages when in poll-only mode. You would need to periodically poll the database for all running job IDs to see if they've been cancelled.
  2. Repeatedly polling the database to ask if running jobs are still actually running could have substantial overhead, or would at least result in a lot of query noise for something that happens rarely.

Rather than going down this path, I think it's more likely we'd want to think about a non-Postgres notifier option of some kind (i.e. Redis). This would lose transactionality, but would be far more scalable and compatible with non-NOTIFY Postgres environments, dbsql drivers, or other databases.

Another option which we've talked about before is letting you use a pgx listener/notifer even if you're using database/sql for the rest of your driver: #352 This potentially covers some Postgres use cases that leverage database/sql, albeit only if you have access to a conn or pool that does have LISTEN/NOTIFY support (like by bypassing pgbouncer).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants