worker icon indicating copy to clipboard operation
worker copied to clipboard

New job_key_mode - abort_replace

Open aubergene opened this issue 6 months ago • 1 comments

Feature description

(I am evaluating Graphile worker to see if it could fit the use case I have in mind)

I would like a new job_key_mode mode perhaps called abort_replace which would trigger abort on the locked task with the matching job_key

Motivating example

I have a job which takes a while to complete and relies on data. If a new job has been submitted which has the same jobKey then the locked task is running with stale data and can be aborted since the result of the task won't be needed

Supporting development

I [tick all that apply]:

  • [x] am interested in building this feature myself
  • [x] am interested in collaborating on building this feature
  • [x] am willing to help testing this feature before it's released
  • [x] am willing to write a test-driven test suite for this feature (before it exists)
  • [ ] am a Graphile sponsor ❤️
  • [ ] have an active support or consultancy contract with Graphile

aubergene avatar Jun 13 '25 12:06 aubergene

I've bounced back and forth on this for a bit... but I couldn't think of a really good reason not to do it.

It would require that each and every job now needs its own abort signal, whereas previously the abortsignal was shared by all jobs, but I don't anticipate that having a significant impact on performance. One critical thing to note is that this would not be resilient. Essentially the worker that's running the job to be cancelled would need to be connected and listening for the abort event. If it gets disconnected from postgres, even for just a second, and happens to miss this message then I have zero interest in making this message resilient.


At the end of the day this is something that you could easily implement externally to Graphile Worker (you just need a message broker and to listen for a cancel signal from inside your task function) so it doesn't feel necessary to complicate Graphile Worker to support it. If it can be implemented without increasing complexity then it feels like a nice add, but if resilience is required you're better off handling it via your task functions directly.

An alternative solution to this, that would make features like this easier to implement without requiring Worker to support them natively, would be to allow tasks to subscribe to events on worker's listener via e.g. helpers.pubsub.subscribe(...). This way your task function could do:

const abortable = (cb) => (payload, helpers) => {
  // Create a new local abort controller
  const controller = new AbortController();

  // And a topic to listen on
  const topic = `abort:${helpers.task.jobKey}`;

  // When either Worker shuts down or the pubsub event is emitted, abort!
  const abort = () => controller.abort();
  helpers.abortSignal.addEventListener("abort", abort);
  helpers.pubsub.subscribe(topic, abort);

  try {
    // Do the normal task, but override abortSignal with our enhanced version
    return await cb(payload, { ...helpers, abortSignal: controller.signal });
  } finally {
    helpers.abortSignal.removeEventListener("abort", abort);
    // We'd do this for you so no real need, but it's cleaner:
    pubsub.unsubscribe(topic, abort);
  }
}

export const task = abortable(async (payload, { abortSignal }) => {
  // Your normal task executor here, using your new fancy abortSignal
  await doSomethingWith({ abortSignal });
});

Taking an optional helper like abortable and adding it to core is also an option - that way users opt-in to any performance hit only when they need it.

benjie avatar Jun 19 '25 16:06 benjie