7171import org .elasticsearch .common .unit .ByteSizeValue ;
7272import org .elasticsearch .common .util .CollectionUtils ;
7373import org .elasticsearch .common .util .Maps ;
74+ import org .elasticsearch .common .util .concurrent .AbstractRunnable ;
7475import org .elasticsearch .common .util .concurrent .EsExecutors ;
76+ import org .elasticsearch .common .util .concurrent .EsRejectedExecutionException ;
7577import org .elasticsearch .common .util .concurrent .ListenableFuture ;
7678import org .elasticsearch .core .FixForMultiProject ;
7779import org .elasticsearch .core .Nullable ;
@@ -1398,9 +1400,34 @@ private void leaveRepoLoop(String repository) {
13981400 }
13991401
14001402 private void finalizeSnapshotEntry (Snapshot snapshot , Metadata metadata , RepositoryData repositoryData ) {
1401- assert currentlyFinalizing .contains (snapshot .getRepository ());
1402- assert repositoryOperations .assertNotQueued (snapshot );
1403- try {
1403+ threadPool .executor (ThreadPool .Names .SNAPSHOT ).execute (new SnapshotFinalization (snapshot , metadata , repositoryData ));
1404+ }
1405+
1406+ /**
1407+ * Implements the finalization process for a snapshot: does some preparatory calculations, builds a {@link SnapshotInfo} and a
1408+ * {@link FinalizeSnapshotContext}, calls {@link Repository#finalizeSnapshot} and handles the outcome by notifying waiting listeners
1409+ * and triggering the next snapshot-related activity (another finalization, a batch of deletes, etc.)
1410+ */
1411+ // This only really makes sense to run against a BlobStoreRepository, and the division of work between this class and
1412+ // BlobStoreRepository#finalizeSnapshot is kind of awkward and artificial; TODO consolidate all this stuff into one place and simplify
1413+ private class SnapshotFinalization extends AbstractRunnable {
1414+
1415+ private final Snapshot snapshot ;
1416+ private final Metadata metadata ;
1417+ private final RepositoryData repositoryData ;
1418+
1419+ SnapshotFinalization (Snapshot snapshot , Metadata metadata , RepositoryData repositoryData ) {
1420+ this .snapshot = snapshot ;
1421+ this .metadata = metadata ;
1422+ this .repositoryData = repositoryData ;
1423+ }
1424+
1425+ @ Override
1426+ protected void doRun () {
1427+ assert ThreadPool .assertCurrentThreadPool (ThreadPool .Names .SNAPSHOT );
1428+ assert currentlyFinalizing .contains (snapshot .getRepository ());
1429+ assert repositoryOperations .assertNotQueued (snapshot );
1430+
14041431 SnapshotsInProgress .Entry entry = SnapshotsInProgress .get (clusterService .state ()).snapshot (snapshot );
14051432 final String failure = entry .failure ();
14061433 logger .trace ("[{}] finalizing snapshot in repository, state: [{}], failure[{}]" , snapshot , entry .state (), failure );
@@ -1428,7 +1455,9 @@ private void finalizeSnapshotEntry(Snapshot snapshot, Metadata metadata, Reposit
14281455 final ListenableFuture <Metadata > metadataListener = new ListenableFuture <>();
14291456 final Repository repo = repositoriesService .repository (snapshot .getRepository ());
14301457 if (entry .isClone ()) {
1431- threadPool .executor (ThreadPool .Names .SNAPSHOT ).execute (ActionRunnable .supply (metadataListener , () -> {
1458+ // This listener is kinda unnecessary since we now always complete it synchronously. It's only here to catch exceptions.
1459+ // TODO simplify this.
1460+ ActionListener .completeWith (metadataListener , () -> {
14321461 final Metadata existing = repo .getSnapshotGlobalMetadata (entry .source ());
14331462 final Metadata .Builder metaBuilder = Metadata .builder (existing );
14341463 final Set <Index > existingIndices = new HashSet <>();
@@ -1450,11 +1479,12 @@ private void finalizeSnapshotEntry(Snapshot snapshot, Metadata metadata, Reposit
14501479 );
14511480 metaBuilder .dataStreams (dataStreamsToCopy , dataStreamAliasesToCopy );
14521481 return metaBuilder .build ();
1453- })) ;
1482+ });
14541483 } else {
14551484 metadataListener .onResponse (metadata );
14561485 }
14571486 metadataListener .addListener (ActionListener .wrap (meta -> {
1487+ assert ThreadPool .assertCurrentThreadPool (ThreadPool .Names .SNAPSHOT );
14581488 final Metadata metaForSnapshot = metadataForSnapshot (entry , meta );
14591489
14601490 final Map <String , SnapshotInfo .IndexSnapshotDetails > indexSnapshotDetails = Maps .newMapWithExpectedSize (
@@ -1554,7 +1584,20 @@ public void onFailure(Exception e) {
15541584 shardGenerations
15551585 )
15561586 ));
1557- } catch (Exception e ) {
1587+ }
1588+
1589+ @ Override
1590+ public void onRejection (Exception e ) {
1591+ if (e instanceof EsRejectedExecutionException esre && esre .isExecutorShutdown ()) {
1592+ logger .debug ("failing finalization of {} due to shutdown" , snapshot );
1593+ handleFinalizationFailure (e , snapshot , repositoryData , ShardGenerations .EMPTY );
1594+ } else {
1595+ onFailure (e );
1596+ }
1597+ }
1598+
1599+ @ Override
1600+ public void onFailure (Exception e ) {
15581601 logger .error (Strings .format ("unexpected failure finalizing %s" , snapshot ), e );
15591602 assert false : new AssertionError ("unexpected failure finalizing " + snapshot , e );
15601603 handleFinalizationFailure (e , snapshot , repositoryData , ShardGenerations .EMPTY );
0 commit comments