@@ -134,6 +134,10 @@ private struct CallbackContext
134134 /// The event handle set when the target fence value is reached.
135135 /// </summary>
136136 public HANDLE EventHandle ;
137+
138+ public HANDLE WaitHandle ;
139+
140+ public FenceWaitPublication Publication ;
137141 }
138142
139143 /// <summary>
@@ -151,39 +155,93 @@ private static WaitForFenceValueTaskSource WaitForFenceAsync(
151155 ID3D12GraphicsCommandList * d3D12GraphicsCommandList ,
152156 ID3D12CommandAllocator * d3D12CommandAllocator )
153157 {
154- HANDLE eventHandle = Windows . CreateEventW ( null , Windows . FALSE , Windows . FALSE , null ) ;
155-
156- device . d3D12ComputeFence . Get ( ) ->SetEventOnCompletion ( d3D12FenceValue , eventHandle ) . Assert ( ) ;
157-
158- WaitForFenceValueTaskSource waitForFenceValueTaskSource = WaitForFenceValueTaskSource . Rent ( ) ;
159- CallbackContext * callbackContext = ( CallbackContext * ) NativeMemory . Alloc ( ( nuint ) sizeof ( CallbackContext ) ) ;
160-
161- callbackContext ->WaitForFenceValueTaskSourceHandle = GCHandle . Alloc ( waitForFenceValueTaskSource ) ;
162- callbackContext ->GraphicsDeviceHandle = GCHandle . Alloc ( device ) ;
163- callbackContext ->D3D12GraphicsCommandList = d3D12GraphicsCommandList ;
164- callbackContext ->D3D12CommandAllocator = d3D12CommandAllocator ;
165- callbackContext ->EventHandle = eventHandle ;
158+ WaitForFenceValueTaskSource ? waitForFenceValueTaskSource = null ;
159+ CallbackContext * callbackContext = null ;
160+ bool referenceHeld = false ;
161+ try
162+ {
163+ waitForFenceValueTaskSource = WaitForFenceValueTaskSource . Rent ( ) ;
164+ callbackContext = ( CallbackContext * ) NativeMemory . AllocZeroed ( ( nuint ) sizeof ( CallbackContext ) ) ;
165+ if ( callbackContext is null )
166+ {
167+ throw new OutOfMemoryException ( ) ;
168+ }
166169
167- HANDLE waitHandle ;
170+ callbackContext ->WaitForFenceValueTaskSourceHandle = GCHandle . Alloc ( waitForFenceValueTaskSource ) ;
171+ callbackContext ->GraphicsDeviceHandle = GCHandle . Alloc ( device ) ;
172+ callbackContext ->D3D12GraphicsCommandList = d3D12GraphicsCommandList ;
173+ callbackContext ->D3D12CommandAllocator = d3D12CommandAllocator ;
174+ device . GetReferenceTracker ( ) . DangerousAddRef ( ) ;
175+ referenceHeld = true ;
168176
169- device . GetReferenceTracker ( ) . DangerousAddRef ( ) ;
177+ callbackContext ->EventHandle = Windows . CreateEventW ( null , Windows . FALSE , Windows . FALSE , null ) ;
178+ if ( callbackContext ->EventHandle == default )
179+ {
180+ throw new Win32Exception ( Marshal . GetLastPInvokeError ( ) ) ;
181+ }
170182
171- int result = Windows . RegisterWaitForSingleObject (
172- phNewWaitObject : & waitHandle ,
173- hObject : eventHandle ,
174- Callback : & WaitForSingleObjectCallbackForWaitForFenceAsync ,
175- Context : callbackContext ,
176- dwMilliseconds : Windows . INFINITE ,
177- dwFlags : 0 ) ;
183+ device . d3D12ComputeFence . Get ( ) ->SetEventOnCompletion ( d3D12FenceValue , callbackContext ->EventHandle ) . Assert ( ) ;
184+
185+ HANDLE waitHandle ;
186+ int result = Windows . RegisterWaitForSingleObject (
187+ phNewWaitObject : & waitHandle ,
188+ hObject : callbackContext ->EventHandle ,
189+ Callback : & WaitForSingleObjectCallbackForWaitForFenceAsync ,
190+ Context : callbackContext ,
191+ dwMilliseconds : Windows . INFINITE ,
192+ dwFlags : 0x00000008 ) ; // WT_EXECUTEONLYONCE
193+ if ( result == 0 )
194+ {
195+ throw new Win32Exception ( Marshal . GetLastPInvokeError ( ) ) ;
196+ }
178197
179- // The register is successful if the return value is nonzero
180- if ( result == 0 )
198+ // Publish the handle before allowing cleanup. A callback arriving
199+ // early records completion and returns without touching it again.
200+ callbackContext ->WaitHandle = waitHandle ;
201+ }
202+ catch
181203 {
182- device . GetReferenceTracker ( ) . DangerousRelease ( ) ;
204+ // The command list was already submitted/detached. Drain it before
205+ // closing an event registered with the fence or recycling its allocator.
206+ try
207+ {
208+ device . d3D12ComputeFence . Get ( ) ->SetEventOnCompletion ( d3D12FenceValue , default ) . Assert ( ) ;
209+ device . computeCommandListPool . Return ( d3D12GraphicsCommandList , d3D12CommandAllocator ) ;
210+ }
211+ finally
212+ {
213+ if ( callbackContext is not null )
214+ {
215+ if ( callbackContext ->WaitForFenceValueTaskSourceHandle . IsAllocated )
216+ {
217+ callbackContext ->WaitForFenceValueTaskSourceHandle . Free ( ) ;
218+ }
219+
220+ if ( callbackContext ->GraphicsDeviceHandle . IsAllocated )
221+ {
222+ callbackContext ->GraphicsDeviceHandle . Free ( ) ;
223+ }
224+
225+ if ( callbackContext ->EventHandle != default )
226+ {
227+ _ = Windows . CloseHandle ( callbackContext ->EventHandle ) ;
228+ }
229+
230+ NativeMemory . Free ( callbackContext ) ;
231+ }
232+
233+ if ( referenceHeld )
234+ {
235+ device . GetReferenceTracker ( ) . DangerousRelease ( ) ;
236+ }
237+ }
183238
184- NativeMemory . Free ( callbackContext ) ;
239+ throw ;
240+ }
185241
186- default ( Win32Exception ) . Throw ( E . E_FAIL ) ;
242+ if ( callbackContext ->Publication . OnRegistered ( ) )
243+ {
244+ CompleteFenceWait ( callbackContext , insideCallback : false ) ;
187245 }
188246
189247 return waitForFenceValueTaskSource ;
@@ -199,7 +257,32 @@ private static void WaitForSingleObjectCallbackForWaitForFenceAsync(void* pConte
199257 {
200258 CallbackContext * callbackContext = ( CallbackContext * ) pContext ;
201259
260+ if ( callbackContext ->Publication . OnSignaled ( ) )
261+ {
262+ CompleteFenceWait ( callbackContext , insideCallback : true ) ;
263+ }
264+ }
265+
266+ private static void CompleteFenceWait ( CallbackContext * callbackContext , bool insideCallback )
267+ {
268+ // Never block on our own callback. The early-callback/publisher path
269+ // runs outside the callback and joins it before freeing its context.
270+ int unregistered = Windows . UnregisterWaitEx ( callbackContext ->WaitHandle ,
271+ insideCallback ? default : ( HANDLE ) ( - 1 ) ) ;
272+ int error = unregistered == 0 ? Marshal . GetLastPInvokeError ( ) : 0 ;
273+ Exception ? failure = FenceWaitPublication . UnregisterAccepted ( unregistered != 0 , error , insideCallback )
274+ ? null : new Win32Exception ( error ) ;
275+
202276 WaitForFenceValueTaskSource waitForFenceValueTaskSource = Unsafe . As < WaitForFenceValueTaskSource > ( callbackContext ->WaitForFenceValueTaskSourceHandle . Target ) ! ;
277+ if ( failure is not null )
278+ {
279+ // Cancellation did not establish that the OS released the wait.
280+ // Quarantine its resources and fault the caller rather than free
281+ // a context/event whose native ownership is now uncertain.
282+ waitForFenceValueTaskSource . Fail ( failure ) ;
283+ return ;
284+ }
285+
203286 GraphicsDevice device = Unsafe . As < GraphicsDevice > ( callbackContext ->GraphicsDeviceHandle . Target ) ! ;
204287 ID3D12GraphicsCommandList * d3D12GraphicsCommandList = callbackContext ->D3D12GraphicsCommandList ;
205288 ID3D12CommandAllocator * d3D12CommandAllocator = callbackContext ->D3D12CommandAllocator ;
@@ -277,6 +360,11 @@ public void Complete()
277360 this . manualResetValueTaskSource . SetResult ( null ) ;
278361 }
279362
363+ public void Fail ( Exception error )
364+ {
365+ this . manualResetValueTaskSource . SetException ( error ) ;
366+ }
367+
280368 /// <inheritdoc/>
281369 public void GetResult ( short token )
282370 {
0 commit comments