@@ -62,6 +62,7 @@ public class BulkIngester<Context> implements AutoCloseable {
62
62
63
63
private @ Nullable ScheduledFuture <?> flushTask ;
64
64
private @ Nullable ScheduledExecutorService scheduler ;
65
+ private boolean isExternalScheduler = false ;
65
66
66
67
// Current state
67
68
private List <BulkOperation > operations = new ArrayList <>();
@@ -82,7 +83,8 @@ private static class RequestExecution<Context> {
82
83
public final List <Context > contexts ;
83
84
public final CompletionStage <BulkResponse > futureResponse ;
84
85
85
- RequestExecution (long id , BulkRequest request , List <Context > contexts , CompletionStage <BulkResponse > futureResponse ) {
86
+ RequestExecution (long id , BulkRequest request , List <Context > contexts ,
87
+ CompletionStage <BulkResponse > futureResponse ) {
86
88
this .id = id ;
87
89
this .request = request ;
88
90
this .contexts = contexts ;
@@ -99,27 +101,25 @@ private BulkIngester(Builder<Context> builder) {
99
101
this .maxOperations = builder .bulkOperations < 0 ? Integer .MAX_VALUE : builder .bulkOperations ;
100
102
this .listener = builder .listener ;
101
103
this .flushIntervalMillis = builder .flushIntervalMillis ;
102
-
103
- if (flushIntervalMillis != null ) {
104
- long flushInterval = flushIntervalMillis ;
105
104
105
+ if (flushIntervalMillis != null || listener != null ) {
106
106
// Create a scheduler if needed
107
- ScheduledExecutorService scheduler ;
108
107
if (builder .scheduler == null ) {
109
- scheduler = Executors .newSingleThreadScheduledExecutor ((r ) -> {
110
- Thread t = Executors .defaultThreadFactory ().newThread (r );
111
- t .setName ("bulk-ingester-flusher#" + ingesterId );
112
- t .setDaemon (true );
113
- return t ;
114
- });
115
-
116
- // Keep it, we'll have to close it.
117
- this .scheduler = scheduler ;
108
+ this .scheduler = Executors .newScheduledThreadPool (maxRequests + 1 , (r ) -> {
109
+ Thread t = Executors .defaultThreadFactory ().newThread (r );
110
+ t .setName ("bulk-ingester-executor#" + ingesterId + "#" + t .getId ());
111
+ t .setDaemon (true );
112
+ return t ;
113
+ });
118
114
} else {
119
115
// It's not ours, we will not close it.
120
- scheduler = builder .scheduler ;
116
+ this .scheduler = builder .scheduler ;
117
+ this .isExternalScheduler = true ;
121
118
}
122
-
119
+ }
120
+
121
+ if (flushIntervalMillis != null ) {
122
+ long flushInterval = flushIntervalMillis ;
123
123
this .flushTask = scheduler .scheduleWithFixedDelay (
124
124
this ::failsafeFlush ,
125
125
flushInterval , flushInterval ,
@@ -221,7 +221,7 @@ public long requestCount() {
221
221
* @see Builder#maxConcurrentRequests
222
222
*/
223
223
public long requestContentionsCount () {
224
- return this .sendRequestCondition .contentions ();
224
+ return this .sendRequestCondition .contentions ();
225
225
}
226
226
227
227
//----- Predicates for the condition variables
@@ -265,7 +265,7 @@ private BulkRequest.Builder newRequest() {
265
265
private void failsafeFlush () {
266
266
try {
267
267
flush ();
268
- } catch (Throwable thr ) {
268
+ } catch (Throwable thr ) {
269
269
// Log the error and continue
270
270
logger .error ("Error in background flush" , thr );
271
271
}
@@ -280,7 +280,8 @@ public void flush() {
280
280
() -> {
281
281
// Build the request
282
282
BulkRequest request = newRequest ().operations (operations ).build ();
283
- List <Context > requestContexts = contexts == null ? Collections .nCopies (operations .size (), null ) : contexts ;
283
+ List <Context > requestContexts = contexts == null ? Collections .nCopies (operations .size (),
284
+ null ) : contexts ;
284
285
285
286
// Prepare for next round
286
287
operations = new ArrayList <>();
@@ -291,7 +292,8 @@ public void flush() {
291
292
long id = sendRequestCondition .invocations ();
292
293
293
294
if (listener != null ) {
294
- listener .beforeBulk (id , request , requestContexts );
295
+ BulkRequest finalRequest = request ;
296
+ scheduler .submit (() -> listener .beforeBulk (id , finalRequest , requestContexts ));
295
297
}
296
298
297
299
CompletionStage <BulkResponse > result = client .bulk (request );
@@ -303,7 +305,7 @@ public void flush() {
303
305
}
304
306
305
307
return new RequestExecution <>(id , request , requestContexts , result );
306
- });
308
+ });
307
309
308
310
if (exec != null ) {
309
311
// A request was actually sent
@@ -317,12 +319,14 @@ public void flush() {
317
319
if (resp != null ) {
318
320
// Success
319
321
if (listener != null ) {
320
- listener .afterBulk (exec .id , exec .request , exec .contexts , resp );
322
+ scheduler .submit (() -> listener .afterBulk (exec .id , exec .request ,
323
+ exec .contexts , resp ));
321
324
}
322
325
} else {
323
326
// Failure
324
327
if (listener != null ) {
325
- listener .afterBulk (exec .id , exec .request , exec .contexts , thr );
328
+ scheduler .submit (() -> listener .afterBulk (exec .id , exec .request ,
329
+ exec .contexts , thr ));
326
330
}
327
331
}
328
332
return null ;
@@ -383,13 +387,14 @@ public void close() {
383
387
// Flush buffered operations
384
388
flush ();
385
389
// and wait for all requests to be completed
386
- closeCondition .whenReady (() -> {});
390
+ closeCondition .whenReady (() -> {
391
+ });
387
392
388
393
if (flushTask != null ) {
389
394
flushTask .cancel (false );
390
395
}
391
396
392
- if (scheduler != null ) {
397
+ if (scheduler != null && ! isExternalScheduler ) {
393
398
scheduler .shutdownNow ();
394
399
}
395
400
}
@@ -404,7 +409,7 @@ public static class Builder<Context> implements ObjectBuilder<BulkIngester<Conte
404
409
private ElasticsearchAsyncClient client ;
405
410
private BulkRequest globalSettings ;
406
411
private int bulkOperations = 1000 ;
407
- private long bulkSize = 5 * 1024 * 1024 ;
412
+ private long bulkSize = 5 * 1024 * 1024 ;
408
413
private int maxConcurrentRequests = 1 ;
409
414
private Long flushIntervalMillis ;
410
415
private BulkListener <Context > listener ;
@@ -438,7 +443,8 @@ public Builder<Context> maxOperations(int count) {
438
443
}
439
444
440
445
/**
441
- * Sets when to flush a new bulk request based on the size in bytes of actions currently added. A request is sent
446
+ * Sets when to flush a new bulk request based on the size in bytes of actions currently added. A
447
+ * request is sent
442
448
* once that size has been exceeded. Defaults to 5 megabytes. Can be set to {@code -1} to disable it.
443
449
*
444
450
* @throws IllegalArgumentException if less than -1.
@@ -452,7 +458,8 @@ public Builder<Context> maxSize(long bytes) {
452
458
}
453
459
454
460
/**
455
- * Sets the number of concurrent requests allowed to be executed. A value of 1 means 1 request is allowed to be executed
461
+ * Sets the number of concurrent requests allowed to be executed. A value of 1 means 1 request is
462
+ * allowed to be executed
456
463
* while accumulating new bulk requests. Defaults to {@code 1}.
457
464
*
458
465
* @throws IllegalArgumentException if less than 1.
@@ -468,7 +475,8 @@ public Builder<Context> maxConcurrentRequests(int max) {
468
475
/**
469
476
* Sets an interval flushing any bulk actions pending if the interval passes. Defaults to not set.
470
477
* <p>
471
- * Flushing is still subject to the maximum number of requests set with {@link #maxConcurrentRequests}.
478
+ * Flushing is still subject to the maximum number of requests set with
479
+ * {@link #maxConcurrentRequests}.
472
480
*
473
481
* @throws IllegalArgumentException if not a positive duration.
474
482
*/
@@ -483,13 +491,25 @@ public Builder<Context> flushInterval(long value, TimeUnit unit) {
483
491
/**
484
492
* Sets an interval flushing any bulk actions pending if the interval passes. Defaults to not set.
485
493
* <p>
486
- * Flushing is still subject to the maximum number of requests set with {@link #maxConcurrentRequests}.
494
+ * Flushing is still subject to the maximum number of requests set with
495
+ * {@link #maxConcurrentRequests}.
496
+ * @deprecated use {@link #scheduler(ScheduledExecutorService)}
487
497
*/
498
+ @ Deprecated
488
499
public Builder <Context > flushInterval (long value , TimeUnit unit , ScheduledExecutorService scheduler ) {
489
500
this .scheduler = scheduler ;
490
501
return flushInterval (value , unit );
491
502
}
492
503
504
+ /**
505
+ * Sets a custom scheduler to run the flush thread and the listener logic. A default one is used if
506
+ * not set.
507
+ */
508
+ public Builder <Context > scheduler (ScheduledExecutorService scheduler ) {
509
+ this .scheduler = scheduler ;
510
+ return this ;
511
+ }
512
+
493
513
public Builder <Context > listener (BulkListener <Context > listener ) {
494
514
this .listener = listener ;
495
515
return this ;
@@ -518,7 +538,8 @@ public Builder<Context> globalSettings(Function<BulkRequest.Builder, BulkRequest
518
538
@ Override
519
539
public BulkIngester <Context > build () {
520
540
// Ensure some chunking criteria are defined
521
- boolean hasCriteria = this .bulkOperations >= 0 || this .bulkSize >= 0 || this .flushIntervalMillis != null ;
541
+ boolean hasCriteria =
542
+ this .bulkOperations >= 0 || this .bulkSize >= 0 || this .flushIntervalMillis != null ;
522
543
523
544
if (!hasCriteria ) {
524
545
throw new IllegalStateException ("No bulk operation chunking criteria have been set." );
0 commit comments