linera-io/linera-protocol · error · JsError

Client disposed while being referenced elsewhere

Error message

Client disposed while being referenced elsewhere

What it means

`Client.asyncDispose()` (exposed to JS as `asyncDispose`) shuts down the web client: it stops the chain listener, then consumes the internal `Arc<ClientContext>` with `Arc::into_inner`. That only succeeds when this instance holds the last reference; every `Chain` object created via `client.chain(chainId)` clones the context, so any live `Chain` (or Client clone) keeps a reference alive and `Arc::into_inner` returns `None`, producing this error.

Source

Thrown at web/@linera/client/src/client.rs:161

        })
    }

    /// Cleanly shut down the client, completing when it is destroyed and all
    /// resources it owns are released.
    ///
    /// # Errors
    ///
    /// If the context is being referenced by any other objects (chains,
    /// applications…). Free these with `.free()` before disposing of this
    /// object.
    ///
    /// Propagates any errors that occurred during background execution of the
    /// client.
    #[wasm_bindgen(js_name = asyncDispose)]
    pub async fn async_dispose(self) -> Result<()> {
        self.stop().await?;

        let context = Arc::into_inner(self.context).ok_or(Error::new(
            "Client disposed while being referenced elsewhere",
        ))?;

        drop(context);

        Ok(())
    }
}

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Call `.free()` on every Chain (and any other handle) obtained from the client before calling `asyncDispose()`.
  2. Centralize teardown: free all tracked chains in one place, then dispose the client.
  3. Keep an array of created chains and free them in a loop right before disposal.
  4. If the error persists, hunt for forgotten Client clones or unresolved promises holding a Chain.

Example fix

// before
const chain = client.chain(chainId);
await client.asyncDispose(); // 'Client disposed while being referenced elsewhere'

// after
const chains = [client.chain(chainId)];
// ... use chains ...
for (const c of chains) c.free();
await client.asyncDispose();
Defensive patterns

Strategy: validation

Validate before calling

const openChains = new Set();
function acquireChain(id) { const c = client.chain(id); openChains.add(c); return c; }
async function disposeClient() { for (const c of openChains) c.free(); openChains.clear(); await client.asyncDispose(); }

Type guard

function allChainsFreed(tracked: Set<Chain>): boolean { return tracked.size === 0; }

Try / catch

try { await client.asyncDispose(); } catch (e) { if (/disposed while being referenced/i.test(e.message)) { freeAllChains(); await client.asyncDispose(); } else throw e; }

Prevention

When it happens

Trigger: Calling `await client.asyncDispose()` while one or more `Chain` handles from `client.chain(...)` are still alive; keeping another clone of the Client somewhere; a long-lived promise still holding a Chain during teardown.

Common situations: Web dApp teardown paths disposing the client before per-chain handles; UI components unmounting in the wrong order; error paths that skip cleanup of chain handles.

Related errors


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/f330f11f21dbaba3. Report an issue: GitHub.