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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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

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


AI-assisted analysis of unoplatform/uno@0418340488 (2026-08-13). Data as JSON: /api/errors/67fde198777053bb. Report an issue: GitHub.