@@ -325,40 +325,52 @@ private CompletableFuture<List<V>> dispatchQueueBatch(List<K> keys, List<Object>
325325 stats .incrementBatchLoadCountBy (keys .size (), new IncrementBatchLoadCountByStatisticsContext <>(keys , callContexts ));
326326 CompletableFuture <List <V >> batchLoad = invokeLoader (keys , callContexts , queuedFutures , loaderOptions .cachingEnabled ());
327327 return batchLoad
328- .thenApply (values -> {
328+ .thenCompose (values -> {
329329 assertResultSize (keys , values );
330330 if (isPublisher () || isMappedPublisher ()) {
331331 // We have already completed the queued futures by the time the overall batchLoad future has completed.
332- return values ;
332+ return CompletableFuture . completedFuture ( values ) ;
333333 }
334334
335335 List <K > clearCacheKeys = new ArrayList <>();
336+ var batchLoaderScheduler = loaderOptions .getBatchLoaderScheduler ();
337+ CompletableFuture <Void >[] scheduledCompletions = new CompletableFuture [keys .size ()];
336338 for (int idx = 0 ; idx < queuedFutures .size (); idx ++) {
337339 K key = keys .get (idx );
338340 V value = values .get (idx );
339341 Object callContext = callContexts .get (idx );
340342 CompletableFuture <V > future = queuedFutures .get (idx );
341- if (value instanceof Throwable ) {
342- stats .incrementLoadErrorCount (new IncrementLoadErrorCountStatisticsContext <>(key , callContext ));
343- future .completeExceptionally ((Throwable ) value );
344- clearCacheKeys .add (keys .get (idx ));
345- } else if (value instanceof Try ) {
346- // we allow the batch loader to return a Try so we can better represent a computation
347- // that might have worked or not.
348- Try <V > tryValue = (Try <V >) value ;
349- if (tryValue .isSuccess ()) {
350- future .complete (tryValue .get ());
351- } else {
343+ Runnable completeValueRunnable = () -> {
344+ if (value instanceof Throwable ) {
352345 stats .incrementLoadErrorCount (new IncrementLoadErrorCountStatisticsContext <>(key , callContext ));
353- future .completeExceptionally (tryValue .getThrowable ());
354- clearCacheKeys .add (keys .get (idx ));
346+ future .completeExceptionally ((Throwable ) value );
347+ clearCacheKeys .add (key );
348+ } else if (value instanceof Try ) {
349+ // we allow the batch loader to return a Try so we can better represent a computation
350+ // that might have worked or not.
351+ Try <V > tryValue = (Try <V >) value ;
352+ if (tryValue .isSuccess ()) {
353+ future .complete (tryValue .get ());
354+ } else {
355+ stats .incrementLoadErrorCount (new IncrementLoadErrorCountStatisticsContext <>(key , callContext ));
356+ future .completeExceptionally (tryValue .getThrowable ());
357+ clearCacheKeys .add (key );
358+ }
359+ } else {
360+ future .complete (value );
355361 }
362+ };
363+ if (batchLoaderScheduler != null ) {
364+ scheduledCompletions [idx ] = batchLoaderScheduler .scheduleCompletion (completeValueRunnable , key , value );
356365 } else {
357- future . complete ( value );
366+ scheduledCompletions [ idx ] = CompletableFutureKit . run ( completeValueRunnable );
358367 }
359368 }
360- possiblyClearCacheEntriesOnExceptions (clearCacheKeys );
361- return values ;
369+ // Wait for all completions to return
370+ return allOf (scheduledCompletions ).thenApply (ignored -> {
371+ possiblyClearCacheEntriesOnExceptions (clearCacheKeys );
372+ return values ;
373+ });
362374 }).exceptionally (ex -> {
363375 stats .incrementBatchLoadExceptionCount (new IncrementBatchLoadExceptionCountStatisticsContext <>(keys , callContexts ));
364376 if (ex instanceof CompletionException ) {
0 commit comments