{"record":{"id":"03aa804dd0b9bcbc","repo":"AvaloniaUI/Avalonia","slug":"compositionimportedgpusemaphore","errorCode":null,"errorMessage":"CompositionImportedGpuSemaphore","messagePattern":"CompositionImportedGpuSemaphore","errorType":"exception","errorClass":"ObjectDisposedException","httpStatus":null,"severity":"error","filePath":"src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs","lineNumber":148,"sourceCode":"        _image?.Dispose();\n        _image = null!;\n    }\n}\n\nclass CompositionImportedGpuSemaphore : CompositionGpuImportedObjectBase, ICompositionImportedGpuSemaphore\n{\n    private readonly IPlatformHandle _handle;\n    private IPlatformRenderInterfaceImportedSemaphore? _semaphore;\n\n    public CompositionImportedGpuSemaphore(IPlatformHandle handle,\n        Compositor compositor, IPlatformRenderInterfaceContext context,\n        IExternalObjectsRenderInterfaceContextFeature feature) : base(compositor, context, feature, handle)\n    {\n        _handle = handle;\n    }\n\n    public IPlatformRenderInterfaceImportedSemaphore Semaphore =>\n        _semaphore ?? throw new ObjectDisposedException(nameof(CompositionImportedGpuSemaphore));\n\n\n    public bool IsUsable => _semaphore != null && Compositor.Server.RenderInterface.Value == Context;\n\n    protected override void Import()\n    {\n        _semaphore = Feature.ImportSemaphore(_handle);\n    }\n\n    public override void Dispose()\n    {\n        _semaphore?.Dispose();\n        _semaphore = null;\n    }\n}\n","sourceCodeStart":130,"sourceCodeEnd":164,"githubUrl":"https://github.com/AvaloniaUI/Avalonia/blob/11c542726898ae954a1ef668c65ec79ec92ab17d/src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs#L130-L164","documentation":"Thrown as ObjectDisposedException (nameof CompositionImportedGpuSemaphore) by the Semaphore property when the imported GPU semaphore is null — after Dispose() nulls it or if Import() failed. As with the GPU image, the IsUsable flag is the intended pre-check (semaphore non-null and matching render context).","triggerScenarios":"Accessing importedGpuSemaphore.Semaphore after Dispose(), or after a failed ImportSemaphore. Reached when a cross-process/cross-device sync primitive has been torn down but still read.","commonSituations":"Device loss invalidating imported semaphores; using a semaphore after the composition object that owns it was disposed; a render loop reading a semaphore that a parallel dispose nulled.","solutions":["Guard with IsUsable before reading Semaphore; re-import when false.","Drop references after Dispose() and stop submitting the semaphore.","Recreate the imported semaphore when the render interface context changes (catch context-lost during import)."],"exampleFix":"// before\nvar sem = importedGpuSemaphore.Semaphore; // throws post-dispose\n// after\nif (!importedGpuSemaphore.IsUsable) importedGpuSemaphore = ReimportSemaphore(compositor, handle);\nvar sem = importedGpuSemaphore.Semaphore;","handlingStrategy":"validation","validationCode":"if (!importedGpuSemaphore.IsUsable) importedGpuSemaphore = ReimportSemaphore(compositor, handle);\nvar sem = importedGpuSemaphore.Semaphore;","typeGuard":"static bool IsUsable(CompositionImportedGpuSemaphore s) => s.IsUsable;","tryCatchPattern":"try { use(importedGpuSemaphore.Semaphore); }\ncatch (ObjectDisposedException) { importedGpuSemaphore = ReimportSemaphore(compositor, handle); use(importedGpuSemaphore.Semaphore); }","preventionTips":["Check IsUsable before Semaphore and re-import when false.","Recreate imported semaphores on render-context change.","Do not submit a semaphore after its owning object is disposed."],"tags":["rendering","composition","gpu","object-disposed","interop","semaphore"],"backgroundTag":null,"analyzedSha":"11c542726898ae954a1ef668c65ec79ec92ab17d","analyzedAt":"2026-08-13T11:57:40.261Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}