Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- Recover `JobCancel()` notifications lost during notifier disconnects by reconciling running jobs after reconnect. [PR #1361](https://github.com/riverqueue/river/pull/1361).
- Deliver `JobCancel()` locally in `PollOnly` mode without blocking unstarted or stopping clients. [PR #1361](https://github.com/riverqueue/river/pull/1361).
- Clear previous cancellation metadata when retrying a job. [PR #1361](https://github.com/riverqueue/river/pull/1361).
- Fixed `AddWorkerSafely` leaving a worker's primary kind and earlier aliases registered when a later kind alias conflicts with an existing registration. Failed registrations now leave the worker registry unchanged. [PR #1440](https://github.com/riverqueue/river/pull/1440).
- Fixed the notifier retrying without backoff and flooding logs when the context passed to `Client.Start` reaches its deadline. [PR #1495](https://github.com/riverqueue/river/pull/1495).

Expand Down
45 changes: 40 additions & 5 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -1533,11 +1533,13 @@ func (c *Client[TTx]) JobCancel(ctx context.Context, jobID int64) (*rivertype.Jo
return nil, err
}

c.notifyProducerWithoutListenerQueueControlEvent(job.Queue, &controlEventPayload{
Action: controlActionCancel,
JobID: job.ID,
Queue: job.Queue,
})
if job.State == rivertype.JobStateRunning {
c.notifyProducerOfCancelWithoutNotifier(ctx, job.Queue, &controlEventPayload{
Action: controlActionCancel,
JobID: job.ID,
Queue: job.Queue,
})
}

return job, nil
}
Expand Down Expand Up @@ -2982,6 +2984,39 @@ func (c *Client[TTx]) notifyProducerWithoutListenerQueueControlEvent(queue strin
}
}

// Deliver cancellations locally when no notifier is configured, including PollOnly.
// Only call after commit so the producer can observe the cancellation metadata.
func (c *Client[TTx]) notifyProducerOfCancelWithoutNotifier(ctx context.Context, queue string, controlEvent *controlEventPayload) {
if c.notifier != nil {
return
}

c.producersMu.RLock()
producer := c.producersByQueueName[queue]
c.producersMu.RUnlock()
if producer == nil {
return
}

state := producer.cancelState.Load()
if state == nil {
return
}
select {
case <-state.done:
return
default:
}

// cancelCh is also drained while a database fetch is in progress.
select {
case producer.cancelCh <- controlEvent.JobID:
producer.testSignals.QueueControlEventTriggered.Signal(controlEvent)
case <-ctx.Done():
case <-state.done:
}
}

func (c *Client[TTx]) queueUpdate(ctx context.Context, executorTx riverdriver.ExecutorTx, name string, params *QueueUpdateParams) (*rivertype.Queue, *controlEventPayload, error) {
updateMetadata := len(params.Metadata) > 0

Expand Down
Loading