/api/task/cancel/{id}

POST SYNC

Request cancellation of a running or pending task.

Description

This endpoint requests cancellation of an asynchronous task. When called, it signals the task to stop execution as soon as possible. The cancellation is cooperative, meaning the task must check for cancellation requests and gracefully terminate its operations.

Cancellation Behavior

Task cancellation is not instantaneous. The task must periodically check for cancellation requests and may need time to clean up resources. After calling this endpoint, continue polling the task status to confirm when cancellation completes.

When to Use Task Cancellation
  • User requested to stop a long-running operation
  • Application needs to abort a task due to timeout or resource constraints
  • Task parameters need to be changed, requiring restart with new values
  • System shutdown or maintenance requiring all tasks to stop

Path Parameters

Parameter Type Required Description
id string Yes The unique task identifier to cancel. This must be a valid task ID from an asynchronous operation.

Response

Returns a 200 OK response if the cancellation request was accepted, 400 Bad Request if the task is already finished, or 404 Not Found if the task doesn't exist.

Success Response Properties

task_idstring
The ID of the task that was requested to cancel.
statusstring
Always "cancelled" for successful cancellation requests.
messagestring
A human-readable confirmation message.

Example Response (Success - 200 OK)

{
  "task_id": "a7f8c9d1-e234-56b7-8901-23c45de67f89",
  "status": "cancelled",
  "message": "Task cancellation requested successfully"
}

Example Response (Task Already Finished - 400 Bad Request)

{
  "error": "Cannot cancel task",
  "message": "Task has already finished (state: COMPLETED)",
  "task_id": "a7f8c9d1-e234-56b7-8901-23c45de67f89"
}

Example Response (Task Not Found - 404 Not Found)

{
  "error": "Task not found",
  "message": "No task exists with ID: invalid-task-id",
  "task_id": "invalid-task-id"
}

Error Codes

Status Code Meaning Description
200 OK Cancellation request accepted successfully
400 Bad Request Task is already finished (COMPLETED, FAILED, or CANCELLED) and cannot be cancelled
404 Not Found No task exists with the specified ID
500 Internal Server Error Server error occurred while processing the cancellation request

Examples

JavaScript Example

const apiHost = 'http://localhost:8080';

// Cancel a task and verify cancellation
async function cancelTask(taskId) {
    try {
        // Request cancellation
        const response = await fetch(`${apiHost}/api/task/cancel/${taskId}`, {
            method: 'POST'
        });

        const result = await response.json();

        if (response.ok) {
            console.log('Cancellation requested:', result.message);

            // Continue polling to confirm task reaches CANCELLED state
            await waitForCancellation(taskId);

        } else {
            console.error('Cancellation failed:', result.message);
            throw new Error(result.message);
        }

    } catch (error) {
        console.error('Error cancelling task:', error);
        throw error;
    }
}

// Poll task status until it reaches CANCELLED state
async function waitForCancellation(taskId) {
    const maxAttempts = 30; // 30 seconds
    let attempts = 0;

    while (attempts < maxAttempts) {
        const statusResponse = await fetch(`${apiHost}/api/task/status/${taskId}`);
        const task = await statusResponse.json();

        console.log(`Task state: ${task.state}`);

        if (task.state === 'CANCELLED') {
            console.log('Task successfully cancelled');
            return;
        }

        if (task.is_finished && task.state !== 'CANCELLED') {
            console.log('Task finished before cancellation took effect');
            return;
        }

        await new Promise(resolve => setTimeout(resolve, 1000));
        attempts++;
    }

    console.warn('Timeout waiting for cancellation confirmation');
}

// Usage example
const taskId = 'a7f8c9d1-e234-56b7-8901-23c45de67f89';
cancelTask(taskId)
    .then(() => console.log('Task cancelled successfully'))
    .catch(error => console.error('Failed to cancel task:', error));

cURL Example

