@temporalio/worker
Version:
Temporal.io SDK Worker sub-package
189 lines • 8.89 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Activity = void 0;
exports.activityLogAttributes = activityLogAttributes;
require("abort-controller/polyfill"); // eslint-disable-line import/no-unassigned-import
const activity_1 = require("@temporalio/activity");
const common_1 = require("@temporalio/common");
const internal_non_workflow_1 = require("@temporalio/common/lib/internal-non-workflow");
const interceptors_1 = require("@temporalio/common/lib/interceptors");
const type_helpers_1 = require("@temporalio/common/lib/type-helpers");
const logger_1 = require("@temporalio/common/lib/logger");
const metrics_1 = require("@temporalio/common/lib/metrics");
const UNINITIALIZED = Symbol('UNINITIALIZED');
class Activity {
info;
fn;
dataConverter;
heartbeatCallback;
cancelReason;
context;
cancel = () => undefined;
abortController = new AbortController();
interceptors;
/**
* Logger bound to `sdkComponent: worker`, with metadata from this activity.
* This is the logger to use for all log messages emitted by the activity
* worker. Note this is not exactly the same thing as the activity context
* logger, which is bound to `sdkComponent: activity`.
*/
workerLogger;
/**
* Metric Meter with tags from this activity, including tags from interceptors.
*/
metricMeter;
constructor(info, fn, dataConverter, heartbeatCallback, workerLogger, workerMetricMeter, interceptors) {
this.info = info;
this.fn = fn;
this.dataConverter = dataConverter;
this.heartbeatCallback = heartbeatCallback;
this.workerLogger = logger_1.LoggerWithComposedMetadata.compose(workerLogger, this.getLogAttributes.bind(this));
this.metricMeter = metrics_1.MetricMeterWithComposedTags.compose(workerMetricMeter, this.getMetricTags.bind(this));
const promise = new Promise((_, reject) => {
this.cancel = (reason) => {
this.cancelReason = reason;
const err = new common_1.CancelledFailure(reason);
this.abortController.abort(err);
reject(err);
};
});
this.context = new activity_1.Context(info, promise, this.abortController.signal, this.heartbeatCallback,
// This is the activity context logger, to be used exclusively from user code
logger_1.LoggerWithComposedMetadata.compose(this.workerLogger, { sdkComponent: common_1.SdkComponent.activity }), this.metricMeter);
// Prevent unhandled rejection
promise.catch(() => undefined);
this.interceptors = { inbound: [], outbound: [] };
interceptors
.map((factory) => factory(this.context))
.forEach(({ inbound, outbound }) => {
if (inbound)
this.interceptors.inbound.push(inbound);
if (outbound)
this.interceptors.outbound.push(outbound);
});
}
getLogAttributes() {
const logAttributes = activityLogAttributes(this.info);
// In case some interceptor uses the logger while initializing...
if (this.interceptors == null)
return logAttributes;
return (0, interceptors_1.composeInterceptors)(this.interceptors.outbound, 'getLogAttributes', (a) => a)(logAttributes);
}
getMetricTags() {
const baseTags = {
namespace: this.info.workflowNamespace,
taskQueue: this.info.taskQueue,
activityType: this.info.activityType,
};
// In case some interceptors use the metric meter while initializing...
if (this.interceptors == null)
return baseTags;
return (0, interceptors_1.composeInterceptors)(this.interceptors.outbound, 'getMetricTags', (a) => a)(baseTags);
}
/**
* Actually executes the function.
*
* Any call up to this function and including this one will be trimmed out of stack traces.
*/
async execute(fn, input) {
let error = UNINITIALIZED; // In case someone decides to throw undefined...
const startTime = process.hrtime.bigint();
this.workerLogger.debug('Activity started');
try {
const executeNextHandler = ({ args }) => fn(...args);
const executeWithInterceptors = (0, interceptors_1.composeInterceptors)(this.interceptors.inbound, 'execute', executeNextHandler);
return await executeWithInterceptors(input);
}
catch (err) {
error = err;
throw err;
}
finally {
const durationNanos = process.hrtime.bigint() - startTime;
const durationMs = Number(durationNanos / 1000000n);
if (error === UNINITIALIZED) {
this.workerLogger.debug('Activity completed', { durationMs });
}
else if ((error instanceof common_1.CancelledFailure || (0, type_helpers_1.isAbortError)(error)) &&
this.context.cancellationSignal.aborted) {
this.workerLogger.debug('Activity completed as cancelled', { durationMs });
}
else if (error instanceof activity_1.CompleteAsyncError) {
this.workerLogger.debug('Activity will complete asynchronously', { durationMs });
}
else {
if (error instanceof common_1.ApplicationFailure && error.category === common_1.ApplicationFailureCategory.BENIGN) {
// Downgrade log level to DEBUG for benign application errors.
this.workerLogger.debug('Activity failed', { error, durationMs });
}
else {
this.workerLogger.warn('Activity failed', { error, durationMs });
}
}
}
}
run(input) {
return activity_1.asyncLocalStorage.run(this.context, async () => {
try {
if (this.fn === undefined)
throw new common_1.IllegalStateError('Activity function is not defined');
const result = await this.execute(this.fn, input);
return { completed: { result: await (0, internal_non_workflow_1.encodeToPayload)(this.dataConverter, result) } };
}
catch (err) {
if (err instanceof activity_1.CompleteAsyncError) {
return { willCompleteAsync: {} };
}
if (this.cancelReason === 'HEARTBEAT_DETAILS_CONVERSION_FAILED') {
// Ignore actual failure, it is likely a CancelledFailure but server
// expects activity to only fail with ApplicationFailure
return {
failed: {
failure: await (0, internal_non_workflow_1.encodeErrorToFailure)(this.dataConverter, common_1.ApplicationFailure.retryable(this.cancelReason, 'CancelledFailure')),
},
};
}
else if (this.cancelReason) {
// Either a CancelledFailure that we threw or AbortError from AbortController
if (err instanceof common_1.CancelledFailure) {
const failure = await (0, internal_non_workflow_1.encodeErrorToFailure)(this.dataConverter, err);
failure.stackTrace = undefined;
return { cancelled: { failure } };
}
else if ((0, type_helpers_1.isAbortError)(err)) {
return { cancelled: { failure: { source: common_1.FAILURE_SOURCE, canceledFailureInfo: {} } } };
}
}
return {
failed: {
failure: await (0, internal_non_workflow_1.encodeErrorToFailure)(this.dataConverter, (0, common_1.ensureApplicationFailure)(err)),
},
};
}
});
}
runNoEncoding(fn, input) {
if (this.fn !== undefined)
throw new common_1.IllegalStateError('Activity function is defined');
return activity_1.asyncLocalStorage.run(this.context, () => this.execute(fn, input));
}
}
exports.Activity = Activity;
/**
* Returns a map of attributes to be set on log messages for a given Activity
*/
function activityLogAttributes(info) {
return {
isLocal: info.isLocal,
attempt: info.attempt,
namespace: info.workflowNamespace,
taskToken: info.base64TaskToken,
workflowId: info.workflowExecution.workflowId,
workflowRunId: info.workflowExecution.runId,
workflowType: info.workflowType,
activityId: info.activityId,
activityType: info.activityType,
taskQueue: info.taskQueue,
};
}
//# sourceMappingURL=activity.js.map