BabylonJS/Babylon.js · error · Error

Disconnect failed

Error message

Disconnect failed

What it means

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.

Source

Thrown at packages/dev/core/src/AudioV2/abstractAudio/abstractAudioNode.ts:67

        if (nodeType & AudioNodeType.HAS_INPUTS) {
            this._upstreamNodes = new Set<AbstractAudioNode>();
        }

        if (nodeType & AudioNodeType.HAS_OUTPUTS) {
            this._downstreamNodes = new Set<AbstractAudioNode>();
        }
    }

    /**
     * Releases associated resources.
     * - Triggers `onDisposeObservable`.
     * @see {@link onDisposeObservable}
     */
    public dispose(): void {
        if (this._downstreamNodes) {
            for (const node of Array.from(this._downstreamNodes)) {
                if (!this._disconnect(node)) {
                    throw new Error("Disconnect failed");
                }
            }
            this._downstreamNodes.clear();
        }

        if (this._upstreamNodes) {
            for (const node of Array.from(this._upstreamNodes)) {
                if (!node._disconnect(this)) {
                    throw new Error("Disconnect failed");
                }
            }
            this._upstreamNodes.clear();
        }

        this.onDisposeObservable.notifyObservers(this);
        this.onDisposeObservable.clear();
    }

View on GitHub (pinned to 0592b347b8)

Solutions

  1. Dispose nodes in dependency order — disconnect children/downstream nodes first (or ensure the downstream node is not already disposed) before calling dispose() on parents.
  2. Check whether the downstream node was already disposed and remove it from tracking yourself instead of relying on dispose() to unlink it.
  3. 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.
  4. 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.

Example fix

// before
sound.dispose();
bus.dispose(); // bus still connected downstream of sound can make sound's dispose throw earlier

// after
bus.disconnect(sound); // or dispose bus first if it owns the link
bus.dispose();
sound.dispose();
Defensive patterns

Strategy: try-catch

Validate before calling

// before disposing, verify the node owns live downstream links
function canDisposeSafely(node) {
    return Array.from(node._downstreamNodes ?? []).every(
        (peer) => !peer.isDisposed && (peer._upstreamNodes?.has(node) ?? false)
    );
}

Type guard

function isConnectedDownstream(node, peer) {
    return node instanceof AbstractAudioNode &&
        Array.from(node._downstreamNodes ?? []).includes(peer);
}

Try / catch

try {
    node.dispose();
} catch (e) {
    if (e.message === "Disconnect failed") {
        // graph already inconsistent; drop references and continue teardown
        node.onDisposeObservable.clear();
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30). Data as JSON: /api/errors/ed724b758a124a52. Report an issue: GitHub.