{"record":{"id":"173162734a5d9021","repo":"denoland/deno","slug":"err-closed-message-port","errorCode":"ERR_CLOSED_MESSAGE_PORT","errorMessage":"Cannot send data on closed MessagePort","messagePattern":"Cannot send data on closed MessagePort","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ext/node/polyfills/worker_threads.ts","lineNumber":1753,"sourceCode":"// underlying port are deserialized without the global host-object\n// deserializers, mirroring Node's behavior where the target context lacks\n// the JS classes registered in the source realm: any host object (e.g. a\n// crypto KeyObject) triggers `messageerror` with the\n// `ERR_MESSAGE_TARGET_CONTEXT_UNAVAILABLE` code, while plain transferable\n// data is delivered as `message`.\nfunction moveMessagePortToContext(\n  port: MessagePort,\n  context: object,\n): object {\n  if (!(ObjectPrototypeIsPrototypeOf(MessagePortPrototype, port))) {\n    throw new ERR_INVALID_ARG_TYPE(\"port\", \"MessagePort\", port);\n  }\n  // Node checks closed-port state before vm.Context to give a clearer\n  // error when the port is detached -- order matters for tests in the\n  // node_compat suite that pass an empty {} as the context.\n  const portId = getMessagePortId(port);\n  if (portId === null) {\n    throw new ERR_CLOSED_MESSAGE_PORT();\n  }\n  const vm = lazyVm();\n  if (!vm.isContext(context)) {\n    throw new ERR_INVALID_ARG_TYPE(\"context\", \"vm.Context\", context);\n  }\n  // Take ownership of the port: clear the id on the original so it can no\n  // longer be used from this context.\n  setMessagePortId(port, null);\n\n  // Allocate the wrapper inside the target context so its prototype chain\n  // is the target realm's (i.e., `wrapper instanceof Object` in the caller\n  // realm is false, matching Node).\n  const wrapper = vm.runInContext(\"({})\", context);\n  wrapper.onmessage = null;\n  wrapper.onmessageerror = null;\n\n  let enabled = false;\n  let closed = false;","sourceCodeStart":1735,"sourceCodeEnd":1771,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/node/polyfills/worker_threads.ts#L1735-L1771","documentation":"moveMessagePortToContext looks up the port's internal id; a null id means the port is detached — already close()d, or already moved (moving clears the id so the original becomes unusable). The check intentionally runs before the vm.Context check to give the clearer ERR_CLOSED_MESSAGE_PORT ('Cannot send data on closed MessagePort') error, mirroring Node's ordering.","triggerScenarios":"port.close() followed by moveMessagePortToContext(port, ctx); or calling moveMessagePortToContext twice with the same port — the second call sees the id cleared by the first.","commonSituations":"Retry logic reusing a port after a failed setup whose cleanup closed it; double registration during init; error handlers closing ports that a pending move still needs.","solutions":["Use a fresh port from a new MessageChannel() for each move; ports are single-use for this API.","Order operations so close() only runs after the move completes; guard with a moved/closed flag.","Catch ERR_CLOSED_MESSAGE_PORT and rebuild the channel rather than retrying the same port."],"exampleFix":"// before\nport.close();\nmoveMessagePortToContext(port, ctx); // ERR_CLOSED_MESSAGE_PORT\n\n// after\nimport { MessageChannel, moveMessagePortToContext } from 'node:worker_threads';\nconst { port1 } = new MessageChannel();\nmoveMessagePortToContext(port1, ctx); // move a live port exactly once","handlingStrategy":"try-catch","validationCode":"const movedOrClosed = new WeakSet<object>();\nfunction closePort(p: MessagePort) { p.close(); movedOrClosed.add(p); }\nfunction isUsable(p: MessagePort) { return !movedOrClosed.has(p); }\n\nif (isUsable(port)) moveMessagePortToContext(port, ctx);","typeGuard":null,"tryCatchPattern":"try {\n  moveMessagePortToContext(port, ctx);\n} catch (e) {\n  if (e?.code === 'ERR_CLOSED_MESSAGE_PORT') {\n    // rebuild the channel and retry with a fresh port\n    const { port1 } = new MessageChannel();\n    moveMessagePortToContext(port1, ctx);\n  } else throw e;\n}","preventionTips":["Treat ports as consumed after move/close; never reuse them.","Centralize close() in one cleanup path.","Track port lifecycle state explicitly in the owning module."],"tags":["worker-threads","messageport","lifecycle","closed-port"],"backgroundTag":"closed-message-port","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}