@@ -21,6 +21,7 @@ public class BulkAllObservable<T> : IDisposable, IObservable<IBulkAllResponse> w
21
21
private readonly CancellationToken _compositeCancelToken ;
22
22
private readonly CancellationTokenSource _compositeCancelTokenSource ;
23
23
private readonly Func < IBulkResponseItem , T , bool > _retryPredicate ;
24
+ private Action < IBulkResponseItem , T > _droppedDocumentCallBack ;
24
25
25
26
public BulkAllObservable (
26
27
IElasticClient client ,
@@ -34,6 +35,7 @@ public BulkAllObservable(
34
35
this . _backOffTime = ( this . _partionedBulkRequest ? . BackOffTime ? . ToTimeSpan ( ) ?? CoordinatedRequestDefaults . BulkAllBackOffTimeDefault ) ;
35
36
this . _bulkSize = this . _partionedBulkRequest . Size ?? CoordinatedRequestDefaults . BulkAllSizeDefault ;
36
37
this . _retryPredicate = this . _partionedBulkRequest . RetryDocumentPredicate ?? RetryBulkActionPredicate ;
38
+ this . _droppedDocumentCallBack = this . _partionedBulkRequest . DroppedDocumentCallback ?? DroppedDocumentCallbackDefault ;
37
39
this . _maxDegreeOfParallelism = _partionedBulkRequest . MaxDegreeOfParallelism ?? CoordinatedRequestDefaults . BulkAllMaxDegreeOfParallelismDefault ;
38
40
this . _compositeCancelTokenSource = CancellationTokenSource . CreateLinkedTokenSource ( cancellationToken ) ;
39
41
this . _compositeCancelToken = this . _compositeCancelTokenSource . Token ;
@@ -53,9 +55,6 @@ public IDisposable Subscribe(IObserver<IBulkAllResponse> observer)
53
55
return this ;
54
56
}
55
57
56
- private static ElasticsearchClientException Throw ( string message , IApiCallDetails details ) =>
57
- new ElasticsearchClientException ( PipelineFailure . BadResponse , message , details ) ;
58
-
59
58
private void BulkAll ( IObserver < IBulkAllResponse > observer )
60
59
{
61
60
var documents = this . _partionedBulkRequest . Documents ;
@@ -116,29 +115,85 @@ private async Task<IBulkAllResponse> BulkAsync(IList<T> buffer, long page, int b
116
115
117
116
this . _compositeCancelToken . ThrowIfCancellationRequested ( ) ;
118
117
119
- var retryDocuments = response . Items . Zip ( buffer , ( i , d ) => new { i , d } )
120
- . Where ( x=> ! response . IsValid && this . _retryPredicate ( x . i , x . d ) )
121
- . Select ( x => x . d )
118
+ if ( ! response . ApiCall . Success )
119
+ return await this . HandleBulkRequest ( buffer , page , backOffRetries , response ) ;
120
+
121
+ var documentsWithResponse = response . Items . Zip ( buffer , Tuple . Create ) . ToList ( ) ;
122
+
123
+ this . HandleDroppedDocuments ( documentsWithResponse , response ) ;
124
+
125
+ var retryDocuments = documentsWithResponse
126
+ . Where ( x=> ! response . IsValid && this . _retryPredicate ( x . Item1 , x . Item2 ) )
127
+ . Select ( x => x . Item2 )
122
128
. ToList ( ) ;
123
129
124
130
if ( retryDocuments . Count > 0 && backOffRetries < this . _backOffRetries )
131
+ return await this . RetryDocuments ( page , ++ backOffRetries , retryDocuments ) ;
132
+ else if ( retryDocuments . Count > 0 )
133
+ throw this . ThrowOnBadBulk ( response , $ "Bulk indexing failed and after retrying { backOffRetries } times") ;
134
+
135
+ this . _partionedBulkRequest . BackPressure ? . Release ( ) ;
136
+ return new BulkAllResponse { Retries = backOffRetries , Page = page } ;
137
+ }
138
+
139
+ private void HandleDroppedDocuments ( List < Tuple < IBulkResponseItem , T > > documentsWithResponse , IBulkResponse response )
140
+ {
141
+ var droppedDocuments = documentsWithResponse
142
+ . Where ( x => ! response . IsValid && ! this . _retryPredicate ( x . Item1 , x . Item2 ) )
143
+ . ToList ( ) ;
144
+ if ( droppedDocuments . Count > 0 && ! this . _partionedBulkRequest . ContinueAfterDroppedDocuments )
145
+ throw this . ThrowOnBadBulk ( response , $ "BulkAll halted after receiving failures that can not be retried from _bulk") ;
146
+ else if ( droppedDocuments . Count > 0 && this . _partionedBulkRequest . ContinueAfterDroppedDocuments )
125
147
{
126
- this . _incrementRetries ( ) ;
127
- await Task . Delay ( this . _backOffTime , this . _compositeCancelToken ) . ConfigureAwait ( false ) ;
128
- return await this . BulkAsync ( retryDocuments , page , ++ backOffRetries ) . ConfigureAwait ( false ) ;
148
+ foreach ( var dropped in droppedDocuments ) this . _droppedDocumentCallBack ( dropped . Item1 , dropped . Item2 ) ;
129
149
}
130
- if ( retryDocuments . Count > 0 )
150
+ }
151
+
152
+ private async Task < IBulkAllResponse > HandleBulkRequest ( IList < T > buffer , long page , int backOffRetries , IBulkResponse response )
153
+ {
154
+ var clientException = response . ApiCall . OriginalException as ElasticsearchClientException ;
155
+ //TODO expose this on IAPiCallDetails as RetryLater in 7.0?
156
+ var failureReason = clientException ? . FailureReason . GetValueOrDefault ( PipelineFailure . Unexpected ) ;
157
+ switch ( failureReason )
131
158
{
132
- this . _incrementFailed ( ) ;
133
- this . _partionedBulkRequest . BackPressure ? . Release ( ) ;
134
- throw Throw ( $ "Bulk indexing failed and after retrying { backOffRetries } times", response . ApiCall ) ;
159
+ case PipelineFailure . MaxRetriesReached :
160
+ //TODO move this to its own PipelineFailure classification in 7.0
161
+ if ( response . ApiCall . AuditTrail . Last ( ) . Event == AuditEvent . FailedOverAllNodes )
162
+ throw this . ThrowOnBadBulk ( response , $ "BulkAll halted after attempted bulk failed over all the active nodes") ;
163
+ return await this . RetryDocuments ( page , ++ backOffRetries , buffer ) ;
164
+ case PipelineFailure . CouldNotStartSniffOnStartup :
165
+ case PipelineFailure . BadAuthentication :
166
+ case PipelineFailure . NoNodesAttempted :
167
+ case PipelineFailure . SniffFailure :
168
+ case PipelineFailure . Unexpected :
169
+ throw this . ThrowOnBadBulk ( response ,
170
+ $ "BulkAll halted after { nameof ( PipelineFailure ) } { failureReason . GetStringValue ( ) } from _bulk") ;
171
+ default :
172
+ return await this . RetryDocuments ( page , ++ backOffRetries , buffer ) ;
135
173
}
174
+ }
175
+
176
+ private async Task < IBulkAllResponse > RetryDocuments ( long page , int backOffRetries , IList < T > retryDocuments )
177
+ {
178
+ this . _incrementRetries ( ) ;
179
+ await Task . Delay ( this . _backOffTime , this . _compositeCancelToken ) . ConfigureAwait ( false ) ;
180
+ return await this . BulkAsync ( retryDocuments , page , backOffRetries ) . ConfigureAwait ( false ) ;
181
+ }
182
+
183
+ private Exception ThrowOnBadBulk ( IElasticsearchResponse response , string message )
184
+ {
185
+ this . _incrementFailed ( ) ;
136
186
this . _partionedBulkRequest . BackPressure ? . Release ( ) ;
137
- return new BulkAllResponse { Retries = backOffRetries , Page = page } ;
187
+ return Throw ( message , response . ApiCall ) ;
138
188
}
189
+ private static ElasticsearchClientException Throw ( string message , IApiCallDetails details ) =>
190
+ new ElasticsearchClientException ( PipelineFailure . BadResponse , message , details ) ;
191
+
139
192
140
193
private static bool RetryBulkActionPredicate ( IBulkResponseItem bulkResponseItem , T d ) => bulkResponseItem . Status == 429 ;
141
194
195
+ private static void DroppedDocumentCallbackDefault ( IBulkResponseItem bulkResponseItem , T d ) { }
196
+
142
197
public bool IsDisposed { get ; private set ; }
143
198
144
199
public void Dispose ( )
0 commit comments