I have an Oban job that does some work, and then calls itself with different data. (The work it does traverses up an external datasource to find the next ID to start from, which is why it works like this).
Each one of these chains of jobs is traversing a unique piece of data indicated in the args by a special data source, so it’s easy for me to figure out which one I want to cancel. The data source ID is propagated every time the job inserts itself, with a pointer to the next page for it to process.
What’s the best way to cancel one of these which may already be in progress for a particular piece of data without letting it keep going and inserting itself?
Looking at the documentation for something like cancel_all_jobs, I can query for the job but it seems like the job might still complete after I cancel it, thus inserting itself and not helping in this scenario as the job would keep traversing the data source.
This is a continuation of a Slack thread from the oban channel: Slack
The cancel_all_jobs function takes a query, not a single job id. If you craft a query for that data source it would apply to all cancellable jobs, namely jobs that are available, scheduled, executing, or retryable. Any “in flight” job, one that isn’t completed, cancelled, or discarded will be cancelled as desired.
However, if I query for the job with cancel all, or cancel it by job ID with cancel, either way; is there not still the race condition where I call cancel and then the job is cancelled but it still manages to insert itself before it stops executing?
Sample code below to show what I’m talking about:
defmodule MyApp.Business do
use Oban.Worker, queue: :events
@impl Oban.Worker
def perform(%Oban.Job{args: %{"datasource_id" => datasource_id, "page_id" => page_id} = args}) do
next_page_id = do_work(datasource_id, page_id)
if next_page_id do
%{args | "page_id" => next_page_id}
|> MyApp.Business.new()
|> Oban.insert()
end
:ok
end
end
What I’m worried about is that the job is currently executing, calling do_work and then I cancel it, it inserts itself, and then the cancel goes through. A new job would still be spawned. I am not sure if this is possible or not
It is possible, as killing the job process itself isn’t transactional. The only way to be sure is to check the status of the previous job. This is precisely one of the use cases that chained jobs was designed for