UNPKG

@temporalio/worker

Version:
176 lines 7.72 kB
"use strict"; 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