@@ -160,19 +160,35 @@ public CompletableFuture<Void> buildIndexAsync(boolean markReadable, boolean use
160
160
lastProgressSnapshot = StoreTimerSnapshot .from (timer );
161
161
}
162
162
message .addKeyAndValue (LogMessageKeys .SESSION_ID , common .getUuid ());
163
- return handleStateAndDoBuildIndexAsync (markReadable , message ).whenComplete ((vignore , ex ) -> {
164
- message .addKeysAndValues (indexingLogMessageKeyValues ()) // add these here to pick up state accumulated during build
165
- .addKeysAndValues (common .indexLogMessageKeyValues ())
166
- .addKeyAndValue (LogMessageKeys .TOTAL_MICROS , TimeUnit .NANOSECONDS .toMicros (System .nanoTime () - startNanos ));
167
- if (LOGGER .isWarnEnabled () && (ex != null )) {
168
- message .addKeyAndValue (LogMessageKeys .RESULT , "failure" );
169
- message .addKeysAndValues (throttle .logMessageKeyValues ()); // this "last attempt" snapshot information can help debugging
170
- LOGGER .warn (message .toString (), ex );
171
- } else if (LOGGER .isInfoEnabled ()) {
172
- message .addKeyAndValue (LogMessageKeys .RESULT , "success" );
173
- LOGGER .info (message .toString ());
174
- }
175
- });
163
+ AtomicReference <Throwable > indexingException = new AtomicReference <>(null );
164
+ return handleStateAndDoBuildIndexAsync (markReadable , message )
165
+ .handle ((ret , ex ) -> {
166
+ if (ex != null ) {
167
+ indexingException .set (ex );
168
+ }
169
+ message .addKeysAndValues (indexingLogMessageKeyValues ()) // add these here to pick up state accumulated during build
170
+ .addKeysAndValues (common .indexLogMessageKeyValues ())
171
+ .addKeyAndValue (LogMessageKeys .TOTAL_MICROS , TimeUnit .NANOSECONDS .toMicros (System .nanoTime () - startNanos ));
172
+ if (LOGGER .isWarnEnabled () && (ex != null )) {
173
+ message .addKeyAndValue (LogMessageKeys .RESULT , "failure" );
174
+ message .addKeysAndValues (throttle .logMessageKeyValues ()); // this "last attempt" snapshot information can help debugging
175
+ LOGGER .warn (message .toString (), ex );
176
+ } else if (LOGGER .isInfoEnabled ()) {
177
+ message .addKeyAndValue (LogMessageKeys .RESULT , "success" );
178
+ LOGGER .info (message .toString ());
179
+ }
180
+ return ret ;
181
+ })
182
+ .thenCompose (ignore -> clearHeartbeats ())
183
+ .handle ((ignore , exIgnore ) -> {
184
+ Throwable ex = indexingException .get ();
185
+ if (ex instanceof RuntimeException ) {
186
+ throw (RuntimeException ) ex ;
187
+ } else if (ex != null ) {
188
+ throw new RuntimeException (ex );
189
+ }
190
+ return null ;
191
+ });
176
192
}
177
193
178
194
abstract List <Object > indexingLogMessageKeyValues ();
@@ -266,7 +282,7 @@ private CompletableFuture<Void> markIndexesWriteOnly(boolean continueBuild, FDBR
266
282
@ Nonnull
267
283
public CompletableFuture <Boolean > markReadableIfBuilt () {
268
284
AtomicBoolean allReadable = new AtomicBoolean (true );
269
- return common . getRunner ().runAsync (context -> openRecordStore (context ).thenCompose (store ->
285
+ return getRunner ().runAsync (context -> openRecordStore (context ).thenCompose (store ->
270
286
forEachTargetIndex (index -> {
271
287
if (store .isIndexReadable (index )) {
272
288
return AsyncUtil .DONE ;
@@ -313,12 +329,9 @@ public CompletableFuture<Boolean> markIndexReadable(boolean markReadablePlease)
313
329
private CompletableFuture <Boolean > markIndexReadableSingleTarget (Index index , AtomicBoolean anythingChanged ,
314
330
AtomicReference <RuntimeException > runtimeExceptionAtomicReference ) {
315
331
// An extension function to reduce markIndexReadable's complexity
316
- return common . getRunner ().runAsync (context ->
332
+ return getRunner ().runAsync (context ->
317
333
common .getRecordStoreBuilder ().copyBuilder ().setContext (context ).openAsync ()
318
334
.thenCompose (store -> {
319
- if (heartbeat != null ) {
320
- heartbeat .clearHeartbeat (store , index );
321
- }
322
335
return policy .shouldAllowUniquePendingState (store ) ?
323
336
store .markIndexReadableOrUniquePending (index ) :
324
337
store .markIndexReadable (index );
@@ -352,12 +365,13 @@ private CompletableFuture<Void> setIndexingTypeOrThrow(FDBRecordStore store, boo
352
365
353
366
@ Nonnull
354
367
private CompletableFuture <Void > setIndexingTypeOrThrow (FDBRecordStore store , boolean continuedBuild , Index index , IndexBuildProto .IndexBuildIndexingStamp newStamp ) {
368
+ final CompletableFuture <Void > checkUpdateHeartbeat = updateHeartbeat (true , store , index );
355
369
if (forceStampOverwrite && !continuedBuild ) {
356
370
// Fresh session + overwrite = no questions asked
357
371
store .saveIndexingTypeStamp (index , newStamp );
358
- return AsyncUtil . DONE ;
372
+ return checkUpdateHeartbeat ;
359
373
}
360
- return store .loadIndexingTypeStampAsync (index )
374
+ return checkUpdateHeartbeat . thenCompose ( ignore -> store .loadIndexingTypeStampAsync (index )
361
375
.thenCompose (savedStamp -> {
362
376
if (savedStamp == null ) {
363
377
if (continuedBuild && newStamp .getMethod () !=
@@ -399,7 +413,7 @@ private CompletableFuture<Void> setIndexingTypeOrThrow(FDBRecordStore store, boo
399
413
}
400
414
// fall down to exception
401
415
throw newPartlyBuiltException (continuedBuild , savedStamp , newStamp , index );
402
- });
416
+ })) ;
403
417
}
404
418
405
419
private boolean shouldAllowTypeConversionContinue (IndexBuildProto .IndexBuildIndexingStamp newStamp , IndexBuildProto .IndexBuildIndexingStamp savedStamp ) {
@@ -521,7 +535,7 @@ protected CompletableFuture<Boolean> doneOrThrottleDelayAndMaybeLogProgress(bool
521
535
522
536
validateTimeLimit (toWait );
523
537
524
- CompletableFuture <Boolean > delay = MoreAsyncUtil .delayedFuture (toWait , TimeUnit .MILLISECONDS , common . getRunner ().getScheduledExecutor ()).thenApply (vignore3 -> true );
538
+ CompletableFuture <Boolean > delay = MoreAsyncUtil .delayedFuture (toWait , TimeUnit .MILLISECONDS , getRunner ().getScheduledExecutor ()).thenApply (vignore3 -> true );
525
539
if (getRunner ().getTimer () != null ) {
526
540
delay = getRunner ().getTimer ().instrument (FDBStoreTimer .Events .INDEXER_DELAY , delay , getRunner ().getExecutor ());
527
541
}
@@ -835,6 +849,25 @@ private CompletableFuture<Void> updateHeartbeat(boolean validate, FDBRecordStore
835
849
return AsyncUtil .DONE ;
836
850
}
837
851
852
+ private CompletableFuture <Void > clearHeartbeats () {
853
+ // Here: if the heartbeat was *not* cleared while marking the index readable, it would be cleared in
854
+ // these dedicated transaction. Heartbeat clearing is not a blocker but a "best effort" operation.
855
+ if (heartbeat == null ) {
856
+ return AsyncUtil .DONE ;
857
+ }
858
+ return forEachTargetIndex (this ::clearHeartbeatSingleTarget );
859
+ }
860
+
861
+ private CompletableFuture <Void > clearHeartbeatSingleTarget (Index index ) {
862
+ return getRunner ().runAsync (context ->
863
+ common .getRecordStoreBuilder ().copyBuilder ().setContext (context ).openAsync ()
864
+ .thenApply (store -> {
865
+ heartbeat .clearHeartbeat (store , index );
866
+ return null ;
867
+ }));
868
+ }
869
+
870
+
838
871
private boolean shouldValidate () {
839
872
final long minimalInterval = policy .getCheckIndexingMethodFrequencyMilliseconds ();
840
873
if (minimalInterval < 0 || isScrubber ) {
0 commit comments