@@ -28,6 +28,7 @@ public sealed partial class BulkWriter : IBulkWriter
2828 private Task ? _recvTask ;
2929 private uint _serverAffectedRows ;
3030 private volatile Exception ? _recvError ;
31+ private readonly CancellationTokenSource _cts = new ( ) ;
3132 private int _completed ;
3233 private int _disposed ;
3334
@@ -54,6 +55,11 @@ public async ValueTask WriteAsync(Table.Table table, CancellationToken cancellat
5455
5556 if ( _recvError != null )
5657 {
58+ if ( _recvError is GreptimeException )
59+ {
60+ throw _recvError ;
61+ }
62+
5763 throw new GreptimeException ( $ "Stream already failed: { _recvError . Message } ", _recvError ) ;
5864 }
5965
@@ -102,11 +108,16 @@ public async ValueTask<uint> CompleteAsync(CancellationToken cancellationToken =
102108
103109 if ( _recvTask != null )
104110 {
105- await _recvTask . ConfigureAwait ( false ) ;
111+ await _recvTask . WaitAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
106112 }
107113
108114 if ( _recvError != null )
109115 {
116+ if ( _recvError is GreptimeException )
117+ {
118+ throw _recvError ;
119+ }
120+
110121 throw new GreptimeException ( $ "Bulk write failed: { _recvError . Message } ", _recvError ) ;
111122 }
112123
@@ -132,6 +143,12 @@ public async ValueTask DisposeAsync()
132143 return ;
133144 }
134145
146+ #if NET8_0_OR_GREATER
147+ await _cts . CancelAsync ( ) . ConfigureAwait ( false ) ;
148+ #else
149+ _cts . Cancel ( ) ;
150+ #endif
151+
135152 _putCall ? . Dispose ( ) ;
136153 _recordBatchBuilder . Dispose ( ) ;
137154
@@ -141,12 +158,14 @@ public async ValueTask DisposeAsync()
141158 {
142159 await _recvTask . WaitAsync ( TimeSpan . FromSeconds ( 5 ) ) . ConfigureAwait ( false ) ;
143160 }
144- catch
161+ catch ( Exception ex )
145162 {
146- // Ignore exceptions during disposal
163+ LogBulkWriteError ( _logger , ex . Message ) ;
147164 }
148165 }
149166
167+ _cts . Dispose ( ) ;
168+
150169 LogBulkWriterDisposed ( _logger ) ;
151170 }
152171
@@ -169,16 +188,28 @@ private async Task InitializeStreamAsync(
169188 _putCall = await _flightClient . StartPut ( descriptor , schema , headers , deadline : null , cancellationToken )
170189 . ConfigureAwait ( false ) ;
171190
172- _recvTask = DrainResponsesAsync ( _putCall . ResponseStream ) ;
191+ _recvTask = RunRecvLoopAsync ( _putCall . ResponseStream ) ;
173192
174193 LogStreamInitialized ( _logger , tableName ) ;
175194 }
176195
177- internal async Task DrainResponsesAsync ( IAsyncStreamReader < FlightPutResult > responseStream )
196+ private async Task RunRecvLoopAsync ( IAsyncStreamReader < FlightPutResult > responseStream )
197+ {
198+ var ( affectedRows , error ) = await DrainResponsesAsync ( responseStream , _cts . Token ) . ConfigureAwait ( false ) ;
199+ _serverAffectedRows = affectedRows ;
200+ _recvError = error ;
201+ }
202+
203+ internal static async Task < ( uint AffectedRows , Exception ? Error ) > DrainResponsesAsync (
204+ IAsyncStreamReader < FlightPutResult > responseStream ,
205+ CancellationToken cancellationToken = default )
178206 {
207+ uint affectedRows = 0 ;
208+ Exception ? error = null ;
209+
179210 try
180211 {
181- while ( await responseStream . MoveNext ( CancellationToken . None ) . ConfigureAwait ( false ) )
212+ while ( await responseStream . MoveNext ( cancellationToken ) . ConfigureAwait ( false ) )
182213 {
183214 var result = responseStream . Current ;
184215 if ( result . ApplicationMetadata == null || result . ApplicationMetadata . IsEmpty )
@@ -191,24 +222,30 @@ internal async Task DrainResponsesAsync(IAsyncStreamReader<FlightPutResult> resp
191222 var resp = JsonSerializer . Deserialize < DoPutResponse > ( result . ApplicationMetadata . Span ) ;
192223 if ( resp != null )
193224 {
194- _serverAffectedRows += resp . AffectedRows ;
225+ affectedRows += resp . AffectedRows ;
195226 }
196227 }
197228 catch ( JsonException ex )
198229 {
199- _recvError ??= new GreptimeException (
230+ error ??= new GreptimeException (
200231 $ "Failed to deserialize PutResult metadata: { ex . Message } ", ex ) ;
201232 }
202233 }
203234 }
235+ catch ( OperationCanceledException ex )
236+ {
237+ error ??= ex ;
238+ }
204239 catch ( RpcException ex )
205240 {
206- _recvError ??= ex ;
241+ error ??= ex ;
207242 }
208- catch ( ObjectDisposedException )
243+ catch ( ObjectDisposedException ex )
209244 {
210- // Stream disposed during shutdown, expected
245+ error ??= ex ;
211246 }
247+
248+ return ( affectedRows , error ) ;
212249 }
213250
214251 private void ThrowIfDisposed ( )
0 commit comments