{"record":{"id":"09e6bab78c241499","repo":"denoland/deno","slug":"invalidstateerror","errorCode":"InvalidStateError","errorMessage":"Already closed","messagePattern":"Already closed","errorType":"exception","errorClass":"DOMException","httpStatus":null,"severity":"error","filePath":"ext/web/01_broadcast_channel.js","lineNumber":144,"sourceCode":"    ArrayPrototypePush(channels, this);\n    refedBroadcastChannelsCount++;\n\n    if (rid === null) {\n      // Create the rid immediately, otherwise there is a time window (and a\n      // race condition) where messages can get lost, because recv() is async.\n      rid = op_broadcast_subscribe();\n      recv();\n    }\n  }\n\n  postMessage(message) {\n    webidl.assertBranded(this, BroadcastChannelPrototype);\n\n    const prefix = \"Failed to execute 'postMessage' on 'BroadcastChannel'\";\n    webidl.requiredArguments(arguments.length, 1, prefix);\n\n    if (this[_closed]) {\n      throw new DOMException(\"Already closed\", \"InvalidStateError\");\n    }\n\n    if (typeof message === \"function\" || typeof message === \"symbol\") {\n      throw new DOMException(\"Uncloneable value\", \"DataCloneError\");\n    }\n\n    // Serialize the message, carrying any SharedArrayBuffer backing stores\n    // out-of-band (referenced by `sabId`) so the message can be deserialized by\n    // an arbitrary number of receivers. `sabId` is 0 when there are none.\n    const { 0: data, 1: sabId } = op_broadcast_serialize(message, null);\n\n    // Send to other listeners in this VM.\n    dispatch(this, this[_name], data, sabId);\n\n    // Send to listeners in other VMs. This must happen before returning from\n    // postMessage(), otherwise close() immediately after postMessage() can\n    // cancel the deferred send before other workers observe the message.\n    op_broadcast_send(rid, this[_name], data, sabId);","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/web/01_broadcast_channel.js#L126-L162","documentation":"BroadcastChannel.postMessage (ext/web/01_broadcast_channel.js:144) throws DOMException 'Already closed' with name InvalidStateError when the channel's closed flag is set. The flag is set by close() (and by the channel being removed), after which every postMessage is rejected before serialization. Once closed, the channel cannot be reopened.","triggerScenarios":"bc.postMessage(msg) after bc.close(); a finally block that closes the channel before an async send completes; two modules sharing a channel name where one closes its instance during shutdown while the other keeps sending.","commonSituations":"Shutdown/teardown ordering bugs in servers and workers; event handlers racing a close triggered by 'unload' or error paths; wrapper classes that auto-close on first error and then attempt to flush queued messages through the same channel.","solutions":["Check bc.closed before posting and skip or rethrow deliberately","Centralize teardown: call close() only after all sends have finished (single shutdown function, await pending sends first)","If a channel may have been closed, create a fresh BroadcastChannel(name) instead of reusing the instance"],"exampleFix":"// before\nfunction shutdown() {\n  bc.close();\n}\nsetInterval(() => bc.postMessage(tick()), 1000); // fires after shutdown -> throws\n\n// after\nlet running = true;\nfunction shutdown() {\n  running = false;\n  bc.close();\n}\nsetInterval(() => {\n  if (!running || bc.closed) return;\n  bc.postMessage(tick());\n}, 1000);","handlingStrategy":"validation","validationCode":"if (!bc || bc.closed) {\n  return; // channel already torn down\n}\nbc.postMessage(message);","typeGuard":"function isOpen(bc: BroadcastChannel | null): bc is BroadcastChannel {\n  return bc !== null && !bc.closed;\n}","tryCatchPattern":"try {\n  bc.postMessage(message);\n} catch (e) {\n  if (e instanceof DOMException && e.name === \"InvalidStateError\") {\n    // channel closed during shutdown; drop or re-create the channel\n  } else {\n    throw e;\n  }\n}","preventionTips":["Check the closed property before every postMessage","Own the lifecycle: one teardown path that closes the channel after pending sends drain","Null out channel references after close() so use-after-close fails loudly"],"tags":["broadcastchannel","messaging","dom-exception","lifecycle","validation"],"backgroundTag":"resource-used-after-close","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}