BabylonJS/Babylon.js · error

clientWaitSync failed

Error message

clientWaitSync failed

What it means

This comes from the engine's async readback path (based on gl.clientWaitSync / fence sync). If the sync object wait returns WAIT_FAILED — the sync object is invalid, the context is lost, or the fence was never signaled properly — the retry loop throws "clientWaitSync failed".

Source

Thrown at packages/dev/core/src/Engines/engine.pure.ts:1006

        return result;
    }

    /**
     * Delete a webGL buffer used with instantiation
     * @param buffer defines the webGL buffer to delete
     */
    public deleteInstancesBuffer(buffer: WebGLBuffer): void {
        this._gl.deleteBuffer(buffer);
    }

    private async _clientWaitAsync(sync: WebGLSync, flags = 0, intervalms = 10): Promise<void> {
        const gl = <WebGL2RenderingContext>(this._gl as any);
        return await new Promise((resolve, reject) => {
            _RetryWithInterval(
                () => {
                    const res = gl.clientWaitSync(sync, flags, 0);
                    if (res == gl.WAIT_FAILED) {
                        throw new Error("clientWaitSync failed");
                    }
                    if (res == gl.TIMEOUT_EXPIRED) {
                        return false;
                    }
                    return true;
                },
                resolve,
                reject,
                intervalms
            );
        });
    }

    /**
     * This function might return null synchronously, so it is technically not async.
     * @internal
     */
    // eslint-disable-next-line no-restricted-syntax

View on GitHub (pinned to 0592b347b8)

Solutions

  1. Check gl.isContextLost() in the catch handler; if lost, rebuild the engine and retry the readback after restore.
  2. Ensure the texture/framebuffer still exists and wasn't disposed between issuing the readback and awaiting the promise.
  3. Retry the readback operation once after a frame; transient WAIT_FAILED can occur on some drivers.
  4. Fall back to synchronous readPixels if async readback repeatedly fails.

Example fix

// before
const data = await engine._readPixelsAsync(x, y, w, h, fmt, type, buffer);
// after
try {
  const data = await engine._readPixelsAsync(x, y, w, h, fmt, type, buffer);
} catch (e) {
  if (String(e).includes("clientWaitSync")) {
    engine._gl.isContextLost()
      ? rebuildEngineAndRedoReadback()
      : readPixelsFallback();
  }
}
Defensive patterns

Strategy: retry

Validate before calling

function canAwaitReadback(engine) {
  return engine.webGLVersion >= 2 && !engine._gl.isContextLost();
}

Type guard

function isReadbackSafe(engine, sync) {
  return engine.webGLVersion >= 2 && !engine._gl.isContextLost() && !!sync;
}

Try / catch

try {
  await readbackPromise;
} catch (e) {
  if (/clientWaitSync failed/.test(e.message)) {
    if (engine._gl.isContextLost()) scheduleEngineRebuild();
    else retryReadbackOnce();
  }
}

Prevention

When it happens

Trigger: Awaiting _readPixelsAsync (or similar fence-based GPU->CPU readback) when the WebGL2 context is lost, the sync object was created on a different context, or a driver-level fence error occurs.

Common situations: Background tab / GPU process crash mid-readback, mobile browser context loss during long-running apps, calling readback after the engine or texture was disposed.

Related errors


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