Skip to content

Commit

Permalink
CTS.cancel() no longer returns Promise
Browse files Browse the repository at this point in the history
  • Loading branch information
rbuckton committed May 25, 2017
1 parent 95b971f commit 948e228
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 92 deletions.
64 changes: 27 additions & 37 deletions src/lib/cancellation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,36 +76,26 @@ export class CancellationTokenSource {
}

/**
* Cancels the source, returning a Promise that is settled when cancellation has completed.
* Any registered callbacks are executed in a later turn. If any callback raises an exception,
* the first such exception can be observed by awaiting the return value of this method.
* Cancels the source, evaluating any registered callbacks. If any callback raises an exception,
* the exception is propagated to a host specific unhanedle exception mechanism.
*/
public cancel(): Promise<void> {
return new Promise<void>((resolve, reject) => {
if (this._state !== "open") {
resolve();
return;
}

this._state = "cancellationRequested";
this._unlink();
public cancel(): void {
if (this._state !== "open") {
return;
}

const callbacks = this._callbacks;
this._callbacks = undefined;
this._state = "cancellationRequested";
this._unlink();

if (callbacks && callbacks.size > 0) {
const pendingOperations: Promise<void>[] = [];
for (const callback of callbacks) {
pendingOperations.push(this._executeCallback(callback));
}
const callbacks = this._callbacks;
this._callbacks = undefined;

// await all pending operations
Promise.all(pendingOperations).then(() => resolve(), reject);
return;
if (callbacks && callbacks.size > 0) {
const pendingOperations: Promise<void>[] = [];
for (const callback of callbacks) {
this._executeCallback(callback);
}

resolve();
});
}
}

/**
Expand Down Expand Up @@ -139,8 +129,8 @@ export class CancellationTokenSource {
/*@internal*/ _register(callback: () => void): CancellationTokenRegistration {
if (!isFunction(callback)) throw new TypeError("Function expected: callback.");

if (this._state === "requested") {
callback();
if (this._state === "cancellationRequested") {
this._executeCallback(callback);
}

if (this._state !== "open") {
Expand Down Expand Up @@ -171,14 +161,18 @@ export class CancellationTokenSource {
}

/**
* Executes the provided callback in a later turn.
* Executes the provided callback.
*
* @param callback The callback to execute.
*/
private _executeCallback(callback: () => void): Promise<void> {
return Promise.resolve().then(() => {
private _executeCallback(callback: () => void): void {
try {
callback();
});
}
catch (e) {
// HostReportError(e)
setTimeout(() => { throw e; }, 0);
}
}

/**
Expand Down Expand Up @@ -220,11 +214,7 @@ export class CancellationToken {

private _source: CancellationTokenSource;

/**
* Creates a new instance of a CancellationToken.
*
* @param canceled An optional value indicating whether the token is canceled.
*/
/*@internal*/
constructor(canceled?: boolean);

/*@internal*/
Expand Down Expand Up @@ -298,4 +288,4 @@ export interface CancellationTokenRegistration {
unregister(): void;
}

const emptyRegistration: CancellationTokenRegistration = Object.create({ unregister() { }})
const emptyRegistration: CancellationTokenRegistration = Object.create({ unregister() { } });
15 changes: 3 additions & 12 deletions src/tests/autoresetevent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,24 +49,15 @@ describe("autoresetevent", () => {
const event = new AutoResetEvent();
const source = new CancellationTokenSource();
const waitPromise = event.wait(source.token);
await source.cancel();
source.cancel();
await assert.throwsAsync(() => waitPromise, CancelError);
});
it("when canceled after wait but before set", async () => {
const event = new AutoResetEvent();
const source = new CancellationTokenSource();
const waitPromise = event.wait(source.token);
const cancelPromise = source.cancel();
event.set();
await cancelPromise;
await waitPromise;
});
it("when canceled after set", async () => {
const event = new AutoResetEvent();
const source = new CancellationTokenSource();
const waitPromise = event.wait(source.token);
event.set();
await source.cancel();
source.cancel();
await waitPromise;
});
it("throws if token not CancellationToken", async () => {
Expand Down Expand Up @@ -119,7 +110,7 @@ describe("autoresetevent", () => {
steps.push("set1");
event.set();

await Promise.resolve();
await delay(1);

steps.push("set2");
event.set();
Expand Down
80 changes: 37 additions & 43 deletions src/tests/cancellation.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
Licensed under the Apache License, Version 2.0.
Copyright (c) Microsoft Corporation.
Licensed under the Apache License, Version 2.0.
See LICENSE file in the project root for details.
***************************************************************************** */

import { assert } from "chai";
import { create } from "domain";
import { CancellationTokenSource, CancellationToken, CancellationTokenRegistration, CancelError } from "../lib";

describe("cancellation", () => {
Expand All @@ -17,9 +18,9 @@ describe("cancellation", () => {
assert.isTrue(source.token.canBeCanceled);
assert.isFalse(source.token.cancellationRequested);
});
it("cancel", async () => {
it("cancel", () => {
const source = new CancellationTokenSource();
await source.cancel();
source.cancel();
assert.isTrue(source.token.canBeCanceled);
assert.isTrue(source.token.cancellationRequested);
});
Expand All @@ -29,36 +30,41 @@ describe("cancellation", () => {
assert.isFalse(source.token.canBeCanceled);
assert.isFalse(source.token.cancellationRequested);
});
it("linked tokens", async () => {
it("linked tokens", () => {
const source1 = new CancellationTokenSource();
const linkedSource = new CancellationTokenSource([source1.token]);
await source1.cancel();
source1.cancel();
assert.isTrue(linkedSource.token.canBeCanceled);
assert.isTrue(linkedSource.token.cancellationRequested);
});
it("error when not a linked token", () => {
assert.throws(() => new CancellationTokenSource(<any>[{}]), TypeError);
});
it("linked tokens already canceled", async () => {
it("linked tokens already canceled", () => {
const source1 = new CancellationTokenSource();
await source1.cancel();
source1.cancel();
const linkedSource = new CancellationTokenSource([source1.token]);
assert.isTrue(linkedSource.token.canBeCanceled);
assert.isTrue(linkedSource.token.cancellationRequested);
});
it("cancel throws if token registration throws", async () => {
const source = new CancellationTokenSource();
const token = source.token;
it("cancel throws if token registration throws", (done) => {
const error = new Error("Error during registration.");
const registration = token.register(() => { throw error; });
let caughtError: Error;
try {
await source.cancel();
}
catch (e) {
caughtError = e;
}
assert.strictEqual(caughtError, error);
const domain = create();
domain.on("error", e => {
try {
assert.strictEqual(e, error);
done();
}
catch (e) {
done(e);
}
});
domain.run(() => {
const source = new CancellationTokenSource();
const token = source.token;
const registration = token.register(() => { throw error; });
source.cancel();
});
});
it("register throws when not a function", () => {
const source = new CancellationTokenSource();
Expand All @@ -75,10 +81,10 @@ describe("cancellation", () => {
const token = source.token;
assert.doesNotThrow(() => token.throwIfCancellationRequested());
});
it("throwIfCancellationRequested when canceled", async () => {
it("throwIfCancellationRequested when canceled", () => {
const source = new CancellationTokenSource();
const token = source.token;
await source.cancel();
source.cancel();
assert.throws(() => token.throwIfCancellationRequested(), CancelError);
});
it("close", () => {
Expand All @@ -94,10 +100,10 @@ describe("cancellation", () => {
assert.isFalse(token.canBeCanceled);
assert.isFalse(token.cancellationRequested);
});
it("new token for source", async () => {
it("new token for source", () => {
const source = new CancellationTokenSource();
const token = new CancellationToken(source);
await source.cancel();
source.cancel();
assert.notStrictEqual(source.token, token);
assert.isTrue(token.canBeCanceled);
assert.isTrue(token.cancellationRequested);
Expand All @@ -109,43 +115,31 @@ describe("cancellation", () => {
});

describe("registration", () => {
it("cancel", async () => {
it("cancel", () => {
const source = new CancellationTokenSource();
const token = source.token;
let callbackInvocations = 0;
const registration = token.register(() => callbackInvocations++);
await source.cancel();
await source.cancel();
source.cancel();
source.cancel();
assert.strictEqual(callbackInvocations, 1);
});
it("cancel (after unregistered)", async () => {
it("cancel (after unregistered)", () => {
const source = new CancellationTokenSource();
const token = source.token;
let callbackInvocations = 0;
const registration = token.register(() => callbackInvocations++);
registration.unregister();
await source.cancel();
source.cancel();
assert.strictEqual(callbackInvocations, 0);
});
it("cancel executes in later turn", async () => {
const source = new CancellationTokenSource();
const token = source.token;
let callbackInvocations = 0;
const registration = token.register(() => callbackInvocations++);
const cancelPromise = source.cancel();
const callbackInvocationsInitialTurn = callbackInvocations;
await cancelPromise;
const callbackInvocationsLaterTurn = callbackInvocations;
assert.strictEqual(callbackInvocationsInitialTurn, 0);
assert.strictEqual(callbackInvocationsLaterTurn, 1);
});
it("close", async () => {
it("close", () => {
const source = new CancellationTokenSource();
const token = source.token;
let callbackInvocations = 0;
const registration = token.register(() => callbackInvocations++);
source.close();
await source.cancel();
source.cancel();
assert.strictEqual(callbackInvocations, 0);
});
});
Expand Down

0 comments on commit 948e228

Please sign in to comment.