unoplatform/uno · error · Exception
ClientWaitSync failed (status=0x{syncStatus:x})
Error message
ClientWaitSync failed (status=0x{syncStatus:x}) What it means
In RenderOverride, QuerySyncGlCanvasElement checks the previous frame's fence sync via gl.ClientWaitSync with a 0 timeout (required by WebGL2). If the return is GL_WAIT_FAILED the sync is broken — the GPU could not signal completion, typically due to context loss, an invalid sync object, or a GPU reset. The sync status from GetSync is included in the message.
Source
Thrown at src/SamplesApp/SamplesApp.Samples/Windows_UI_Composition/QuerySyncGlCanvasElement.cs:197
{
gl.DeleteSync(_frameSync);
_frameSync = 0;
}
}
protected override unsafe void RenderOverride(GL gl)
{
var t = (float)(DateTime.UtcNow - _startTime).TotalSeconds;
// --- Check last frame's fence; the GPU should long be done by the next paint ---
if (_frameSync != 0)
{
// WebGL2 requires a 0 timeout for ClientWaitSync.
var waitResult = (GLEnum)gl.ClientWaitSync(_frameSync, (SyncObjectMask)0, 0);
gl.GetSync(_frameSync, SyncParameterName.SyncStatus, 1, out _, out int syncStatus);
if (waitResult == GLEnum.WaitFailed)
{
throw new Exception($"ClientWaitSync failed (status=0x{syncStatus:x})");
}
gl.DeleteSync(_frameSync);
_frameSync = 0;
}
// --- Poll the occlusion query from a previous frame without stalling ---
if (_queryInFlight)
{
gl.GetQueryObject(_query, QueryObjectParameterName.ResultAvailable, out uint available);
if (available != 0)
{
gl.GetQueryObject(_query, QueryObjectParameterName.Result, out uint anySamplesPassed);
_lastProbeVisible = anySamplesPassed != 0;
_queryInFlight = false;
}
}
gl.ClearColor(0.06f, 0.06f, 0.08f, 1f);View on GitHub (pinned to 0418340488)
Solutions
- Check for context loss: on WebGL listen for the webglcontextlost event; on native query gl.GetGraphicsResetStatus() (or the GLES counterpart) to detect a GPU reset.
- Verify _frameSync is non-zero and was returned by a recent FenceSync call before ClientWaitSync, and clear it to 0 immediately after DeleteSync so it is not reused.
- Treat WAIT_FAILED as recoverable: delete the failed sync, reset _frameSync = 0, skip the frame, and re-fence on the next render rather than throwing.
- If the GetSync status is SIGNALED but ClientWaitSync still returned WAIT_FAILED, suspect a driver bug — file against the vendor and downgrade the throw to a warning.
- On WebGL2, ensure the sync is created on the same context that waits on it (cross-context syncs are invalid).
Example fix
// before
var waitResult = (GLEnum)gl.ClientWaitSync(_frameSync, (SyncObjectMask)0, 0);
gl.GetSync(_frameSync, SyncParameterName.SyncStatus, 1, out _, out int syncStatus);
if (waitResult == GLEnum.WaitFailed)
throw new Exception($"ClientWaitSync failed (status=0x{syncStatus:x})");
gl.DeleteSync(_frameSync); _frameSync = 0;
// after — recover instead of throwing on transient failure
var waitResult = (GLEnum)gl.ClientWaitSync(_frameSync, (SyncObjectMask)0, 0);
if (waitResult == GLEnum.WaitFailed)
{
gl.GetSync(_frameSync, SyncParameterName.SyncStatus, 1, out _, out int syncStatus);
System.Diagnostics.Debug.WriteLine($"ClientWaitSync failed (status=0x{syncStatus:x}); resetting fence");
gl.DeleteSync(_frameSync); _frameSync = 0; // recover, skip gating this frame
} Defensive patterns
Strategy: validation
Validate before calling
// Verify the sync object is valid and check for context loss before waiting
if (_frameSync == 0) return;
// On WebGL, listen for webglcontextlost; on native, query graphics reset status
gl.GetSync(_frameSync, SyncParameterName.ObjectType, 1, out _, out int objType);
if (objType != (int)GLEnum.SyncFence) { _frameSync = 0; return; } // stale/invalid sync Try / catch
try
{
var waitResult = (GLEnum)gl.ClientWaitSync(_frameSync, (SyncObjectMask)0, 0);
if (waitResult == GLEnum.WaitFailed) throw new Exception($"status=0x{syncStatus:x}");
}
catch (Exception ex) when (ex.Message.Contains("ClientWaitSync failed"))
{
App.MainWindow?.LogError($"ClientWaitSync failed: {ex.Message}");
gl.DeleteSync(_frameSync); _frameSync = 0; // recover, skip gating this frame
} Prevention
- Detect context loss (WebGL event / GetGraphicsResetStatus) and reset sync state.
- Clear _frameSync to 0 immediately after DeleteSync so it is never reused stale.
- Treat WAIT_FAILED as recoverable: reset and continue rather than throwing.
- On WebGL2, create and wait on syncs within the same context.
When it happens
Trigger: When _frameSync != 0 (a fence was inserted last frame), gl.ClientWaitSync(_frameSync, 0, 0) returns GLEnum.WaitFailed (lines 193-197). The accompanying GetSync(SyncStatus) value is appended.
Common situations: The GL context was lost (WebGL context-loss event) and the sync object is now invalid; a GPU reset (TDR on Windows, watchdog on mobile) invalidated outstanding syncs; the sync object was already deleted and _frameSync was not cleared; on some drivers ClientWaitSync with timeout 0 returns WAIT_FAILED if the GPU has not started the commands yet rather than TIMEOUT_EXPIRED (driver bug); WebGL2 where fence syncs have stricter semantics.
Related errors
- Readback FBO is not complete
- MapBufferRange(PixelPackBuffer) returned null
- PBO round-trip mismatch at byte {i}: got {readBack[i]}, expe
- Program link failed: {infoLog}
- {type} compile failed: {infoLog}
AI-assisted analysis of unoplatform/uno@0418340488 (2026-08-13).
Data as JSON: /api/errors/67fde198777053bb.
Report an issue: GitHub.