private void onChunkOperationComplete(PutChunk chunk) {
    if (chunk.getChunkBlobId() == null) {
      // the overall operation has failed if any of the chunk fails.
      if (chunk.getChunkException() == null) {
        logger.error("{}: Operation on chunk failed, but no exception was set", loggingContext);
      }
      logger.error("{}: Failed putting chunk at index: {}, failing the entire operation", loggingContext,
          chunk.getChunkIndex());
      setOperationCompleted();
    } else if (chunk != metadataPutChunk) {
      // a data chunk has succeeded.
      metadataPutChunk.addChunkId(chunk.chunkBlobId, chunk.chunkBlobSize, chunk.chunkIndex);
      metadataPutChunk.maybeNotifyForChunkCreation(chunk);
      maybeUpdateSlippedPutSuccessMetrics(chunk);
    } else {
      blobId = chunk.getChunkBlobId();
      maybeUpdateSlippedPutSuccessMetrics(chunk);
      setOperationCompleted();
    }
    long operationLatencyMs = time.milliseconds() - chunk.chunkFillCompleteAtMs;
    if (chunk.chunkBlobProperties != null) {
      if (chunk.chunkBlobProperties.isEncrypted()) {
        routerMetrics.putEncryptedChunkOperationLatencyMs.update(operationLatencyMs);
      } else {
        routerMetrics.putChunkOperationLatencyMs.update(operationLatencyMs, TimeUnit.MILLISECONDS);
      }
    }
    chunk.clear();
  }
Called when the operation on a {@link PutChunk} is complete: That is, the chunk is successfully put or there was
an irrecoverable error in doing so. The {@link PutChunk} guarantees that in the former case,
the blobId of the chunk is set; and in the latter case, it is null.
@param chunk the {@link PutChunk} that has completed its operation.
ambry-router/src/main/java/com/github/ambry/router/PutOperation.java