@Override
    protected void configure() {
      IndexShard shard = mock(IndexShard.class);
      bind(IndexShard.class).toInstance(shard);
      when(shard.shardId()).thenReturn(shardId);
      Index index = new Index(TEST_TABLE_NAME);
      bind(Index.class).toInstance(index);
      bind(ShardId.class).toInstance(shardId);
      MapBinder<ReferenceIdent, ShardReferenceImplementation> binder =
          MapBinder.newMapBinder(
              binder(), ReferenceIdent.class, ShardReferenceImplementation.class);
      binder
          .addBinding(SysShardsTableInfo.INFOS.get(new ColumnIdent("id")).ident())
          .toInstance(shardIdExpression);
      bind(ShardReferenceResolver.class).asEagerSingleton();
      bind(AllocationDecider.class).to(DiskThresholdDecider.class);
      bind(ShardCollectService.class).asEagerSingleton();

      bind(DiscoveryService.class).toInstance(discoveryService);

      // blob stuff
      MapBinder<ReferenceIdent, BlobShardReferenceImplementation> blobBinder =
          MapBinder.newMapBinder(
              binder(), ReferenceIdent.class, BlobShardReferenceImplementation.class);
      bind(Settings.class).annotatedWith(IndexSettings.class).toInstance(ImmutableSettings.EMPTY);
    }
  private WriteResult<DeleteResponse> shardDeleteOperation(
      BulkShardRequest request, DeleteRequest deleteRequest, IndexShard indexShard) {
    Engine.Delete delete =
        indexShard.prepareDelete(
            deleteRequest.type(),
            deleteRequest.id(),
            deleteRequest.version(),
            deleteRequest.versionType(),
            Engine.Operation.Origin.PRIMARY);
    indexShard.delete(delete);
    // update the request with the version so it will go to the replicas
    deleteRequest.versionType(delete.versionType().versionTypeForReplicationAndRecovery());
    deleteRequest.version(delete.version());

    assert deleteRequest.versionType().validateVersionForWrites(deleteRequest.version());

    DeleteResponse deleteResponse =
        new DeleteResponse(
            request.index(),
            deleteRequest.type(),
            deleteRequest.id(),
            delete.version(),
            delete.found());
    return new WriteResult(deleteResponse, delete.getTranslogLocation());
  }
 /**
  * Restores shard from {@link RestoreSource} associated with this shard in routing table
  *
  * @param recoveryState recovery state
  */
 public void restore(final RecoveryState recoveryState) {
   RestoreSource restoreSource = indexShard.routingEntry().restoreSource();
   if (restoreSource == null) {
     throw new IndexShardRestoreFailedException(shardId, "empty restore source");
   }
   if (logger.isTraceEnabled()) {
     logger.trace("[{}] restoring shard  [{}]", restoreSource.snapshotId(), shardId);
   }
   try {
     recoveryState.getTranslog().totalOperations(0);
     recoveryState.getTranslog().totalOperationsOnStart(0);
     indexShard.prepareForIndexRecovery();
     IndexShardRepository indexShardRepository =
         repositoriesService.indexShardRepository(restoreSource.snapshotId().getRepository());
     ShardId snapshotShardId = shardId;
     if (!shardId.getIndex().equals(restoreSource.index())) {
       snapshotShardId = new ShardId(restoreSource.index(), shardId.id());
     }
     indexShardRepository.restore(
         restoreSource.snapshotId(), shardId, snapshotShardId, recoveryState);
     indexShard.prepareForTranslogRecovery();
     indexShard.finalizeRecovery();
     indexShard.postRecovery("restore done");
     restoreService.indexShardRestoreCompleted(restoreSource.snapshotId(), shardId);
   } catch (Throwable t) {
     if (Lucene.isCorruptionException(t)) {
       restoreService.failRestore(restoreSource.snapshotId(), shardId());
     }
     throw new IndexShardRestoreFailedException(shardId, "restore failed", t);
   }
 }
 private void maybeRefreshEngine() {
   if (indexSettings.getRefreshInterval().millis() > 0) {
     for (IndexShard shard : this.shards.values()) {
       switch (shard.state()) {
         case CREATED:
         case RECOVERING:
         case CLOSED:
           continue;
         case POST_RECOVERY:
         case STARTED:
         case RELOCATED:
           try {
             if (shard.isRefreshNeeded()) {
               shard.refresh("schedule");
             }
           } catch (EngineClosedException | AlreadyClosedException ex) {
             // fine - continue;
           }
           continue;
         default:
           throw new IllegalStateException("unknown state: " + shard.state());
       }
     }
   }
 }
    /**
     * Returns the shards to purge, i.e. the local started primary shards that have ttl enabled and
     * disable_purge to false
     */
    private List<IndexShard> getShardsToPurge() {
      List<IndexShard> shardsToPurge = new ArrayList<>();
      MetaData metaData = clusterService.state().metaData();
      for (IndexService indexService : indicesService) {
        // check the value of disable_purge for this index
        IndexMetaData indexMetaData = metaData.index(indexService.index());
        if (indexMetaData == null) {
          continue;
        }
        if (indexService.getIndexSettings().isTTLPurgeDisabled()) {
          continue;
        }

        // check if ttl is enabled for at least one type of this index
        boolean hasTTLEnabled = false;
        for (String type : indexService.mapperService().types()) {
          DocumentMapper documentType = indexService.mapperService().documentMapper(type);
          if (documentType.TTLFieldMapper().enabled()) {
            hasTTLEnabled = true;
            break;
          }
        }
        if (hasTTLEnabled) {
          for (IndexShard indexShard : indexService) {
            if (indexShard.state() == IndexShardState.STARTED
                && indexShard.routingEntry().primary()
                && indexShard.routingEntry().started()) {
              shardsToPurge.add(indexShard);
            }
          }
        }
      }
      return shardsToPurge;
    }
  /**
   * Execute the given {@link IndexRequest} on a primary shard, throwing a {@link
   * RetryOnPrimaryException} if the operation needs to be re-tried.
   */
  protected final WriteResult<IndexResponse> executeIndexRequestOnPrimary(
      BulkShardRequest shardRequest, IndexRequest request, IndexShard indexShard) throws Throwable {
    Engine.Index operation = prepareIndexOperationOnPrimary(shardRequest, request, indexShard);
    Mapping update = operation.parsedDoc().dynamicMappingsUpdate();
    final ShardId shardId = indexShard.shardId();
    if (update != null) {
      final String indexName = shardId.getIndex();
      mappingUpdatedAction.updateMappingOnMasterSynchronously(indexName, request.type(), update);
      operation = prepareIndexOperationOnPrimary(shardRequest, request, indexShard);
      update = operation.parsedDoc().dynamicMappingsUpdate();
      if (update != null) {
        throw new RetryOnPrimaryException(
            shardId, "Dynamics mappings are not available on the node that holds the primary yet");
      }
    }
    final boolean created = indexShard.index(operation);

    // update the version on request so it will happen on the replicas
    final long version = operation.version();
    request.version(version);
    request.versionType(request.versionType().versionTypeForReplicationAndRecovery());

    assert request.versionType().validateVersionForWrites(request.version());

    return new WriteResult(
        new IndexResponse(
            shardId.getIndex(), request.type(), request.id(), request.version(), created),
        operation.getTranslogLocation());
  }
 public synchronized void updateMetaData(final IndexMetaData metadata) {
   if (indexSettings.updateIndexMetaData(metadata)) {
     final Settings settings = indexSettings.getSettings();
     for (final IndexShard shard : this.shards.values()) {
       try {
         shard.onRefreshSettings(settings);
       } catch (Exception e) {
         logger.warn("[{}] failed to refresh shard settings", e, shard.shardId().id());
       }
     }
     try {
       indexStore.onRefreshSettings(settings);
     } catch (Exception e) {
       logger.warn("failed to refresh index store settings", e);
     }
     try {
       slowLog.onRefreshSettings(
           settings); // this will be refactored soon anyway so duplication is ok here
     } catch (Exception e) {
       logger.warn("failed to refresh slowlog settings", e);
     }
     if (refreshTask.getInterval().equals(indexSettings.getRefreshInterval()) == false) {
       rescheduleRefreshTasks();
     }
   }
 }
 public PercolateContext(
     PercolateShardRequest request,
     SearchShardTarget searchShardTarget,
     IndexShard indexShard,
     IndexService indexService,
     PageCacheRecycler pageCacheRecycler,
     BigArrays bigArrays,
     ScriptService scriptService,
     Query aliasFilter,
     ParseFieldMatcher parseFieldMatcher) {
   super(parseFieldMatcher, request);
   this.indexShard = indexShard;
   this.indexService = indexService;
   this.fieldDataService = indexService.fieldData();
   this.searchShardTarget = searchShardTarget;
   this.percolateQueryRegistry = indexShard.percolateRegistry();
   this.types = new String[] {request.documentType()};
   this.pageCacheRecycler = pageCacheRecycler;
   this.bigArrays = bigArrays.withCircuitBreaking();
   this.querySearchResult = new QuerySearchResult(0, searchShardTarget);
   this.engineSearcher = indexShard.acquireSearcher("percolate");
   this.searcher = new ContextIndexSearcher(this, engineSearcher);
   this.scriptService = scriptService;
   this.numberOfShards = request.getNumberOfShards();
   this.aliasFilter = aliasFilter;
   this.startTime = request.getStartTime();
 }
  /**
   * Perform phase 3 of the recovery process
   *
   * <p>Phase3 again takes a snapshot of the translog, however this time the snapshot is acquired
   * under a write lock. The translog operations are sent to the target node where they are
   * replayed.
   *
   * <p>{@code InternalEngine#recover} is responsible for taking the snapshot of the translog, and
   * after phase 3 completes the snapshots from all three phases are released.
   */
  @Override
  public void phase3(Translog.Snapshot snapshot) throws ElasticsearchException {
    if (shard.state() == IndexShardState.CLOSED) {
      throw new IndexShardClosedException(request.shardId());
    }
    cancellableThreads.checkForCancel();
    StopWatch stopWatch = new StopWatch().start();
    final int totalOperations;
    logger.trace(
        "[{}][{}] recovery [phase3] to {}: sending transaction log operations",
        indexName,
        shardId,
        request.targetNode());

    // Send the translog operations to the target node
    totalOperations = sendSnapshot(snapshot);

    cancellableThreads.execute(
        new Interruptable() {
          @Override
          public void run() throws InterruptedException {
            // Send the FINALIZE request to the target node. The finalize request
            // clears unreferenced translog files, refreshes the engine now that
            // new segments are available, and enables garbage collection of
            // tombstone files. The shard is also moved to the POST_RECOVERY phase
            // during this time
            transportService
                .submitRequest(
                    request.targetNode(),
                    RecoveryTarget.Actions.FINALIZE,
                    new RecoveryFinalizeRecoveryRequest(request.recoveryId(), request.shardId()),
                    TransportRequestOptions.options()
                        .withTimeout(recoverySettings.internalActionLongTimeout()),
                    EmptyTransportResponseHandler.INSTANCE_SAME)
                .txGet();
          }
        });

    if (request.markAsRelocated()) {
      // TODO what happens if the recovery process fails afterwards, we need to mark this back to
      // started
      try {
        shard.relocated("to " + request.targetNode());
      } catch (IllegalIndexShardStateException e) {
        // we can ignore this exception since, on the other node, when it moved to phase3
        // it will also send shard started, which might cause the index shard we work against
        // to move be closed by the time we get to the the relocated method
      }
    }
    stopWatch.stop();
    logger.trace(
        "[{}][{}] recovery [phase3] to {}: took [{}]",
        indexName,
        shardId,
        request.targetNode(),
        stopWatch.totalTime());
    response.phase3Time = stopWatch.totalTime().millis();
    response.phase3Operations = totalOperations;
  }
 @Override
 protected EmptyResult shardOperation(OptimizeRequest request, ShardRouting shardRouting) {
   IndexShard indexShard =
       indicesService
           .indexServiceSafe(shardRouting.shardId().getIndex())
           .shardSafe(shardRouting.shardId().id());
   indexShard.optimize(request);
   return EmptyResult.INSTANCE;
 }
 @Override
 public void onCache(ShardId shardId, String fieldName, Accountable ramUsage) {
   if (shardId != null) {
     final IndexShard shard = indexService.getShardOrNull(shardId.id());
     if (shard != null) {
       shard.fieldData().onCache(shardId, fieldName, ramUsage);
     }
   }
 }
 @Override
 public void onRemoval(ShardId shardId, String fieldName, boolean wasEvicted, long sizeInBytes) {
   if (shardId != null) {
     final IndexShard shard = indexService.getShardOrNull(shardId.id());
     if (shard != null) {
       shard.fieldData().onRemoval(shardId, fieldName, wasEvicted, sizeInBytes);
     }
   }
 }
 @Override
 public void onRemoval(ShardId shardId, Accountable accountable) {
   if (shardId != null) {
     final IndexShard shard = indexService.getShardOrNull(shardId.id());
     if (shard != null) {
       long ramBytesUsed = accountable != null ? accountable.ramBytesUsed() : 0L;
       shard.shardBitsetFilterCache().onRemoval(ramBytesUsed);
     }
   }
 }
 @Override
 public synchronized void close() throws Exception {
   if (closed == false) {
     closed = true;
     for (IndexShard shard : this) {
       shard.close("eol", false);
       IOUtils.close(shard.store());
     }
   } else {
     throw new AlreadyClosedException("too bad");
   }
 }
 public synchronized void assertAllEqual(int expectedCount) throws IOException {
   Set<Uid> primaryIds = getShardDocUIDs(primary);
   assertThat(primaryIds.size(), equalTo(expectedCount));
   for (IndexShard replica : replicas) {
     Set<Uid> replicaIds = getShardDocUIDs(replica);
     Set<Uid> temp = new HashSet<>(primaryIds);
     temp.removeAll(replicaIds);
     assertThat(replica.routingEntry() + " is missing docs", temp, empty());
     temp = new HashSet<>(replicaIds);
     temp.removeAll(primaryIds);
     assertThat(replica.routingEntry() + " has extra docs", temp, empty());
   }
 }
 public synchronized void removeShard(int shardId, String reason) {
   final ShardId sId = new ShardId(index(), shardId);
   final IndexShard indexShard;
   if (shards.containsKey(shardId) == false) {
     return;
   }
   logger.debug("[{}] closing... (reason: [{}])", shardId, reason);
   HashMap<Integer, IndexShard> newShards = new HashMap<>(shards);
   indexShard = newShards.remove(shardId);
   shards = unmodifiableMap(newShards);
   closeShard(reason, sId, indexShard, indexShard.store(), indexShard.getIndexEventListener());
   logger.debug("[{}] closed (reason: [{}])", shardId, reason);
 }
 // NOTE: O(numShards) cost, but numShards should be smallish?
 private long getAvgShardSizeInBytes() throws IOException {
   long sum = 0;
   int count = 0;
   for (IndexShard indexShard : this) {
     sum += indexShard.store().stats().sizeInBytes();
     count++;
   }
   if (count == 0) {
     return -1L;
   } else {
     return sum / count;
   }
 }
  /** finalizes the recovery process */
  public void finalizeRecovery() {
    if (shard.state() == IndexShardState.CLOSED) {
      throw new IndexShardClosedException(request.shardId());
    }
    cancellableThreads.checkForCancel();
    StopWatch stopWatch = new StopWatch().start();
    logger.trace("[{}][{}] finalizing recovery to {}", indexName, shardId, request.targetNode());
    cancellableThreads.execute(recoveryTarget::finalizeRecovery);

    if (request.isPrimaryRelocation()) {
      // in case of primary relocation we have to ensure that the cluster state on the primary
      // relocation target has all
      // replica shards that have recovered or are still recovering from the current primary,
      // otherwise replication actions
      // will not be send to these replicas. To accomplish this, first block new recoveries, then
      // take version of latest cluster
      // state. This means that no new recovery can be completed based on information of a newer
      // cluster state than the current one.
      try (Releasable ignored =
          delayNewRecoveries.apply(
              "primary relocation hand-off in progress or completed for " + shardId)) {
        final long currentClusterStateVersion = currentClusterStateVersionSupplier.get();
        logger.trace(
            "[{}][{}] waiting on {} to have cluster state with version [{}]",
            indexName,
            shardId,
            request.targetNode(),
            currentClusterStateVersion);
        cancellableThreads.execute(
            () -> recoveryTarget.ensureClusterStateVersion(currentClusterStateVersion));

        logger.trace(
            "[{}][{}] performing relocation hand-off to {}",
            indexName,
            shardId,
            request.targetNode());
        cancellableThreads.execute(() -> shard.relocated("to " + request.targetNode()));
      }
      /**
       * if the recovery process fails after setting the shard state to RELOCATED, both relocation
       * source and target are failed (see {@link IndexShard#updateRoutingEntry}).
       */
    }
    stopWatch.stop();
    logger.trace(
        "[{}][{}] finalizing recovery to {}: took [{}]",
        indexName,
        shardId,
        request.targetNode(),
        stopWatch.totalTime());
  }
  @Override
  protected ShardValidateQueryResponse shardOperation(ShardValidateQueryRequest request) {
    IndexService indexService = indicesService.indexServiceSafe(request.shardId().getIndex());
    IndexShard indexShard = indexService.getShard(request.shardId().id());
    final QueryShardContext queryShardContext = indexService.newQueryShardContext();
    queryShardContext.setTypes(request.types());

    boolean valid;
    String explanation = null;
    String error = null;
    Engine.Searcher searcher = indexShard.acquireSearcher("validate_query");

    DefaultSearchContext searchContext =
        new DefaultSearchContext(
            0,
            new ShardSearchLocalRequest(
                request.types(), request.nowInMillis(), request.filteringAliases()),
            null,
            searcher,
            indexService,
            indexShard,
            scriptService,
            pageCacheRecycler,
            bigArrays,
            threadPool.estimatedTimeInMillisCounter(),
            parseFieldMatcher,
            SearchService.NO_TIMEOUT);
    SearchContext.setCurrent(searchContext);
    try {
      searchContext.parsedQuery(queryShardContext.toQuery(request.query()));
      searchContext.preProcess();

      valid = true;
      if (request.rewrite()) {
        explanation = getRewrittenQuery(searcher.searcher(), searchContext.query());
      } else if (request.explain()) {
        explanation = searchContext.filteredQuery().query().toString();
      }
    } catch (QueryShardException | ParsingException e) {
      valid = false;
      error = e.getDetailedMessage();
    } catch (AssertionError | IOException e) {
      valid = false;
      error = e.getMessage();
    } finally {
      searchContext.close();
      SearchContext.removeCurrent();
    }

    return new ShardValidateQueryResponse(request.shardId(), valid, explanation, error);
  }
 protected final void processAfter(
     boolean refresh, IndexShard indexShard, Translog.Location location) {
   if (refresh) {
     try {
       indexShard.refresh("refresh_flag_index");
     } catch (Throwable e) {
       // ignore
     }
   }
   if (indexShard.getTranslogDurability() == Translog.Durabilty.REQUEST && location != null) {
     indexShard.sync(location);
   }
   indexShard.maybeFlush();
 }
  /**
   * Creates shard snapshot
   *
   * @param snapshotId snapshot id
   * @param snapshotStatus snapshot status
   */
  public void snapshot(final SnapshotId snapshotId, final IndexShardSnapshotStatus snapshotStatus) {
    IndexShardRepository indexShardRepository =
        repositoriesService.indexShardRepository(snapshotId.getRepository());
    if (!indexShard.routingEntry().primary()) {
      throw new IndexShardSnapshotFailedException(
          shardId, "snapshot should be performed only on primary");
    }
    if (indexShard.routingEntry().relocating()) {
      // do not snapshot when in the process of relocation of primaries so we won't get conflicts
      throw new IndexShardSnapshotFailedException(shardId, "cannot snapshot while relocating");
    }
    if (indexShard.state() == IndexShardState.CREATED
        || indexShard.state() == IndexShardState.RECOVERING) {
      // shard has just been created, or still recovering
      throw new IndexShardSnapshotFailedException(shardId, "shard didn't fully recover yet");
    }

    try {
      SnapshotIndexCommit snapshotIndexCommit = indexShard.snapshotIndex();
      try {
        indexShardRepository.snapshot(snapshotId, shardId, snapshotIndexCommit, snapshotStatus);
        if (logger.isDebugEnabled()) {
          StringBuilder sb = new StringBuilder();
          sb.append("snapshot (")
              .append(snapshotId.getSnapshot())
              .append(") completed to ")
              .append(indexShardRepository)
              .append(", took [")
              .append(TimeValue.timeValueMillis(snapshotStatus.time()))
              .append("]\n");
          sb.append("    index    : version [")
              .append(snapshotStatus.indexVersion())
              .append("], number_of_files [")
              .append(snapshotStatus.numberOfFiles())
              .append("] with total_size [")
              .append(new ByteSizeValue(snapshotStatus.totalSize()))
              .append("]\n");
          logger.debug(sb.toString());
        }
      } finally {
        snapshotIndexCommit.close();
      }
    } catch (SnapshotFailedEngineException e) {
      throw e;
    } catch (IndexShardSnapshotFailedException e) {
      throw e;
    } catch (Throwable e) {
      throw new IndexShardSnapshotFailedException(shardId, "Failed to snapshot", e);
    }
  }
    @Override
    public TerminationHandle warmReader(
        final IndexShard indexShard, final Engine.Searcher searcher) {
      final MapperService mapperService = indexShard.mapperService();
      final Map<String, MappedFieldType> warmUpGlobalOrdinals = new HashMap<>();
      for (DocumentMapper docMapper : mapperService.docMappers(false)) {
        for (FieldMapper fieldMapper : docMapper.mappers()) {
          final MappedFieldType fieldType = fieldMapper.fieldType();
          final String indexName = fieldType.name();
          if (fieldType.eagerGlobalOrdinals() == false) {
            continue;
          }
          warmUpGlobalOrdinals.put(indexName, fieldType);
        }
      }
      final IndexFieldDataService indexFieldDataService = indexShard.indexFieldDataService();
      final CountDownLatch latch = new CountDownLatch(warmUpGlobalOrdinals.size());
      for (final MappedFieldType fieldType : warmUpGlobalOrdinals.values()) {
        executor.execute(
            () -> {
              try {
                final long start = System.nanoTime();
                IndexFieldData.Global ifd = indexFieldDataService.getForField(fieldType);
                DirectoryReader reader = searcher.getDirectoryReader();
                IndexFieldData<?> global = ifd.loadGlobal(reader);
                if (reader.leaves().isEmpty() == false) {
                  global.load(reader.leaves().get(0));
                }

                if (indexShard.warmerService().logger().isTraceEnabled()) {
                  indexShard
                      .warmerService()
                      .logger()
                      .trace(
                          "warmed global ordinals for [{}], took [{}]",
                          fieldType.name(),
                          TimeValue.timeValueNanos(System.nanoTime() - start));
                }
              } catch (Exception e) {
                indexShard
                    .warmerService()
                    .logger()
                    .warn("failed to warm-up global ordinals for [{}]", e, fieldType.name());
              } finally {
                latch.countDown();
              }
            });
      }
      return () -> latch.await();
    }
 @Override
 protected ShardUpgradeResult shardOperation(UpgradeRequest request, ShardRouting shardRouting) {
   IndexShard indexShard =
       indicesService
           .indexServiceSafe(shardRouting.shardId().getIndex())
           .shardSafe(shardRouting.shardId().id());
   org.apache.lucene.util.Version oldestLuceneSegment = indexShard.upgrade(request);
   // We are using the current version of Elasticsearch as upgrade version since we update mapping
   // to match the current version
   return new ShardUpgradeResult(
       shardRouting.shardId(),
       indexShard.routingEntry().primary(),
       Version.CURRENT,
       oldestLuceneSegment);
 }
 private void maybeFSyncTranslogs() {
   if (indexSettings.getTranslogDurability() == Translog.Durability.ASYNC) {
     for (IndexShard shard : this.shards.values()) {
       try {
         Translog translog = shard.getTranslog();
         if (translog.syncNeeded()) {
           translog.sync();
         }
       } catch (EngineClosedException | AlreadyClosedException ex) {
         // fine - continue;
       } catch (IOException e) {
         logger.warn("failed to sync translog", e);
       }
     }
   }
 }
 @Override
 protected void onCancel(String reason, @Nullable Exception suppressedException) {
   RuntimeException e;
   if (shard.state() == IndexShardState.CLOSED) { // check if the shard got closed on us
     e =
         new IndexShardClosedException(
             shard.shardId(),
             "shard is closed and recovery was canceled reason [" + reason + "]");
   } else {
     e = new ExecutionCancelledException("recovery was canceled reason [" + reason + "]");
   }
   if (suppressedException != null) {
     e.addSuppressed(suppressedException);
   }
   throw e;
 }
 private void closeShard(
     String reason, ShardId sId, IndexShard indexShard, Store store, IndexEventListener listener) {
   final int shardId = sId.id();
   final Settings indexSettings = this.getIndexSettings().getSettings();
   try {
     try {
       listener.beforeIndexShardClosed(sId, indexShard, indexSettings);
     } finally {
       // this logic is tricky, we want to close the engine so we rollback the changes done to it
       // and close the shard so no operations are allowed to it
       if (indexShard != null) {
         try {
           // only flush we are we closed (closed index or shutdown) and if we are not deleted
           final boolean flushEngine = deleted.get() == false && closed.get();
           indexShard.close(reason, flushEngine);
         } catch (Exception e) {
           logger.debug("[{}] failed to close index shard", e, shardId);
           // ignore
         }
       }
       // call this before we close the store, so we can release resources for it
       listener.afterIndexShardClosed(sId, indexShard, indexSettings);
     }
   } finally {
     try {
       store.close();
     } catch (Exception e) {
       logger.warn(
           "[{}] failed to close store on shard removal (reason: [{}])", e, shardId, reason);
     }
   }
 }
 @Override
 public int hashCode() {
   int result = shard.hashCode();
   result = 31 * result + (int) (readerVersion ^ (readerVersion >>> 32));
   result = 31 * result + value.hashCode();
   return result;
 }
 @Override
 public IndexingResult perform(IndexRequest request) throws Exception {
   TransportWriteAction.WriteResult<IndexResponse> result =
       TransportIndexAction.executeIndexRequestOnPrimary(request, primary, null);
   request.primaryTerm(primary.getPrimaryTerm());
   return new IndexingResult(request, result.getResponse());
 }
 private Set<Uid> getShardDocUIDs(final IndexShard shard) throws IOException {
   shard.refresh("get_uids");
   try (Engine.Searcher searcher = shard.acquireSearcher("test")) {
     Set<Uid> ids = new HashSet<>();
     for (LeafReaderContext leafContext : searcher.reader().leaves()) {
       LeafReader reader = leafContext.reader();
       Bits liveDocs = reader.getLiveDocs();
       for (int i = 0; i < reader.maxDoc(); i++) {
         if (liveDocs == null || liveDocs.get(i)) {
           Document uuid = reader.document(i, Collections.singleton(UidFieldMapper.NAME));
           ids.add(Uid.createUid(uuid.get(UidFieldMapper.NAME)));
         }
       }
     }
     return ids;
   }
 }
 @Override
 public boolean equals(Object o) {
   if (this == o) return true;
   CleanupKey that = (CleanupKey) o;
   if (readerVersion != that.readerVersion) return false;
   if (!indexShard.equals(that.indexShard)) return false;
   return true;
 }