/** * Copyright 2016 LinkedIn Corp. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ package com.github.ambry.router; import com.github.ambry.clustermap.PartitionId; import com.github.ambry.clustermap.ReplicaId; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; /** * A implementation of {@link OperationTracker}. It internally maintains the status of a * corresponding operation, and returns information that decides if the operation should * continue or terminate. * * This implementation simplifies such that it unifies parallelism. That is, a single parallelism * parameter controls the maximum number of total allowed in-flight requests to both local and remote * replicas. This simplification is valid for PUT operation, yet a mature implementation will take * a more sophisticated control of parallelism in the future. * * This class assumes a request will be {@code succeeded, failed, or timedout} (which means failed). * So a deterministic response will be received in a definite time, and no request will pend forever. * When a request is timed out, it is considered as failed. * * A typical usage of an {@code SimpleOperationTracker} would be: *<pre> *{@code * * SimpleOperationTracker operationTracker = new SimpleOperationTracker(datacenterName, * partitionId, crossColoEnabled, successTarget, parallelism); * //... * Iterator<ReplicaId> itr = operationTracker.getReplicaIterator(); * while (itr.hasNext()) { * ReplicaId nextReplica = itr.next(); * //determine request can be sent to the replica, i.e., connection available. * if(true) { * itr.remove(); * } * } *} *</pre> * */ class SimpleOperationTracker implements OperationTracker { protected final int successTarget; protected final int parallelism; protected final LinkedList<ReplicaId> replicaPool = new LinkedList<ReplicaId>(); protected int totalReplicaCount = 0; protected int inflightCount = 0; protected int succeededCount = 0; protected int failedCount = 0; private final OpTrackerIterator otIterator; private Iterator<ReplicaId> replicaIterator; /** * Constructor for an {@code SimpleOperationTracker}. * * @param datacenterName The datacenter where the router is located. * @param partitionId The partition on which the operation is performed. * @param crossColoEnabled {@code true} if requests can be sent to remote replicas, {@code false} * otherwise. * @param successTarget The number of successful responses required to succeed the operation. * @param parallelism The maximum number of inflight requests at any point of time. * @param shuffleReplicas Indicates if the replicas need to be shuffled. */ SimpleOperationTracker(String datacenterName, PartitionId partitionId, boolean crossColoEnabled, int successTarget, int parallelism, boolean shuffleReplicas) { if (parallelism < 1) { throw new IllegalArgumentException("Parallelism has to be > 0. Configured to be " + parallelism); } this.successTarget = successTarget; this.parallelism = parallelism; // Order the replicas so that local healthy replicas are ordered and returned first, // then the remote healthy ones, and finally the possibly down ones. List<? extends ReplicaId> replicas = partitionId.getReplicaIds(); LinkedList<ReplicaId> downReplicas = new LinkedList<>(); if (shuffleReplicas) { Collections.shuffle(replicas); } for (ReplicaId replicaId : replicas) { String replicaDcName = replicaId.getDataNodeId().getDatacenterName(); if (!replicaId.isDown()) { if (replicaDcName.equals(datacenterName)) { replicaPool.addFirst(replicaId); } else if (crossColoEnabled) { replicaPool.addLast(replicaId); } } else { if (replicaDcName.equals(datacenterName)) { downReplicas.addFirst(replicaId); } else if (crossColoEnabled) { downReplicas.addLast(replicaId); } } } replicaPool.addAll(downReplicas); totalReplicaCount = replicaPool.size(); if (totalReplicaCount < successTarget) { throw new IllegalArgumentException( "Total Replica count " + totalReplicaCount + " is less than success target " + successTarget); } this.otIterator = new OpTrackerIterator(); } /** * Constructor for an {@code SimpleOperationTracker}, which shuffles replicas. * * @param datacenterName The datacenter where the router is located. * @param partitionId The partition on which the operation is performed. * @param crossColoEnabled {@code true} if requests can be sent to remote replicas, {@code false} * otherwise. * @param successTarget The number of successful responses required to succeed the operation. * @param parallelism The maximum number of inflight requests at any point of time. */ SimpleOperationTracker(String datacenterName, PartitionId partitionId, boolean crossColoEnabled, int successTarget, int parallelism) { this(datacenterName, partitionId, crossColoEnabled, successTarget, parallelism, true); } @Override public boolean hasSucceeded() { return succeededCount >= successTarget; } @Override public boolean isDone() { return hasSucceeded() || hasFailed(); } @Override public void onResponse(ReplicaId replicaId, boolean isSuccessFul) { inflightCount--; if (isSuccessFul) { succeededCount++; } else { failedCount++; } } @Override public Iterator<ReplicaId> getReplicaIterator() { replicaIterator = replicaPool.iterator(); return otIterator; } private class OpTrackerIterator implements Iterator<ReplicaId> { @Override public boolean hasNext() { return inflightCount < parallelism && replicaIterator.hasNext(); } @Override public void remove() { replicaIterator.remove(); inflightCount++; } @Override public ReplicaId next() { if (!hasNext()) { throw new NoSuchElementException(); } return replicaIterator.next(); } } private boolean hasFailed() { return (totalReplicaCount - failedCount) < successTarget; } }