@@ -28,7 +28,10 @@ partial class WriteAheadLog
2828 [ AsyncMethodBuilder ( typeof ( SpawningAsyncTaskMethodBuilder ) ) ]
2929 private async Task FlushAsync ( long previousIndex , TimeSpan timeout , CancellationToken token )
3030 {
31- var cleanupTask = Task . CompletedTask ;
31+ // Weak ref tracks the task, but allows GC to collect associated state machine
32+ // as soon as possible. While the task is running, it cannot be collected, because it's referenced
33+ // by the async state machine.
34+ var cleanupTask = GCHandle . Alloc ( Task . CompletedTask , GCHandleType . Weak ) ;
3235 try
3336 {
3437 for ( long newIndex , oldSnapshot = SnapshotIndex , newSnapshot ;
@@ -59,17 +62,21 @@ private async Task FlushAsync(long previousIndex, TimeSpan timeout, Cancellation
5962 lockManager . ReleaseReadLock ( ) ;
6063 }
6164 }
62-
63- if ( cleanupTask . IsCompleted && oldSnapshot < newSnapshot )
64- cleanupTask = CleanUpAsync ( newSnapshot , token ) ;
65+
66+ if ( cleanupTask . Target is null or Task { IsCompletedSuccessfully : true } && oldSnapshot < newSnapshot )
67+ cleanupTask . Target = CleanUpAsync ( newSnapshot , token ) ;
6568
6669 manualFlushQueue . Drain ( ) ;
6770 await flushTrigger . WaitAsync ( timeout , token ) . ConfigureAwait ( false ) ;
6871 }
6972 }
70- catch ( OperationCanceledException e ) when ( e . CancellationToken == token )
73+ catch ( OperationCanceledException e ) when ( e . CancellationToken == token && cleanupTask . Target is Task target )
74+ {
75+ await target . ConfigureAwait ( ConfigureAwaitOptions . SuppressThrowing ) ;
76+ }
77+ finally
7178 {
72- await cleanupTask . ConfigureAwait ( ConfigureAwaitOptions . SuppressThrowing ) ;
79+ cleanupTask . Free ( ) ;
7380 }
7481 }
7582
0 commit comments