@temporalio/worker
Version:
Temporal.io SDK Worker sub-package
176 lines • 7.72 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReusableVMWorkflow = exports.ReusableVMWorkflowCreator = void 0;
const node_assert_1 = __importDefault(require("node:assert"));
const node_url_1 = require("node:url");
const node_util_1 = require("node:util");
const node_async_hooks_1 = require("node:async_hooks");
const node_vm_1 = __importDefault(require("node:vm"));
const common_1 = require("@temporalio/common");
const type_helpers_1 = require("@temporalio/common/lib/type-helpers");
const core_bridge_1 = require("@temporalio/core-bridge");
const logger_1 = require("../logger");
const vm_shared_1 = require("./vm-shared");
/**
* A WorkflowCreator that creates VMWorkflows in the current isolate
*/
class ReusableVMWorkflowCreator {
constructor(script, workflowBundle, isolateExecutionTimeoutMs,
/** Known activity names registered on the executing worker */
registeredActivityNames) {
this.workflowBundle = workflowBundle;
this.isolateExecutionTimeoutMs = isolateExecutionTimeoutMs;
this.registeredActivityNames = registeredActivityNames;
if (!ReusableVMWorkflowCreator.unhandledRejectionHandlerHasBeenSet) {
(0, vm_shared_1.setUnhandledRejectionHandler)((runId) => ReusableVMWorkflowCreator.workflowByRunId.get(runId));
ReusableVMWorkflowCreator.unhandledRejectionHandlerHasBeenSet = true;
}
const sharedModules = new Map();
const __webpack_module_cache__ = new Proxy({}, {
get: (_, p) => {
// Try the shared modules first
const sharedModule = sharedModules.get(p);
if (sharedModule) {
return sharedModule;
}
const moduleCache = this.context.__TEMPORAL_ACTIVATOR__?.moduleCache;
return moduleCache?.get(p);
},
set: (_, p, val) => {
const moduleCache = this.context.__TEMPORAL_ACTIVATOR__?.moduleCache;
if (moduleCache != null) {
moduleCache.set(p, val);
}
else {
// Workflow has not yet been loaded, share the module
sharedModules.set(p, val);
}
return true;
},
});
const globals = {
AsyncLocalStorage: node_async_hooks_1.AsyncLocalStorage,
URL: node_url_1.URL,
URLSearchParams: node_url_1.URLSearchParams,
assert: node_assert_1.default,
__webpack_module_cache__,
TextEncoder: node_util_1.TextEncoder,
TextDecoder: node_util_1.TextDecoder,
AbortController,
};
this._context = node_vm_1.default.createContext(globals, { microtaskMode: 'afterEvaluate' });
this.injectConsole();
script.runInContext(this.context);
this.contextKeysToPreserve = new Set(Object.keys(this.context));
for (const v of sharedModules.values()) {
(0, type_helpers_1.deepFreeze)(v);
}
for (const k of this.contextKeysToPreserve) {
(0, type_helpers_1.deepFreeze)(this.context[k]);
}
}
get context() {
const { _context } = this;
if (_context == null) {
throw new common_1.IllegalStateError('Tried to use v8 context after Workflow creator was destroyed');
}
return _context;
}
/**
* Inject console.log and friends into a vm context.
*
* Overridable for test purposes.
*/
injectConsole() {
(0, vm_shared_1.injectConsole)(this.context);
}
/**
* Create a workflow with given options
*/
async createWorkflow(options) {
const context = this.context;
const bag = {};
const { isolateExecutionTimeoutMs, contextKeysToPreserve } = this;
const workflowModule = new Proxy({}, {
get(_, fn) {
return (...args) => {
Object.assign(context, bag);
// runInContext does not accept args, pass via globals
context.__TEMPORAL_ARGS__ = args;
try {
return node_vm_1.default.runInContext(`__TEMPORAL__.api.${fn}(...__TEMPORAL_ARGS__)`, context, {
timeout: isolateExecutionTimeoutMs,
displayErrors: true,
});
}
finally {
const keysToDelete = [];
// TODO: non-enumerable global properties?
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects
for (const k in context) {
if (!contextKeysToPreserve.has(k)) {
bag[k] = context[k];
context[k] = undefined;
keysToDelete.push(k);
}
}
for (const k in keysToDelete) {
delete context[k];
}
// Need to preserve this for the unhandledRejection handler.
// TODO: There's probably a better way but this is simplest since we want to maintain compatibility with
// the non-reusable vm implementation.
context.__TEMPORAL_ACTIVATOR__ = bag.__TEMPORAL_ACTIVATOR__;
}
};
},
});
workflowModule.initRuntime({
...options,
sourceMap: this.workflowBundle.sourceMap,
getTimeOfDay: () => (0, logger_1.timeOfDayToBigint)((0, core_bridge_1.getTimeOfDay)()),
registeredActivityNames: this.registeredActivityNames,
});
const activator = bag.__TEMPORAL_ACTIVATOR__;
const newVM = new ReusableVMWorkflow(options.info.runId, context, activator, workflowModule);
ReusableVMWorkflowCreator.workflowByRunId.set(options.info.runId, newVM);
return newVM;
}
/**
* Create a new instance, pre-compile scripts from given code.
*
* This method is generic to support subclassing.
*/
static async create(workflowBundle, isolateExecutionTimeoutMs, registeredActivityNames) {
vm_shared_1.globalHandlers.install(); // Call is idempotent
await vm_shared_1.globalHandlers.addWorkflowBundle(workflowBundle);
const script = new node_vm_1.default.Script(workflowBundle.code, { filename: workflowBundle.filename });
return new this(script, workflowBundle, isolateExecutionTimeoutMs, registeredActivityNames);
}
/**
* Cleanup the pre-compiled script
*/
async destroy() {
vm_shared_1.globalHandlers.removeWorkflowBundle(this.workflowBundle);
delete this._context;
}
}
exports.ReusableVMWorkflowCreator = ReusableVMWorkflowCreator;
/**
* TODO(bergundy): Get rid of this static state somehow
*/
ReusableVMWorkflowCreator.unhandledRejectionHandlerHasBeenSet = false;
ReusableVMWorkflowCreator.workflowByRunId = new Map();
/**
* A Workflow implementation using Node.js' built-in `vm` module
*/
class ReusableVMWorkflow extends vm_shared_1.BaseVMWorkflow {
async dispose() {
ReusableVMWorkflowCreator.workflowByRunId.delete(this.runId);
}
}
exports.ReusableVMWorkflow = ReusableVMWorkflow;
//# sourceMappingURL=reusable-vm.js.map