Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.30.29] - 2026-07-30

### Fixed

- **Queue.LastSubmissionIndex deadlock** — `onZero` callbacks from `Triage()`
called `lastSubmissionIndex()` which locked `Queue.mu`, already held by
`Submit()`. Changed `lastSubmissionIndex` from `mutex`-protected `uint64` to
`atomic.Uint64` — single writer (Submit), lock-free readers (onZero callbacks).
ADR-056 deadlock chain: Submit→Triage→onZero→lastSubmissionIndex→mu.

## [0.30.28] - 2026-07-30

### Fixed
Expand Down
19 changes: 10 additions & 9 deletions queue_native.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package wgpu
import (
"fmt"
"sync"
"sync/atomic"

"github.com/gogpu/wgpu/core"
"github.com/gogpu/wgpu/hal"
Expand All @@ -27,8 +28,10 @@ type Queue struct {
// lastSubmissionIndex is the most recent submission index returned by
// hal.Queue.Submit(). Used by DestroyQueue to conservatively defer
// resource destruction until after the latest known submission completes.
// Protected by mu.
lastSubmissionIndex uint64
// Atomic: written by Submit (under mu), read by onZero callbacks which
// may fire during Triage while Queue.mu is held. Using atomic avoids
// deadlock: Submit→Triage→onZero→lastSubmissionIndex→mu (ADR-056).
lastSubmissionIndex atomic.Uint64
}

// Submit submits command buffers for execution. Non-blocking.
Expand Down Expand Up @@ -97,7 +100,7 @@ func (q *Queue) Submit(commandBuffers ...*CommandBuffer) (uint64, error) {
}

// Track the latest submission index for deferred resource destruction.
q.lastSubmissionIndex = subIdx
q.lastSubmissionIndex.Store(subIdx)

// Record inflight resources and clean up completed ones.
// dstTextures/dstBuffers prevent premature Release (BUG-DX12-006: use-after-free).
Expand Down Expand Up @@ -346,13 +349,11 @@ func (q *Queue) SetSwapchainSuppressed(suppressed bool) {
}

// LastSubmissionIndex returns the most recent submission index.
// Used by resource Release() methods to schedule deferred destruction.
// Safe for concurrent use — reads under the queue mutex.
// Used by resource Release() and onZero callbacks to schedule deferred destruction.
// Lock-free via atomic — safe to call from onZero callbacks during Triage
// while Queue.mu is held by Submit. ADR-056 deadlock fix.
func (q *Queue) LastSubmissionIndex() uint64 {
q.mu.Lock()
idx := q.lastSubmissionIndex
q.mu.Unlock()
return idx
return q.lastSubmissionIndex.Load()
}

// destroyQueue returns the device's DestroyQueue, or nil if unavailable.
Expand Down
Loading