Skip to content

vulkan: Fix crash during async pipeline compilation and handle destruction (#3245) - #40

Open
olehkuznetsov wants to merge 1 commit into
android-graphics:devfrom
olehkuznetsov:cherry-pick-pr-3245
Open

vulkan: Fix crash during async pipeline compilation and handle destruction (#3245)#40
olehkuznetsov wants to merge 1 commit into
android-graphics:devfrom
olehkuznetsov:cherry-pick-pr-3245

Conversation

@olehkuznetsov

@olehkuznetsov olehkuznetsov commented Sep 10, 2026

Copy link
Copy Markdown

Cherry-pick of upstream commit LunarG/gfxreconstruct@9888829 (PR #3245).

Upstream Description

When replaying traces with parallel pipeline creation enabled (--pipeline-creation-jobs / --pcj), background worker threads compile pipelines asynchronously. Several edge cases in async handle tracking, handle destruction, and pipeline cache lifecycle caused driver crashes and race conditions.

Key issues and fixes:

  1. Reference-counted handle tracking across concurrent tasks:
    Multiple async pipeline creation tasks frequently share common dependencies (VkPipelineLayout, VkRenderPass, VkShaderModule). Previously, dependencies were tracked globally without task reference counting. When the first task finished, it cleared the handle, allowing premature destruction and use-after-free in subsequent tasks still compiling on background threads (e.g. SIGSEGV in SPIR_Parser::parse).

    • Fix: Track dependencies by async task ID sets and deferred callbacks in struct AsyncTrackedHandle (async_tracked_handles_) and per-task sync callbacks in async_task_sync_fns_. A handle is only cleared when all referencing tasks have finished. In DestroyAsyncHandle, all referencing tasks are synchronized before destroying the handle.
  2. Handle destruction lifecycle and consolidation:
    Consolidate handle destruction across VkPipeline, VkPipelineLayout, VkRenderPass, and VkShaderModule via DestroyTrackedHandle. Defer destruction when handles are tracked by active async tasks via DestroyAsyncHandle.

  3. Intercept VkPipelineLayout destruction:
    During async pipeline compilation, the main thread previously destroyed VkPipelineLayout handles immediately via vkDestroyPipelineLayout because GFXReconstruct lacked an override, crashing background compilation threads accessing the layout.

    • Fix: Add OverrideDestroyPipelineLayout to replay_overrides.json and defer layout destruction via DestroyTrackedHandle until referencing async tasks complete.
  4. Shared pipeline cache lifecycle and dispatch ordering:
    In batched creations or shared caches, multiple pipelines reference the same pipeline cache ID. OverrideDestroyPipeline previously destroyed the cache on the first pipeline destruction, leading to assertion failures (tracked_pipeline_caches_) or missed cache saves. Additionally, capturing out_pipelines.data() by raw pointer into the MainThreadQueue completion callback caused dangling pointers because the result vector in the future's shared state is freed once all handles in the batch have synced their futures (object->future = {}).

    • Fix: Extract DestroyAssociatedPipelineCache and track remaining correspondences (sameIdFound) so vkDestroyPipelineCache and SavePipelineCache execute only after the last pipeline is destroyed. Capture pipeline_handles by value into completion closures. In DestroyAsyncHandle, drain main_thread_queue_.poll() after worker synchronization so cache registrations dispatch before destruction, and assert all referencing tasks have erased their IDs.
  5. Exception handling and future synchronization invariants:
    In sync_handle, unhandled worker thread exceptions could escape during handle synchronization.

    • Fix: Enforce the object->future_handle_index < async_handles.size() invariant via GFXRECON_ASSERT, catch const std::exception& with GFXRECON_LOG_FATAL to fail fast with diagnostic information, and clear object->future = {} upon resolution to release the future's shared state and prevent redundant synchronization.

Porting Notes for android-graphics:dev:

  • Used GetDeviceTable in DestroyAssociatedPipelineCache as android-graphics:dev has not migrated to injected device calls (#3172/#3191).
  • Used standard decoder parameter signature in Process_vkDestroyPipelineLayout as android-graphics:dev has not migrated to decoder args (4e528616).

…ction (LunarG#3245)

- Track handles used by asynchronous compilation tasks using reference-counted
  task IDs, synchronizing all referencing tasks in DestroyAsyncHandle and
  only releasing shared handles when all tasks finish to prevent use-after-free.
- Consolidate handle destruction across VkPipeline, VkPipelineLayout,
  VkRenderPass, and VkShaderModule using DestroyTrackedHandle to defer
  destruction until referencing async tasks complete.
- Safely track and destroy associated VkPipelineCache instances across
  batched and shared pipeline creations, avoiding premature destruction.
- Enforce bounds assertions in sync_handle and log fatal on worker
  exceptions to prevent silent invalid handle propagation.
- Capture pipeline handles by value in completion callbacks to avoid
  dangling pointer references when futures are cleared.

(cherry picked from commit 9888829)

Change-Id: Id54edd158b3d665fd74544fe7734e8236a6a6964
@olehkuznetsov

Copy link
Copy Markdown
Author

Tested #36+#40 with ~40 local gfrx games - all work fine

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant