@temporalio/worker
Version:
Temporal.io SDK Worker sub-package
423 lines (422 loc) • 20.7 kB
TypeScript
import { EventEmitter } from 'node:events';
import { BehaviorSubject, MonoTypeOperatorFunction, Observable, OperatorFunction, Subject } from 'rxjs';
import { Info as ActivityInfo } from '@temporalio/activity';
import { DataConverter, defaultPayloadConverter, MetricMeter } from '@temporalio/common';
import { Decoded } from '@temporalio/common/lib/internal-non-workflow';
import { Duration } from '@temporalio/common/lib/time';
import { OmitFirstParam } from '@temporalio/common/lib/type-helpers';
import { native } from '@temporalio/core-bridge';
import { coresdk } from '@temporalio/proto';
import { type SinkCall } from '@temporalio/workflow';
import { NativeConnection } from './connection';
import { Logger } from './logger';
import { ReplayHistoriesIterable, ReplayResult } from './replay';
import { History, Runtime } from './runtime';
import { CloseableGroupedObservable } from './rxutils';
import { CompiledWorkerOptions, CompiledWorkerOptionsWithBuildId, ReplayWorkerOptions, WorkerOptions } from './worker-options';
import { WorkflowCodecRunner } from './workflow-codec-runner';
import { Workflow, WorkflowCreator } from './workflow/interface';
import { WorkflowBundleWithSourceMapAndFilename } from './workflow/workflow-worker-thread/input';
export { DataConverter, defaultPayloadConverter };
/**
* The worker's possible states
* * `INITIALIZED` - The initial state of the Worker after calling {@link Worker.create} and successful connection to the server
* * `RUNNING` - {@link Worker.run} was called, polling task queues
* * `STOPPING` - {@link Worker.shutdown} was called or received shutdown signal, worker will forcefully shutdown in {@link WorkerOptions.shutdownGraceTime | shutdownGraceTime}
* * `DRAINING` - Core has indicated that shutdown is complete and all Workflow tasks have been drained, waiting for activities and cached workflows eviction
* * `DRAINED` - All activities and workflows have completed, ready to shutdown
* * `STOPPED` - Shutdown complete, {@link Worker.run} resolves
* * `FAILED` - Worker encountered an unrecoverable error, {@link Worker.run} should reject with the error
*/
export type State = 'INITIALIZED' | 'RUNNING' | 'STOPPED' | 'STOPPING' | 'DRAINING' | 'DRAINED' | 'FAILED';
type WorkflowActivation = coresdk.workflow_activation.WorkflowActivation;
export type ActivityTaskWithBase64Token = {
task: coresdk.activity_task.ActivityTask;
base64TaskToken: string;
protobufEncodedTask: Buffer;
};
export interface NativeWorkerLike {
type: 'worker';
initiateShutdown: OmitFirstParam<typeof native.workerInitiateShutdown>;
finalizeShutdown(): Promise<void>;
flushCoreLogs(): void;
pollWorkflowActivation: OmitFirstParam<typeof native.workerPollWorkflowActivation>;
pollActivityTask: OmitFirstParam<typeof native.workerPollActivityTask>;
completeWorkflowActivation: OmitFirstParam<typeof native.workerCompleteWorkflowActivation>;
completeActivityTask: OmitFirstParam<typeof native.workerCompleteActivityTask>;
recordActivityHeartbeat: OmitFirstParam<typeof native.workerRecordActivityHeartbeat>;
}
export interface NativeReplayHandle {
worker: NativeWorkerLike;
historyPusher: native.HistoryPusher;
}
interface NativeWorkerConstructor {
create(runtime: Runtime, connection: NativeConnection, options: CompiledWorkerOptionsWithBuildId): Promise<NativeWorkerLike>;
createReplay(runtime: Runtime, options: CompiledWorkerOptionsWithBuildId): Promise<NativeReplayHandle>;
}
interface WorkflowWithLogAttributes {
workflow: Workflow;
logAttributes: Record<string, unknown>;
}
export declare class NativeWorker implements NativeWorkerLike {
protected readonly runtime: Runtime;
protected readonly nativeWorker: native.Worker;
readonly type = "worker";
readonly pollWorkflowActivation: OmitFirstParam<typeof native.workerPollWorkflowActivation>;
readonly pollActivityTask: OmitFirstParam<typeof native.workerPollActivityTask>;
readonly completeWorkflowActivation: OmitFirstParam<typeof native.workerCompleteWorkflowActivation>;
readonly completeActivityTask: OmitFirstParam<typeof native.workerCompleteActivityTask>;
readonly recordActivityHeartbeat: OmitFirstParam<typeof native.workerRecordActivityHeartbeat>;
readonly initiateShutdown: OmitFirstParam<typeof native.workerInitiateShutdown>;
static create(runtime: Runtime, connection: NativeConnection, options: CompiledWorkerOptionsWithBuildId): Promise<NativeWorkerLike>;
static createReplay(runtime: Runtime, options: CompiledWorkerOptionsWithBuildId): Promise<NativeReplayHandle>;
protected constructor(runtime: Runtime, nativeWorker: native.Worker);
flushCoreLogs(): void;
finalizeShutdown(): Promise<void>;
}
/**
* Notify that an activity has started, used as input to {@link Worker.activityHeartbeatSubject}
*
* Used to detect rogue activities.
*/
interface HeartbeatCreateNotification {
type: 'create';
base64TaskToken: string;
}
/**
* Heartbeat request used as input to {@link Worker.activityHeartbeatSubject}
*/
interface Heartbeat {
type: 'heartbeat';
info: ActivityInfo;
base64TaskToken: string;
taskToken: Uint8Array;
details?: any;
onError: () => void;
}
/**
* Notifies that an activity has been complete, used as input to {@link Worker.activityHeartbeatSubject}
*/
interface ActivityCompleteNotification {
type: 'completion';
flushRequired: boolean;
callback(): void;
base64TaskToken: string;
}
/**
* Notifies that an Activity heartbeat has been flushed, used as input to {@link Worker.activityHeartbeatSubject}
*/
interface HeartbeatFlushNotification {
type: 'flush';
base64TaskToken: string;
}
/**
* Input for the {@link Worker.activityHeartbeatSubject}
*/
type HeartbeatInput = Heartbeat | ActivityCompleteNotification | HeartbeatFlushNotification | HeartbeatCreateNotification;
export type PollerState = 'POLLING' | 'SHUTDOWN' | 'FAILED';
/**
* Status overview of a Worker.
* Useful for troubleshooting issues and overall obeservability.
*/
export interface WorkerStatus {
/**
* General run state of a Worker
*/
runState: State;
/**
* General state of the Workflow poller
*/
workflowPollerState: PollerState;
/**
* General state of the Activity poller
*/
activityPollerState: PollerState;
/**
* Whether this Worker has an outstanding Workflow poll request
*/
hasOutstandingWorkflowPoll: boolean;
/**
* Whether this Worker has an outstanding Activity poll request
*/
hasOutstandingActivityPoll: boolean;
/**
* Number of in-flight (currently actively processed) Workflow activations
*/
numInFlightWorkflowActivations: number;
/**
* Number of in-flight (currently actively processed) Activities
*
* This includes both local and non-local Activities.
*
* See {@link numInFlightNonLocalActivities} and {@link numInFlightLocalActivities} for a breakdown.
*/
numInFlightActivities: number;
/**
* Number of in-flight (currently actively processed) non-Local Activities
*/
numInFlightNonLocalActivities: number;
/**
* Number of in-flight (currently actively processed) Local Activities
*/
numInFlightLocalActivities: number;
/**
* Number of Workflow executions cached in Worker memory
*/
numCachedWorkflows: number;
/**
* Number of running Activities that have emitted a heartbeat
*/
numHeartbeatingActivities: number;
}
interface RunUntilOptions {
/**
* Maximum time to wait for the provided Promise to complete after the Worker has stopped or failed.
*
* Until TS SDK 1.11.2, `Worker.runUntil(...)` would wait _indefinitely_ for both the Worker's run
* Promise _and_ the provided Promise to resolve or fail, _even in error cases_. In most practical
* use cases, that would create a possibility for the Worker to hang indefinitely if the Worker
* was stopped due to "unexpected" factors
*
* For example, in the common test idiom show below, sending a `SIGINT` to the process would
* initiate shutdown of the Worker, potentially resulting in termination of the Worker before the
* Workflow completes; in that case, the Workflow would never complete, and consequently, the
* `runUntil` Promise would never resolve, leaving the process in a hang state.
*
* ```ts
* await Worker.runUntil(() => client.workflow.execute(...));
* ```
*
* The behavior of `Worker.runUntil(...)` has therefore been changedin 1.11.3 so that if the worker
* shuts down before the inner promise completes, `runUntil` will allow no more than a certain delay
* (i.e. `promiseCompletionTimeout`) for the inner promise to complete, after which a
* {@link PromiseCompletionTimeoutError} is thrown.
*
* In most practical use cases, no delay is actually required; `promiseCompletionTimeout` therefore
* defaults to 0 second, meaning the Worker will not wait for the inner promise to complete.
* You may adjust this value in the very rare cases where waiting is pertinent; set it to a
* very high value to mimic the previous behavior.
*
* This time is calculated from the moment the Worker reachs either the `STOPPED` or the `FAILED`
* state. {@link Worker.runUntil} throws a {@link PromiseCompletionTimeoutError} if the if the
* Promise still hasn't completed after that delay.
*
* @default 0 don't wait
*/
promiseCompletionTimeout?: Duration;
}
/**
* The temporal Worker connects to Temporal Server and runs Workflows and Activities.
*/
export declare class Worker {
protected readonly runtime: Runtime;
protected readonly nativeWorker: NativeWorkerLike;
/**
* Optional WorkflowCreator - if not provided, Worker will not poll on Workflows
*/
protected readonly workflowCreator: WorkflowCreator | undefined;
readonly options: CompiledWorkerOptions;
/** Logger bound to 'sdkComponent: worker' */
protected readonly logger: Logger;
protected readonly metricMeter: MetricMeter;
protected readonly connection?: NativeConnection | undefined;
protected readonly isReplayWorker: boolean;
protected readonly activityHeartbeatSubject: Subject<HeartbeatInput>;
protected readonly stateSubject: BehaviorSubject<State>;
protected readonly unexpectedErrorSubject: Subject<void>;
protected readonly instantTerminateErrorSubject: Subject<void>;
protected readonly workflowPollerStateSubject: BehaviorSubject<PollerState>;
protected readonly activityPollerStateSubject: BehaviorSubject<PollerState>;
/**
* Whether or not this worker has an outstanding workflow poll request
*/
protected hasOutstandingWorkflowPoll: boolean;
/**
* Whether or not this worker has an outstanding activity poll request
*/
protected hasOutstandingActivityPoll: boolean;
protected readonly numInFlightActivationsSubject: BehaviorSubject<number>;
protected readonly numInFlightActivitiesSubject: BehaviorSubject<number>;
protected readonly numInFlightNonLocalActivitiesSubject: BehaviorSubject<number>;
protected readonly numInFlightLocalActivitiesSubject: BehaviorSubject<number>;
protected readonly numCachedWorkflowsSubject: BehaviorSubject<number>;
protected readonly numHeartbeatingActivitiesSubject: BehaviorSubject<number>;
protected readonly evictionsEmitter: EventEmitter;
protected static nativeWorkerCtor: NativeWorkerConstructor;
protected static replayWorkerCount: number;
private static readonly SELF_INDUCED_SHUTDOWN_EVICTION;
protected readonly workflowCodecRunner: WorkflowCodecRunner;
/**
* Create a new Worker.
* This method initiates a connection to the server and will throw (asynchronously) on connection failure.
*/
static create(options: WorkerOptions): Promise<Worker>;
protected static createWorkflowCreator(workflowBundle: WorkflowBundleWithSourceMapAndFilename, compiledOptions: CompiledWorkerOptions, logger: Logger): Promise<WorkflowCreator>;
/**
* Create a replay Worker, and run the provided history against it. Will resolve as soon as
* the history has finished being replayed, or if the workflow produces a nondeterminism error.
*
* @param workflowId If provided, use this as the workflow id during replay. Histories do not
* contain a workflow id, so it must be provided separately if your workflow depends on it.
* @throws {@link DeterminismViolationError} if the workflow code is not compatible with the history.
* @throws {@link ReplayError} on any other replay related error.
*/
static runReplayHistory(options: ReplayWorkerOptions, history: History | unknown, workflowId?: string): Promise<void>;
/**
* Create a replay Worker, running all histories provided by the passed in iterable.
*
* Returns an async iterable of results for each history replayed.
*/
static runReplayHistories(options: ReplayWorkerOptions, histories: ReplayHistoriesIterable): AsyncIterableIterator<ReplayResult>;
private static validateHistory;
private static constructReplayWorker;
protected static getOrCreateBundle(compiledOptions: CompiledWorkerOptions, logger: Logger): Promise<WorkflowBundleWithSourceMapAndFilename | undefined>;
/**
* Create a new Worker from nativeWorker.
*/
protected constructor(runtime: Runtime, nativeWorker: NativeWorkerLike,
/**
* Optional WorkflowCreator - if not provided, Worker will not poll on Workflows
*/
workflowCreator: WorkflowCreator | undefined, options: CompiledWorkerOptions,
/** Logger bound to 'sdkComponent: worker' */
logger: Logger, metricMeter: MetricMeter, connection?: NativeConnection | undefined, isReplayWorker?: boolean);
/**
* An Observable which emits each time the number of in flight activations changes
*/
get numInFlightActivations$(): Observable<number>;
/**
* An Observable which emits each time the number of in flight Activity tasks changes
*/
get numInFlightActivities$(): Observable<number>;
/**
* An Observable which emits each time the number of cached workflows changes
*/
get numRunningWorkflowInstances$(): Observable<number>;
/**
* Get the poll state of this worker
*/
getState(): State;
/**
* Get a status overview of this Worker
*/
getStatus(): WorkerStatus;
protected get state(): State;
protected set state(state: State);
/**
* Start shutting down the Worker. The Worker stops polling for new tasks and sends
* {@link https://typescript.temporal.io/api/namespaces/activity#cancellation | cancellation}
* (via a {@link CancelledFailure} with `message` set to `'WORKER_SHUTDOWN'`) to running Activities.
* Note: if the Activity accepts cancellation (i.e. re-throws or allows the `CancelledFailure`
* to be thrown out of the Activity function), the Activity Task will be marked as failed, not
* cancelled. It's helpful for the Activity Task to be marked failed during shutdown because the
* Server will retry the Activity sooner (than if the Server had to wait for the Activity Task
* to time out).
*
* When called, immediately transitions {@link state} to `'STOPPING'` and asks Core to shut down.
* Once Core has confirmed that it's shutting down, the Worker enters `'DRAINING'` state. It will
* stay in that state until both task pollers receive a `ShutdownError`, at which point we'll
* transition to `DRAINED` state. Once all currently running Activities and Workflow Tasks have
* completed, the Worker transitions to `'STOPPED'`.
*/
shutdown(): void;
/**
* An observable that completes when {@link state} becomes `'DRAINED'` or throws if {@link state} transitions to
* `'STOPPING'` and remains that way for {@link this.options.shutdownForceTimeMs}.
*/
protected forceShutdown$(): Observable<never>;
/**
* An observable which repeatedly polls for new tasks unless worker becomes suspended.
* The observable stops emitting once core is shutting down.
*/
protected pollLoop$<T>(pollFn: () => Promise<T>): Observable<T>;
/**
* Process Activity tasks
*/
protected activityOperator(): OperatorFunction<ActivityTaskWithBase64Token, Uint8Array>;
/**
* Process activations from the same workflow execution to an observable of completions.
*
* Injects a synthetic eviction activation when the worker transitions to no longer polling.
*/
protected handleWorkflowActivations(activations$: CloseableGroupedObservable<string, coresdk.workflow_activation.WorkflowActivation>): Observable<Uint8Array>;
/**
* Process a single activation to a completion.
*/
protected handleActivation(workflow: WorkflowWithLogAttributes | undefined, { activation, synthetic }: {
activation: coresdk.workflow_activation.WorkflowActivation;
synthetic: boolean;
}): Promise<{
state: WorkflowWithLogAttributes | undefined;
output: {
completion?: Uint8Array;
close: boolean;
};
}>;
protected createWorkflow(activation: Decoded<coresdk.workflow_activation.WorkflowActivation>, initWorkflowJob: Decoded<coresdk.workflow_activation.IInitializeWorkflow>): Promise<WorkflowWithLogAttributes>;
/**
* Process extracted external calls from Workflow post activation.
*
* Each SinkCall is translated into a injected sink function call.
*
* This function does not throw, it will log in case of missing sinks
* or failed sink function invocations.
*/
protected processSinkCalls(externalCalls: SinkCall[], isReplaying: boolean, logAttributes: Record<string, unknown>): Promise<void>;
/**
* Listen on heartbeats emitted from activities and send them to core.
* Errors from core responses are translated to cancellation requests and fed back via the activityFeedbackSubject.
*/
protected activityHeartbeat$(): Observable<void>;
/**
* Poll core for `WorkflowActivation`s while respecting worker state.
*/
protected workflowPoll$(): Observable<WorkflowActivation>;
/**
* Poll for Workflow activations, handle them, and report completions.
*/
protected workflow$(): Observable<void>;
/**
* Poll core for `ActivityTask`s while respecting worker state
*/
protected activityPoll$(): Observable<ActivityTaskWithBase64Token>;
protected activity$(): Observable<void>;
protected takeUntilState<T>(state: State): MonoTypeOperatorFunction<T>;
/**
* Run the Worker until `fnOrPromise` completes, then {@link shutdown} and wait for {@link run} to complete.
*
* Be aware that the Worker may shutdown for reasons other than the completion of the provided promise,
* e.g. due to the process receiving a SIGINT signal, direct call to `Worker.shutdown()`, or a critical
* error that imposes a shutdown of the Worker.
*
* Throws on fatal Worker errors.
*
* **SDK versions `>=1.11.3`**:
* If the worker shuts down before the inner promise completes, allow no more than
* {@link RunUntilOptions.promiseCompletionTimeout} for the inner promise to complete,
* after which a {@link PromiseCompletionTimeoutError} is thrown.
*
* **SDK versions `>=1.5.0`**:
* This method always waits for both worker shutdown and inner `fnOrPromise` to complete.
* If one of worker run -or- the inner promise throw an error, that error is rethrown.
* If both throw an error, a {@link CombinedWorkerRunError} with a `cause` attribute containing both errors.
*
* **SDK versions `< 1.5.0`**:
* This method would not wait for worker to complete shutdown if the inner `fnOrPromise` threw an error.
*
* @returns the result of `fnOrPromise`
*/
runUntil<R>(fnOrPromise: Promise<R> | (() => Promise<R>), options?: RunUntilOptions): Promise<R>;
/**
* Start polling on the Task Queue for tasks. Completes after graceful {@link shutdown}, once the Worker reaches the
* `'STOPPED'` state.
*
* Throws on a fatal error or failure to shutdown gracefully.
*
* @see {@link errors}
*
* To stop polling, call {@link shutdown} or send one of {@link Runtime.options.shutdownSignals}.
*/
run(): Promise<void>;
}
export declare function parseWorkflowCode(code: string, codePath?: string): WorkflowBundleWithSourceMapAndFilename;