# Simple cancellation request
curl -X POST "http://localhost:8080/api/task/cancel/a7f8c9d1-e234-56b7-8901-23c45de67f89"

# Cancel task and check response status
TASK_ID="a7f8c9d1-e234-56b7-8901-23c45de67f89"
API_HOST="http://localhost:8080"

# Request cancellation
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$API_HOST/api/task/cancel/$TASK_ID")
HTTP_CODE=$(echo "$RESPONSE" | tail -n 1)
BODY=$(echo "$RESPONSE" | head -n -1)

echo "HTTP Status: $HTTP_CODE"
echo "Response: $BODY" | jq '.'

# If successful, verify cancellation
if [ "$HTTP_CODE" = "200" ]; then
    echo "Cancellation requested successfully"

    # Poll until cancelled
    while true; do
        STATUS_RESPONSE=$(curl -s "$API_HOST/api/task/status/$TASK_ID")
        STATE=$(echo "$STATUS_RESPONSE" | jq -r '.state')

        echo "Current state: $STATE"

        if [ "$STATE" = "CANCELLED" ]; then
            echo "Task cancelled successfully"
            break
        fi

        sleep 1
    done
else
    echo "Cancellation failed"
fi

Go Example

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "time"
)

const apiHost = "http://localhost:8080"

type CancelResponse struct {
    TaskID  string `json:"task_id"`
    Status  string `json:"status"`
    Message string `json:"message"`
}

type ErrorResponse struct {
    Error   string `json:"error"`
    Message string `json:"message"`
    TaskID  string `json:"task_id"`
}

type TaskStatus struct {
    TaskID     string `json:"task_id"`
    State      string `json:"state"`
    IsFinished bool   `json:"is_finished"`
}

func cancelTask(taskID string) error {
    // Send cancellation request
    url := fmt.Sprintf("%s/api/task/cancel/%s", apiHost, taskID)
    resp, err := http.Post(url, "application/json", nil)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return err
    }

    if resp.StatusCode == http.StatusOK {
        var result CancelResponse
        if err := json.Unmarshal(body, &result); err != nil {
            return err
        }
        fmt.Printf("Cancellation requested: %s\n", result.Message)

        // Wait for cancellation to complete
        return waitForCancellation(taskID)

    } else {
        var errResp ErrorResponse
        if err := json.Unmarshal(body, &errResp); err != nil {
            return err
        }
        return fmt.Errorf("%s: %s", errResp.Error, errResp.Message)
    }
}

func waitForCancellation(taskID string) error {
    maxAttempts := 30 // 30 seconds

    for i := 0; i < maxAttempts; i++ {
        statusURL := fmt.Sprintf("%s/api/task/status/%s", apiHost, taskID)
        resp, err := http.Get(statusURL)
        if err != nil {
            return err
        }

        body, err := io.ReadAll(resp.Body)
        resp.Body.Close()

        if err != nil {
            return err
        }

        var task TaskStatus
        if err := json.Unmarshal(body, &task); err != nil {
            return err
        }

        fmt.Printf("Task state: %s\n", task.State)

        if task.State == "CANCELLED" {
            fmt.Println("Task successfully cancelled")
            return nil
        }

        if task.IsFinished && task.State != "CANCELLED" {
            fmt.Println("Task finished before cancellation took effect")
            return nil
        }

        time.Sleep(1 * time.Second)
    }

    return fmt.Errorf("timeout waiting for cancellation")
}

func main() {
    taskID := "a7f8c9d1-e234-56b7-8901-23c45de67f89"

    if err := cancelTask(taskID); err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }

    fmt.Println("Task cancelled successfully")
}

Try It Out

Test task cancellation with a task ID from your QMail server:

http://localhost:8080/api/task/cancel/{id}
Testing Note

This will actually attempt to cancel the task on your QMail server. Make sure you're using a valid task ID from a running or pending task. Already finished tasks will return a 400 error.

Related Endpoints