UNPKG

@temporalio/workflow

Version:
210 lines 10.2 kB
"use strict"; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var _CancellationScope_cancelRequested; Object.defineProperty(exports, "__esModule", { value: true }); exports.RootCancellationScope = exports.CancellationScope = exports.AsyncLocalStorage = void 0; exports.disableStorage = disableStorage; exports.registerSleepImplementation = registerSleepImplementation; const common_1 = require("@temporalio/common"); const time_1 = require("@temporalio/common/lib/time"); const stack_helpers_1 = require("./stack-helpers"); const global_attributes_1 = require("./global-attributes"); const flags_1 = require("./flags"); // AsyncLocalStorage is injected via vm module into global scope. // In case Workflow code is imported in Node.js context, replace with an empty class. exports.AsyncLocalStorage = globalThis.AsyncLocalStorage ?? class { }; /** Magic symbol used to create the root scope - intentionally not exported */ const NO_PARENT = Symbol('NO_PARENT'); /** * Cancellation Scopes provide the mechanic by which a Workflow may gracefully handle incoming requests for cancellation * (e.g. in response to {@link WorkflowHandle.cancel} or through the UI or CLI), as well as request cancelation of * cancellable operations it owns (e.g. Activities, Timers, Child Workflows, etc). * * Cancellation Scopes form a tree, with the Workflow's main function running in the root scope of that tree. * By default, cancellation propagates down from a parent scope to its children and its cancellable operations. * A non-cancellable scope can receive cancellation requests, but is never effectively considered as cancelled, * thus shieldding its children and cancellable operations from propagation of cancellation requests it receives. * * Scopes are created using the `CancellationScope` constructor or the static helper methods {@link cancellable}, * {@link nonCancellable} and {@link withTimeout}. `withTimeout` creates a scope that automatically cancels itself after * some duration. * * Cancellation of a cancellable scope results in all operations created directly in that scope to throw a * {@link CancelledFailure} (either directly, or as the `cause` of an {@link ActivityFailure} or a * {@link ChildWorkflowFailure}). Further attempt to create new cancellable scopes or cancellable operations within a * scope that has already been cancelled will also immediately throw a {@link CancelledFailure} exception. It is however * possible to create a non-cancellable scope at that point; this is often used to execute rollback or cleanup * operations. For example: * * ```ts * async function myWorkflow(...): Promise<void> { * try { * // This activity runs in the root cancellation scope. Therefore, a cancelation request on * // the Workflow execution (e.g. through the UI or CLI) automatically propagates to this * // activity. Assuming that the activity properly handle the cancellation request, then the * // call below will throw an `ActivityFailure` exception, with `cause` sets to an * // instance of `CancelledFailure`. * await someActivity(); * } catch (e) { * if (isCancellation(e)) { * // Run cleanup activity in a non-cancellable scope * await CancellationScope.nonCancellable(async () => { * await cleanupActivity(); * } * } else { * throw e; * } * } * } * ``` * * A cancellable scope may be programatically cancelled by calling {@link cancel|`scope.cancel()`}`. This may be used, * for example, to explicitly request cancellation of an Activity or Child Workflow: * * ```ts * const cancellableActivityScope = new CancellationScope(); * const activityPromise = cancellableActivityScope.run(() => someActivity()); * cancellableActivityScope.cancel(); // Cancels the activity * await activityPromise; // Throws `ActivityFailure` with `cause` set to `CancelledFailure` * ``` */ class CancellationScope { constructor(options) { _CancellationScope_cancelRequested.set(this, false); this.timeout = (0, time_1.msOptionalToNumber)(options?.timeout); this.cancellable = options?.cancellable ?? true; this.cancelRequested = new Promise((_, reject) => { // @ts-expect-error TSC doesn't understand that the Promise executor runs synchronously this.reject = (err) => { __classPrivateFieldSet(this, _CancellationScope_cancelRequested, true, "f"); reject(err); }; }); (0, stack_helpers_1.untrackPromise)(this.cancelRequested); // Avoid unhandled rejections (0, stack_helpers_1.untrackPromise)(this.cancelRequested.catch(() => undefined)); if (options?.parent !== NO_PARENT) { this.parent = options?.parent || CancellationScope.current(); if (this.parent.cancellable || (__classPrivateFieldGet(this.parent, _CancellationScope_cancelRequested, "f") && !(0, global_attributes_1.getActivator)().hasFlag(flags_1.SdkFlags.NonCancellableScopesAreShieldedFromPropagation))) { __classPrivateFieldSet(this, _CancellationScope_cancelRequested, __classPrivateFieldGet(this.parent, _CancellationScope_cancelRequested, "f"), "f"); (0, stack_helpers_1.untrackPromise)(this.parent.cancelRequested.catch((err) => { this.reject(err); })); } else { (0, stack_helpers_1.untrackPromise)(this.parent.cancelRequested.catch((err) => { if (!(0, global_attributes_1.getActivator)().hasFlag(flags_1.SdkFlags.NonCancellableScopesAreShieldedFromPropagation)) { this.reject(err); } })); } } } /** * Whether the scope was effectively cancelled. A non-cancellable scope can never be considered cancelled. */ get consideredCancelled() { return __classPrivateFieldGet(this, _CancellationScope_cancelRequested, "f") && this.cancellable; } /** * Activate the scope as current and run `fn` * * Any timers, Activities, Triggers and CancellationScopes created in the body of `fn` * automatically link their cancellation to this scope. * * @return the result of `fn` */ run(fn) { return storage.run(this, this.runInContext.bind(this, fn)); } /** * Method that runs a function in AsyncLocalStorage context. * * Could have been written as anonymous function, made into a method for improved stack traces. */ async runInContext(fn) { let timerScope; if (this.timeout) { timerScope = new CancellationScope(); (0, stack_helpers_1.untrackPromise)(timerScope .run(() => sleep(this.timeout)) .then(() => this.cancel(), () => { // scope was already cancelled, ignore })); } try { return await fn(); } finally { if (timerScope && !timerScope.consideredCancelled && (0, global_attributes_1.getActivator)().hasFlag(flags_1.SdkFlags.NonCancellableScopesAreShieldedFromPropagation)) { timerScope.cancel(); } } } /** * Request to cancel the scope and linked children */ cancel() { this.reject(new common_1.CancelledFailure('Cancellation scope cancelled')); } /** * Get the current "active" scope */ static current() { // Using globals directly instead of a helper function to avoid circular import return storage.getStore() ?? globalThis.__TEMPORAL_ACTIVATOR__.rootScope; } /** Alias to `new CancellationScope({ cancellable: true }).run(fn)` */ static cancellable(fn) { return new this({ cancellable: true }).run(fn); } /** Alias to `new CancellationScope({ cancellable: false }).run(fn)` */ static nonCancellable(fn) { return new this({ cancellable: false }).run(fn); } /** Alias to `new CancellationScope({ cancellable: true, timeout }).run(fn)` */ static withTimeout(timeout, fn) { return new this({ cancellable: true, timeout }).run(fn); } } exports.CancellationScope = CancellationScope; _CancellationScope_cancelRequested = new WeakMap(); const storage = new exports.AsyncLocalStorage(); /** * Avoid exposing the storage directly so it doesn't get frozen */ function disableStorage() { storage.disable(); } class RootCancellationScope extends CancellationScope { constructor() { super({ cancellable: true, parent: NO_PARENT }); } cancel() { this.reject(new common_1.CancelledFailure('Workflow cancelled')); } } exports.RootCancellationScope = RootCancellationScope; /** This function is here to avoid a circular dependency between this module and workflow.ts */ let sleep = (_) => { throw new common_1.IllegalStateError('Workflow has not been properly initialized'); }; function registerSleepImplementation(fn) { sleep = fn; } //# sourceMappingURL=cancellation-scope.js.map