UNPKG

@temporalio/worker

Version:
423 lines (422 loc) 20.7 kB
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;