Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Describe way how to detect cancellation reason #10

Closed
ikokostya opened this issue Jul 5, 2017 · 5 comments
Closed

Describe way how to detect cancellation reason #10

ikokostya opened this issue Jul 5, 2017 · 5 comments

Comments

@ikokostya
Copy link

ikokostya commented Jul 5, 2017

Problem

Assume that we have function which can be cancelled from two sources:

  1. Internal timeout.
  2. External cancellation token.
function doRequest(token: CancellationToken): Promise<any> {
    const timeoutTokenSource = new CancellationTokenSource();
    setTimeout(() => timeoutTokenSource.cancel(), 5000);
    const requestTokenSource = new CancellationTokenSource([
        timeoutTokenSource.token,
        token
    ]);
    return doHttpRequest(requestTokenSource.token);
}

In this case is not possible to distinguish from outside cancellation by timeout and by passed token, because if cancellation requested, function doRequest() always returns promise rejected with CancelError.

In this article author suggests approach with checking tokens for cancellation in catch block:

async function doRequest(token: CancellationToken): Promise<any> {
    const timeoutTokenSource = new CancellationTokenSource();
    setTimeout(() => timeoutTokenSource.cancel(), 5000);
    const requestTokenSource = new CancellationTokenSource([
        timeoutTokenSource.token,
        token
    ]);

    try {
        return await doHttpRequest(requestTokenSource.token);
    } catch (err) {
        if ((err instanceof CancelError) && timeoutTokenSource.token.canellationRequested) {
            throw new TimeoutError();
        }
        throw err;
    }
}

Questions

  • Is it correct approach?

  • Should CancelaltionTokenSource class has cancellationRequested property (like in .NET)? So we can write

    if (timeoutTokenSource.canellationRequested) { ... }

    instead

    if (timeoutTokenSource.token.canellationRequested) { ... }
@rbuckton
Copy link
Collaborator

The initial spec text adds a token property to the CancelError instance it throws, but that will point back to requestTokenSource.token in this case. So this is likely the best approach.

I did not add cancellationRequested to CancellatonTokenSource to simplify the API and remove redundancy. As such, you always write to the source (by calling cancel/close to set its state), and read from the token.

@rbuckton
Copy link
Collaborator

rbuckton commented Jul 24, 2017

If we decide to take on the Stretch Goals in the proposal then you could possibly leverage a custom reason:

async function doRequest(token: CancellationToken): Promise<any> {
    const timeoutTokenSource = new CancellationTokenSource();
    setTimeout(() => timeoutTokenSource.cancel(new TimeoutError()), 5000);
    const requestTokenSource = new CancellationTokenSource([
        timeoutTokenSource.token,
        token
    ]);

    try {
        return await doHttpRequest(requestTokenSource.token);
    } catch (err) {
        if (err instanceof TimeoutError) {
            ...
        }
        throw err;
    }
}

@ikokostya
Copy link
Author

ikokostya commented Jul 26, 2017

The initial spec text adds a token property to the CancelError instance it throws, but that will point back to requestTokenSource.token in this case. So this is likely the best approach.

By specification token property is setup in throwIfCancellationRequested method. But it doesn't work if throw CanelError (or any other error) manually:

async function doRequest(token: CancellationToken) {
    if (token.cancellationRequested) {
        throw new CancelError();
    }
}

const tokenSource = new CancellationTokenSource();

doRequest(tokenSource.token).catch(err => {
    assert(err instanceof CancelError);
    assert(err.token === undefined);
});

tokenSource.cancel();

@ikokostya
Copy link
Author

In .NET OperationCanceledException can take token in constructor.

Maybe CancelError should be declared in similar way:

class CancelError {
    /**
     * Gets a token associated with the operation that was canceled.
     */
    token?: CancellationToken;

    constructor(message?: string, token?: CancellationToken);
}

@rbuckton
Copy link
Collaborator

I'm closing this issue as the proposal no longer specifies cancellation behavior. See #22 for the current thinking regarding cancellation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants