{"record":{"id":"ed724b758a124a52","repo":"BabylonJS/Babylon.js","slug":"disconnect-failed","errorCode":null,"errorMessage":"Disconnect failed","messagePattern":"Disconnect failed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/dev/core/src/AudioV2/abstractAudio/abstractAudioNode.ts","lineNumber":67,"sourceCode":"        if (nodeType & AudioNodeType.HAS_INPUTS) {\n            this._upstreamNodes = new Set<AbstractAudioNode>();\n        }\n\n        if (nodeType & AudioNodeType.HAS_OUTPUTS) {\n            this._downstreamNodes = new Set<AbstractAudioNode>();\n        }\n    }\n\n    /**\n     * Releases associated resources.\n     * - Triggers `onDisposeObservable`.\n     * @see {@link onDisposeObservable}\n     */\n    public dispose(): void {\n        if (this._downstreamNodes) {\n            for (const node of Array.from(this._downstreamNodes)) {\n                if (!this._disconnect(node)) {\n                    throw new Error(\"Disconnect failed\");\n                }\n            }\n            this._downstreamNodes.clear();\n        }\n\n        if (this._upstreamNodes) {\n            for (const node of Array.from(this._upstreamNodes)) {\n                if (!node._disconnect(this)) {\n                    throw new Error(\"Disconnect failed\");\n                }\n            }\n            this._upstreamNodes.clear();\n        }\n\n        this.onDisposeObservable.notifyObservers(this);\n        this.onDisposeObservable.clear();\n    }\n","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/BabylonJS/Babylon.js/blob/0592b347b8a4ee0236089ea86a749cacfdb266d8/packages/dev/core/src/AudioV2/abstractAudio/abstractAudioNode.ts#L49-L85","documentation":"AbstractAudioNode.dispose() tears down the audio graph by disconnecting every downstream node before clearing its bookkeeping. It calls the internal _disconnect(node) for each downstream node, and if any of those returns false (the node was not registered as downstream, this node has no downstream set, or the other node's _onDisconnect rejected), dispose() throws \"Disconnect failed\" instead of silently leaking the connection. This indicates the audio graph bookkeeping is out of sync with what dispose() expects.","triggerScenarios":"Calling dispose() on an audio node whose _downstreamNodes set contains a node for which this._disconnect(node) returns false — i.e. the downstream node has no _upstreamNodes set (wrong AudioNodeType), or its _onDisconnect callback refuses the removal (e.g. the peer node was already disposed or mutated externally). Also thrown when a subclass overrides _disconnect/_onDisconnect and returns false.","commonSituations":"Disposing nodes in an order or from multiple code paths that double-dispose; manually manipulating connections via internals instead of connect()/disconnect(); custom node subclasses constructed with a nodeType that doesn't match how they're connected (e.g. an input-only node connected as downstream); engine teardown while another callback already removed the link.","solutions":["Dispose nodes in dependency order — disconnect children/downstream nodes first (or ensure the downstream node is not already disposed) before calling dispose() on parents.","Check whether the downstream node was already disposed and remove it from tracking yourself instead of relying on dispose() to unlink it.","If you wrote a custom subclass, verify it passes the correct AudioNodeType so _downstreamNodes/_upstreamNodes sets exist, and that its _onDisconnect override returns true for valid removals.","As a last resort, wrap dispose() in try/catch since the throw happens after partial teardown; the node's onDisposeObservable may not have fired, so clean up listeners manually."],"exampleFix":"// before\nsound.dispose();\nbus.dispose(); // bus still connected downstream of sound can make sound's dispose throw earlier\n\n// after\nbus.disconnect(sound); // or dispose bus first if it owns the link\nbus.dispose();\nsound.dispose();","handlingStrategy":"try-catch","validationCode":"// before disposing, verify the node owns live downstream links\nfunction canDisposeSafely(node) {\n    return Array.from(node._downstreamNodes ?? []).every(\n        (peer) => !peer.isDisposed && (peer._upstreamNodes?.has(node) ?? false)\n    );\n}","typeGuard":"function isConnectedDownstream(node, peer) {\n    return node instanceof AbstractAudioNode &&\n        Array.from(node._downstreamNodes ?? []).includes(peer);\n}","tryCatchPattern":"try {\n    node.dispose();\n} catch (e) {\n    if (e.message === \"Disconnect failed\") {\n        // graph already inconsistent; drop references and continue teardown\n        node.onDisposeObservable.clear();\n    } else {\n        throw e;\n    }\n}","preventionTips":["Dispose the audio graph top-down (outputs before sources) and never dispose a node twice.","Use only the public connect()/disconnect() APIs; avoid touching _downstreamNodes or internals.","Remove your onDisposeObservable callbacks so disposed peers don't leave stale links.","In custom node subclasses, pass the AudioNodeType that matches how the node is actually wired."],"tags":["audio","webaudio","dispose","graph-state"],"backgroundTag":"audio-node-disconnect-failed","analyzedSha":"0592b347b8a4ee0236089ea86a749cacfdb266d8","analyzedAt":"2026-08-30T15:11:20.442Z","schemaVersion":2},"datasetVersion":"2026-08-30T18:17:15.746Z"}