Skip to content

fix: unified resource lifecycle — Release() via ResourceRef.Drop() (#287) - #288

Merged
kolkov merged 1 commit into
mainfrom
fix/adr-056-unified-resource-lifecycle
Jul 30, 2026
Merged

fix: unified resource lifecycle — Release() via ResourceRef.Drop() (#287)#288
kolkov merged 1 commit into
mainfrom
fix/adr-056-unified-resource-lifecycle

Conversation

@kolkov

@kolkov kolkov commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

  • BindGroup.Release() bypasses Phase 2 ResourceRef — use-after-free on shared encoder path #287: BindGroup/RenderPipeline/ComputePipeline Release() bypassed Phase 2 ResourceRef ref-counting, causing use-after-free on shared encoder path
  • Fix: Release()ref.Drop() with onZero callback for deferred HAL destruction (Rust wgpu Arc<T> pattern, ADR-056)
  • DestroyQueue deadlock fix: Triage()/FlushAll() execute callbacks outside mutex to prevent onZeroDefer() re-entry deadlock
  • 6 lifecycle tests covering all fixed resource types

Details

Deep research of Dawn (C++) and Rust wgpu resource lifecycle confirmed: single ref-counted lifecycle (Rust model) is simpler and safer than Dawn's two-level split (which caused CVE-2026-6304).

Buffer already used this pattern since v0.28.9. This PR extends it to BindGroup, RenderPipeline, ComputePipeline. Audit confirmed Texture, Sampler, ShaderModule, BindGroupLayout don't have ResourceRef — no Phase 1/Phase 2 disconnect.

Test plan

  • TestBindGroup_ReleaseUsesRefDrop — refcount: 1→Clone→2→Release→1→Drop→0→onZero
  • TestBindGroup_ReleaseWithoutClone_DestroysImmediately — no Clone: 1→Release→0→onZero
  • TestRenderPipeline_ReleaseUsesRefDrop — same pattern for pipeline
  • TestComputePipeline_ReleaseUsesRefDrop — same pattern for compute
  • TestBindGroup_WithBuffer_ReleaseUsesRefDrop — full BindGroup.Release() bypasses Phase 2 ResourceRef — use-after-free on shared encoder path #287 scenario with buffer binding
  • TestMixedResourceLifecycle_TrackedSubmission — TrackSubmission + Triage + onZero chain (deadlock regression test)
  • Build: native + WASM + cross-platform
  • Lint: 0 issues
  • All existing tests pass

Fixes #287.

…DR-056, #287)

BindGroup, RenderPipeline, ComputePipeline Release() now uses ref.Drop()
instead of direct dq.Defer(lastSubmissionIndex). HAL resource stays alive
until ALL refs (user + GPU tracked) are dropped. Matches Rust wgpu Arc<T>
pattern (binding_model.rs:1192, life.rs:298).

DestroyQueue: Triage/FlushAll execute callbacks outside mutex to prevent
deadlock when onZero → Defer re-enters the lock.

6 lifecycle tests: refcount transitions, Release-before-Submit,
Release-without-Clone, mixed-resource TrackedSubmission.

Fixes #287.
@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.83051% with 6 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
device_native.go 81.81% 6 Missing ⚠️

📢 Thoughts on this report? Let us know!

@kolkov
kolkov merged commit 146bc28 into main Jul 30, 2026
15 checks passed
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.

BindGroup.Release() bypasses Phase 2 ResourceRef — use-after-free on shared encoder path

1 participant