|
@@ -71,7 +71,9 @@ import org.elasticsearch.common.settings.Settings;
|
|
|
import org.elasticsearch.common.unit.ByteSizeValue;
|
|
|
import org.elasticsearch.common.util.CollectionUtils;
|
|
|
import org.elasticsearch.common.util.Maps;
|
|
|
+import org.elasticsearch.common.util.concurrent.AbstractRunnable;
|
|
|
import org.elasticsearch.common.util.concurrent.EsExecutors;
|
|
|
+import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException;
|
|
|
import org.elasticsearch.common.util.concurrent.ListenableFuture;
|
|
|
import org.elasticsearch.core.FixForMultiProject;
|
|
|
import org.elasticsearch.core.Nullable;
|
|
@@ -1398,9 +1400,34 @@ public final class SnapshotsService extends AbstractLifecycleComponent implement
|
|
|
}
|
|
|
|
|
|
private void finalizeSnapshotEntry(Snapshot snapshot, Metadata metadata, RepositoryData repositoryData) {
|
|
|
- assert currentlyFinalizing.contains(snapshot.getRepository());
|
|
|
- assert repositoryOperations.assertNotQueued(snapshot);
|
|
|
- try {
|
|
|
+ threadPool.executor(ThreadPool.Names.SNAPSHOT).execute(new SnapshotFinalization(snapshot, metadata, repositoryData));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Implements the finalization process for a snapshot: does some preparatory calculations, builds a {@link SnapshotInfo} and a
|
|
|
+ * {@link FinalizeSnapshotContext}, calls {@link Repository#finalizeSnapshot} and handles the outcome by notifying waiting listeners
|
|
|
+ * and triggering the next snapshot-related activity (another finalization, a batch of deletes, etc.)
|
|
|
+ */
|
|
|
+ // This only really makes sense to run against a BlobStoreRepository, and the division of work between this class and
|
|
|
+ // BlobStoreRepository#finalizeSnapshot is kind of awkward and artificial; TODO consolidate all this stuff into one place and simplify
|
|
|
+ private class SnapshotFinalization extends AbstractRunnable {
|
|
|
+
|
|
|
+ private final Snapshot snapshot;
|
|
|
+ private final Metadata metadata;
|
|
|
+ private final RepositoryData repositoryData;
|
|
|
+
|
|
|
+ SnapshotFinalization(Snapshot snapshot, Metadata metadata, RepositoryData repositoryData) {
|
|
|
+ this.snapshot = snapshot;
|
|
|
+ this.metadata = metadata;
|
|
|
+ this.repositoryData = repositoryData;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void doRun() {
|
|
|
+ assert ThreadPool.assertCurrentThreadPool(ThreadPool.Names.SNAPSHOT);
|
|
|
+ assert currentlyFinalizing.contains(snapshot.getRepository());
|
|
|
+ assert repositoryOperations.assertNotQueued(snapshot);
|
|
|
+
|
|
|
SnapshotsInProgress.Entry entry = SnapshotsInProgress.get(clusterService.state()).snapshot(snapshot);
|
|
|
final String failure = entry.failure();
|
|
|
logger.trace("[{}] finalizing snapshot in repository, state: [{}], failure[{}]", snapshot, entry.state(), failure);
|
|
@@ -1428,7 +1455,9 @@ public final class SnapshotsService extends AbstractLifecycleComponent implement
|
|
|
final ListenableFuture<Metadata> metadataListener = new ListenableFuture<>();
|
|
|
final Repository repo = repositoriesService.repository(snapshot.getRepository());
|
|
|
if (entry.isClone()) {
|
|
|
- threadPool.executor(ThreadPool.Names.SNAPSHOT).execute(ActionRunnable.supply(metadataListener, () -> {
|
|
|
+ // This listener is kinda unnecessary since we now always complete it synchronously. It's only here to catch exceptions.
|
|
|
+ // TODO simplify this.
|
|
|
+ ActionListener.completeWith(metadataListener, () -> {
|
|
|
final Metadata existing = repo.getSnapshotGlobalMetadata(entry.source());
|
|
|
final Metadata.Builder metaBuilder = Metadata.builder(existing);
|
|
|
final Set<Index> existingIndices = new HashSet<>();
|
|
@@ -1450,11 +1479,12 @@ public final class SnapshotsService extends AbstractLifecycleComponent implement
|
|
|
);
|
|
|
metaBuilder.dataStreams(dataStreamsToCopy, dataStreamAliasesToCopy);
|
|
|
return metaBuilder.build();
|
|
|
- }));
|
|
|
+ });
|
|
|
} else {
|
|
|
metadataListener.onResponse(metadata);
|
|
|
}
|
|
|
metadataListener.addListener(ActionListener.wrap(meta -> {
|
|
|
+ assert ThreadPool.assertCurrentThreadPool(ThreadPool.Names.SNAPSHOT);
|
|
|
final Metadata metaForSnapshot = metadataForSnapshot(entry, meta);
|
|
|
|
|
|
final Map<String, SnapshotInfo.IndexSnapshotDetails> indexSnapshotDetails = Maps.newMapWithExpectedSize(
|
|
@@ -1554,7 +1584,20 @@ public final class SnapshotsService extends AbstractLifecycleComponent implement
|
|
|
shardGenerations
|
|
|
)
|
|
|
));
|
|
|
- } catch (Exception e) {
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onRejection(Exception e) {
|
|
|
+ if (e instanceof EsRejectedExecutionException esre && esre.isExecutorShutdown()) {
|
|
|
+ logger.debug("failing finalization of {} due to shutdown", snapshot);
|
|
|
+ handleFinalizationFailure(e, snapshot, repositoryData, ShardGenerations.EMPTY);
|
|
|
+ } else {
|
|
|
+ onFailure(e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onFailure(Exception e) {
|
|
|
logger.error(Strings.format("unexpected failure finalizing %s", snapshot), e);
|
|
|
assert false : new AssertionError("unexpected failure finalizing " + snapshot, e);
|
|
|
handleFinalizationFailure(e, snapshot, repositoryData, ShardGenerations.EMPTY);
|