Skip to content

Feature: Task cancellation with abort hooks #14

@deepjoy

Description

@deepjoy

Summary

Support cancelling tasks by group, type, or custom filter, with an on_cancel hook in TaskExecutor for cleanup logic.

Motivation

When a user cancels a sync profile or the connectivity monitor goes offline, we need to:

  1. Cancel all pending tasks for that profile without affecting other profiles
  2. Clean up in-flight work (e.g. call AbortMultipartUpload for uploads that won't complete)
  3. Give running executors a signal to stop gracefully

Without cancellation hooks, orphaned multipart uploads accumulate on S3 and incur storage charges. Without scoped cancellation, stopping one profile requires stopping the entire scheduler.

Proposed Behavior

  • Scoped cancellation — cancel by group, type, or predicate:
    // Cancel all tasks for a specific endpoint
    scheduler.cancel_group("s3://play.min.io").await;
    
    // Cancel all tasks of a type
    scheduler.cancel_type("upload-part").await;
    
    // Cancel with a predicate
    scheduler.cancel_where(|task| task.dedup_key().starts_with("profile:dr-backup")).await;
  • Cancellation tokenTaskContext exposes a cancellation token that running executors can check:
    async fn execute<'a>(&'a self, ctx: &'a TaskContext) -> Result<(), TaskError> {
        for chunk in data.chunks(BUFFER_SIZE) {
            ctx.check_cancelled()?;  // returns Err(TaskError::Cancelled) if cancelled
            self.upload(chunk).await?;
        }
        Ok(())
    }
  • Abort hookTaskExecutor gains an optional on_cancel method:
    async fn on_cancel<'a>(&'a self, ctx: &'a TaskContext) -> Result<(), TaskError> {
        let upload_id: String = ctx.state().get("upload_id")?;
        self.s3.abort_multipart_upload(&upload_id).await?;
        Ok(())
    }
  • Parent cancellation cascades — cancelling a parent task cancels all its pending/running children and invokes their on_cancel hooks
  • Cancelled tasks are recorded in task history with HistoryStatus::Cancelled

Design Considerations

  • on_cancel should have a timeout to prevent cleanup from blocking indefinitely
  • Cancellation of a running task should be cooperative (via token check), not forced (task kill)
  • Cancelled children should not cause the parent's finalize to run — the parent should also transition to cancelled
  • Batch cancellation should be atomic where possible (cancel all matching tasks in one operation, not one at a time)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions