Skip to content

Commit 74bcc08

Browse files
GPUUploadManager: fix potential issue with stale pages in ScheduleBufferUpdate
1 parent deb956d commit 74bcc08

3 files changed

Lines changed: 27 additions & 6 deletions

File tree

Graphics/GraphicsTools/include/GPUUploadManagerImpl.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,12 @@ class GPUUploadManagerImpl final : public ObjectBase<IGPUUploadManager>
148148
// Returns the number of pending operations.
149149
size_t GetNumPendingOps() const { return m_NumPendingOps.load(std::memory_order_acquire); }
150150

151+
// Returns true if the page is sealed for new writes.
152+
bool IsSealed() const { return (m_State.load(std::memory_order_acquire) & SEALED_BIT) != 0; }
153+
151154
#ifdef DILIGENT_DEBUG
152155
// Returns the number of active writers. This is used for testing and debugging purposes.
153156
Uint32 DbgGetWriterCount() const { return m_State.load(std::memory_order_acquire) & WRITER_MASK; }
154-
155-
// Returns true if the page is sealed for new writes. This is used for testing and debugging purposes.
156-
bool DbgIsSealed() const { return (m_State.load(std::memory_order_acquire) & SEALED_BIT) != 0; }
157157
#endif
158158

159159
void ReleaseStagingBuffer(IDeviceContext* pContext);

Graphics/GraphicsTools/interface/GPUUploadManager.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ DILIGENT_BEGIN_INTERFACE(IGPUUploadManager, IObject)
135135
void* pCallbackData DEFAULT_VALUE(nullptr)) PURE;
136136

137137
/// Retrieves GPU upload manager statistics.
138+
///
139+
/// The method must not be called concurrently with RenderThreadUpdate().
138140
VIRTUAL void METHOD(GetStats)(THIS_
139141
GPUUploadManagerStats REF Stats) CONST PURE;
140142
};

Graphics/GraphicsTools/src/GPUUploadManagerImpl.cpp

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ bool GPUUploadManagerImpl::Page::ScheduleBufferUpdate(IBuffer*
234234

235235
void GPUUploadManagerImpl::Page::ExecutePendingOps(IDeviceContext* pContext, Uint64 FenceValue)
236236
{
237-
VERIFY(DbgIsSealed(), "Page must be sealed before executing pending operations");
237+
VERIFY(IsSealed(), "Page must be sealed before executing pending operations");
238238
VERIFY(DbgGetWriterCount() == 0, "All writers must finish before executing pending operations");
239239

240240
if (m_pData != nullptr && !m_PersistentMapped)
@@ -284,7 +284,7 @@ void GPUUploadManagerImpl::Page::Reset(IDeviceContext* pContext)
284284

285285
bool GPUUploadManagerImpl::Page::TryEnqueue()
286286
{
287-
VERIFY(DbgIsSealed(), "Page must be sealed before it can be enqueued for execution");
287+
VERIFY(IsSealed(), "Page must be sealed before it can be enqueued for execution");
288288
VERIFY(DbgGetWriterCount() == 0, "All writers must finish before the page can be enqueued");
289289

290290
bool Expected = false;
@@ -412,6 +412,25 @@ void GPUUploadManagerImpl::ScheduleBufferUpdate(IDeviceContext* pC
412412
continue;
413413
}
414414

415+
// Prevent ABA-style bug when pages are reset and reused
416+
// * Thread T1 loads P0 as current, then gets descheduled before TryBeginWriting().
417+
// * Render thread swaps current to P1, seals P0, sees it has 0 ops, calls Reset(nullptr)
418+
// which clears SEALED_BIT, and pushes P0 to FreePages
419+
// * T1 resumes and calls P0->TryBeginWriting() and succeeds (because SEALED_BIT is now cleared).
420+
// * T1 schedules an update into a page that is not current and is simultaneously sitting in the free list,
421+
// potentially being popped/installed elsewhere.
422+
{
423+
Page* CurNow = m_pCurrentPage.load(std::memory_order_acquire);
424+
// Validate that the page is either:
425+
// * still current OR
426+
// * already sealed (meaning it got rotated after we began writing, which is fine because writer count protects it)
427+
if (P != CurNow && !P->IsSealed())
428+
{
429+
Writer.EndWriting(); // Undo the writer count
430+
continue; // Reload current and retry
431+
}
432+
}
433+
415434
const bool UpdateScheduled = Writer.ScheduleBufferUpdate(pDstBuffer, DstOffset, NumBytes, pSrcData, Callback, pCallbackData);
416435
if (Writer.EndWriting() == Page::WritingStatus::LastWriterSealed)
417436
{
@@ -492,7 +511,7 @@ bool GPUUploadManagerImpl::TryRotatePage(IDeviceContext* pContext, Page* Expecte
492511

493512
bool GPUUploadManagerImpl::TryEnqueuePage(Page* P)
494513
{
495-
VERIFY_EXPR(P->DbgIsSealed());
514+
VERIFY_EXPR(P->IsSealed());
496515
if (P->TryEnqueue())
497516
{
498517
if (P->GetNumPendingOps() > 0)

0 commit comments

Comments
 (0)