All Downloads are FREE. Search and download functionalities are using the official Maven repository.

oracle.kv.impl.api.rgstate.RepNodeState Maven / Gradle / Ivy

/*-
 * Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle NoSQL
 * Database made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle NoSQL Database for a copy of the license and
 * additional information.
 */

package oracle.kv.impl.api.rgstate;

import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static oracle.kv.impl.util.ObjectUtil.checkNull;

import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;

import oracle.kv.AuthenticationFailureException;
import oracle.kv.Consistency;
import oracle.kv.impl.api.AsyncRequestHandlerAPI;
import oracle.kv.impl.api.RequestHandlerAPI;
import oracle.kv.impl.api.Response;
import oracle.kv.impl.api.ops.NOP;
import oracle.kv.impl.async.ResultHandler;
import oracle.kv.impl.fault.OperationFaultException;
import oracle.kv.impl.metadata.Metadata;
import oracle.kv.impl.topo.DatacenterId;
import oracle.kv.impl.topo.RepNodeId;
import oracle.kv.impl.topo.ResourceId;
import oracle.kv.impl.util.SerialVersion;
import oracle.kv.impl.util.registry.AsyncRegistryUtils;
import oracle.kv.impl.util.registry.RegistryUtils;

import com.sleepycat.je.rep.ReplicatedEnvironment;
import com.sleepycat.je.rep.ReplicatedEnvironment.State;
import com.sleepycat.je.utilint.VLSN;

/**
 * The operational state associated with the RN or KV client. This state is
 * dynamic and is used primarily to dispatch a request to a specific RN within
 * a replication group.
 * 

* Note that the methods used to modify state are specified as * update rather that set methods. Due to the * distributed and concurrent nature of the processing it's entirely possible * that state update requests can be received out of order, so each update * method must be prepared to ignore obsolete update requests. The return value * from the update methods indicates whether an update was * actually made, or whether the request was obsolete and therefore ignored. */ public class RepNodeState { private final RepNodeId rnId; private final ResourceId trackerId; private final Logger logger; /** The ID of the zone containing the RN, or null if not known. */ private volatile DatacenterId znId = null; /** * The remote request handler for the RN, supporting either the sync or * async API. */ private final ReqHandlerRef reqHandlerRef; /** * The last known haState associated with the RN. We simply want the latest * state so access to it is unsynchronized. */ private volatile State repState = null; /** * The VLSN state associated with this RN */ private final VLSNState vlsnState; private int topoSeqNum = Metadata.UNKNOWN_SEQ_NUM; /** * The size of the sample used for the trailing average. The sample needs * to be large enough so that it represents the different types of * operations, but not too large so that it can't react rapidly enough to * changes in the response times. */ public static final int SAMPLE_SIZE = 8; /** * Accumulates the response time from read requests, so they can be load * balanced. */ private final ResponseTimeAccumulator readAccumulator; /** * The accumulated response times across both read and write operations. */ private final AtomicLong accumRespTimeMs = new AtomicLong(0); /** * The number of request actively being processed by this node. */ private final AtomicInteger activeRequestCount = new AtomicInteger(0); /* The max active request count. Just a rough metric. */ private volatile int maxActiveRequestCount = 0; /** * The total requests dispatched to this node. We don't use an atomic long * here, since it's just intended to be a rough metric and losing an * occasional increment is acceptable. */ private volatile long totalRequestCount; /** * The total number of requests that resulted in exceptions. */ private volatile int errorCount; /** * The approx interval over which the VLSN rate is updated. We may want * to have a different rate for a client hosted dispatcher versus a RN * hosted dispatcher to minimize the number of open tcp connections. RNS * will use these connections only when forwarding requests, which should * be a relatively uncommon event. */ public static int RATE_INTERVAL_MS = 10000; RepNodeState(RepNodeId rnId, ResourceId trackerId, boolean async, Logger logger) { this(rnId, trackerId, async, logger, RATE_INTERVAL_MS); } RepNodeState(RepNodeId rnId, ResourceId trackerId, boolean async, Logger logger, int rateIntervalMs) { this.rnId = rnId; this.trackerId = trackerId; this.logger = checkNull("logger", logger); readAccumulator = new ResponseTimeAccumulator(); reqHandlerRef = async ? new AsyncReqHandlerRef() : new SyncReqHandlerRef(); vlsnState = new VLSNState(rateIntervalMs); /* * Start it out as being in the replica state, so an attempt is made to * contact it by sending it a request. At this point the state can be * adjusted appropriately. */ repState = State.REPLICA; } /** * Returns the unique resourceId associated with the RepNode */ public RepNodeId getRepNodeId() { return rnId; } /** * Returns the ID of the zone containing the RepNode, or {@code null} if * not known. * * @return the zone ID or {@code null} */ public DatacenterId getZoneId() { return znId; } /** * Sets the ID of the zone containing the RepNode. * * @param znId the zone ID */ public void setZoneId(final DatacenterId znId) { this.znId = znId; } /** * See ReqHandlerRef.get */ public RequestHandlerAPI getReqHandlerRef(RegistryUtils registryUtils, long timeoutMs) { return reqHandlerRef.getSync(registryUtils, timeoutMs); } /** Async version. */ public void getReqHandlerRef(RegistryUtils registryUtils, long timeoutMs, ResultHandler hand) { reqHandlerRef.getAsync(registryUtils, timeoutMs, hand); } /** * See ReqHandlerRef.needsResolution() */ public boolean reqHandlerNeedsResolution() { return reqHandlerRef.needsResolution(); } /** * See ReqHandlerRef.needsRepair() */ public boolean reqHandlerNeedsRepair() { return reqHandlerRef.needsRepair(); } /** * This method returns its results via a result handler even when the * implementation is synchronous. Callers should not assume that results * will be returned immediately, either due to blocking or a delay until * the callback handler is called. * * See ReqHandlerRef.resolve */ public void resolveReqHandlerRef(RegistryUtils registryUtils, long timeoutMs, ResultHandler handler) { reqHandlerRef.resolve(registryUtils, timeoutMs, handler); } /** * See ReqHandlerRef.reset */ public void resetReqHandlerRef() { reqHandlerRef.reset(); } /** * See ReqHandlerRef.noteException(Exception) */ public void noteReqHandlerException(Exception e) { reqHandlerRef.noteException(e); } /** * Returns the SerialVersion that should be used with the request handler. */ public short getRequestHandlerSerialVersion() { return reqHandlerRef.getSerialVersion(); } /** * Returns the current known replication state associated with the * node. */ public ReplicatedEnvironment.State getRepState() { return repState; } /** * Updates the rep state. * * @param state the new rep state */ void updateRepState(State state) { repState = state; } /** * Returns the current VLSN associated with the RN. *

* The VLSN can be used to determine how current a replica is with respect * to the Master. It may return a null VLSN if the RN has never been * contacted. */ VLSN getVLSN() { return vlsnState.getVLSN(); } boolean isObsoleteVLSNState() { return vlsnState.isObsolete(); } /** * Updates the VLSN associated with the node. * * @param newVLSN the new VLSN */ void updateVLSN(VLSN newVLSN) { vlsnState.updateVLSN(newVLSN); } /** * Returns the current topo seq number associated with the node. A negative * return value means that the sequence number is unknown. */ int getTopoSeqNum() { return topoSeqNum; } /** * Updates the topo sequence number, moving it forward. If it's less than * the current sequence number it's ignored. * * @param newTopoSeqNum the new sequence number */ synchronized void updateTopoSeqNum(int newTopoSeqNum) { if (topoSeqNum < newTopoSeqNum) { topoSeqNum = newTopoSeqNum; } } /** * Resets the topo sequence number as specified. */ synchronized void resetTopoSeqNum(int endSeqNum) { topoSeqNum = endSeqNum; } /** * Returns the error count. */ public int getErrorCount() { return errorCount; } /** * Increments the error count associated with the RN. * * @return the incremented error count */ public int incErrorCount() { return ++errorCount; } /** * Returns the average trailing read response time associated with the RN * in milliseconds. *

* It's used primarily for load balancing purposes. */ int getAvReadRespTimeMs() { return readAccumulator.getAverage(); } /** * Accumulates the average response time by folding in this contribution * from a successful request to the accumulated response times. This method * is only invoked upon successful completion of a read request. *

* lastAccessTime is also updated each time this method is * invoked. * * @param forWrite determines if the time is being accumulated for a write * operation * @param responseTimeMs the response time associated with this successful * request. */ public void accumRespTime(boolean forWrite, int responseTimeMs) { if (!forWrite) { readAccumulator.update(responseTimeMs); } accumRespTimeMs.getAndAdd(responseTimeMs); } /** * Returns the number of outstanding remote requests to the RN. *

* It's used to ensure that all the outgoing connections are not used * up because the node is slow in responding to requests. */ int getActiveRequestCount() { return activeRequestCount.get(); } /** * Returns the max active requests at this RN, since the count was last * reset. */ public int getMaxActiveRequestCount() { return maxActiveRequestCount; } /** * Returns the total number of requests dispatched to this node. */ public long getTotalRequestCount() { return totalRequestCount; } public long getAccumRespTimeMs() { return accumRespTimeMs.get(); } /** * Resets the total request count. */ public void resetStatsCounts() { totalRequestCount = 0; maxActiveRequestCount = 0; accumRespTimeMs.set(0); errorCount = 0; } /** * Invoked before each remote request is issued to maintain the outstanding * request count. It must be paired with a requestEnd in a finally block. * * @return the number of requests active at this node */ public int requestStart() { totalRequestCount++; final int count = activeRequestCount.incrementAndGet(); maxActiveRequestCount = Math.max(maxActiveRequestCount, count); return count; } /** * Invoked after each remote request completes. It, along with the * requestStart method, is used to maintain the outstanding request count. */ public void requestEnd() { activeRequestCount.decrementAndGet(); } /** * Returns a descriptive string for the rep node state */ public String printString() { return String.format("node: %s " + "state: %s " + "errors: %,d" + "av resp time %,d ms " + "total requests: %,d", getRepNodeId().toString(), getRepState().toString(), getErrorCount(), getAvReadRespTimeMs(), getTotalRequestCount()); } @Override public String toString() { return "RepNodeState[" + getRepNodeId() + ", " + getRepState() + "]"; } /** * AttributeValue encapsulates a RN state value along with a sequence * number used to compare attribute values for recency. * * @param the type associate with the attribute value. */ @SuppressWarnings("unused") private static class AttributeValue { final V value; final long sequence; private AttributeValue(V value, long sequence) { super(); this.value = value; this.sequence = sequence; } /** Returns the value associated with the attribute. */ private V getValue() { return value; } /** * Returns a sequence that can be used to order two attribute * values in time. Given two values v1 and v2, if v2.getSequence() * > v1.getSequence() implies that v2 is the more recent value. */ private long getSequence() { return sequence; } } /** * Encapsulates the computation of average response times. */ private static class ResponseTimeAccumulator { /* * The samples in ms. */ final short[] samples; int sumMs = 0; int index = 0; private ResponseTimeAccumulator() { samples = new short[SAMPLE_SIZE]; sumMs = 0; } private synchronized void update(int sampleMs) { if (sampleMs > Short.MAX_VALUE) { sampleMs = Short.MAX_VALUE; } index = (++index >= SAMPLE_SIZE) ? 0 : index; sumMs += (sampleMs - samples[index]); samples[index] = (short)sampleMs; } /* * Unsynchronized to minimize contention, a slightly stale value is * good enough. */ private int getAverage() { return (sumMs / SAMPLE_SIZE); } } /** * Wraps a remote RequestHandlerAPI reference along with its accompanying * state. It also encapsulates the operations used to maintain the remote * reference. *

* The RemoteReference is typically resolved via the first call to * getSync() or getAsync(). If an exception is encountered at any time, * either during the initial resolve call, or subsequently when an attempt * is made to invoke a method through it, the exception is noted in * exceptionSummary and the remote reference is considered to be in * error. Such erroneous remote references are not considered as candidates * for request dispatch. The RequestHandlerUpdate thread will attempt to * resolve all such references and restore them on a periodic basis. This * resolution is done out of the request's thread of control to minimize * the timeout latencies associated with erroneous references. */ private abstract class ReqHandlerRef { volatile short serialVersion; ReqHandlerRef() { /* TODO: Set to SerialVersion.UNKNOWN to catch errors. */ serialVersion = SerialVersion.CURRENT; } /** * Resets the state of the reference back to its initial state. */ abstract void reset(); /** * Returns the serial version number used to serialize requests to this * request handler. */ short getSerialVersion() { return serialVersion; } /** * Returns true if the ref is not currently resolved. This may be * because the reference has an exception associated with it or simply * because it was never referenced before. */ abstract boolean needsResolution(); /** * Returns true if the ref has a pending exception associated with it * and the reference needs to be resolved before before it can be used. */ abstract boolean needsRepair(); /** * Attempts to resolve the request handler, returning a boolean to the * result handler to say whether the resolution succeeded, and an * exception if there was a specific cause for a failure. Note that * the implementation may be asynchronous. */ abstract void resolve(RegistryUtils registryUtils, long timeoutMs, ResultHandler handler); /** * Returns a remote reference to the RN that can be used to make remote * method calls. It will try resolve the reference if it has never been * resolved before. This method is synchronous. * * @param registryUtils is used to resolve "never been resolved before" * references * @param timeoutMs the amount of time to be spent trying to resolve * the handler * * @return a remote reference to the RN's request handler, or null. */ RequestHandlerAPI getSync(RegistryUtils registryUtils, long timeoutMs) { throw new UnsupportedOperationException( "The sync operation is not supported"); } /** * Like the previous get overloading, but asynchronous. */ @SuppressWarnings("unused") void getAsync(RegistryUtils registryUtils, long timeoutMs, ResultHandler handler) { throw new UnsupportedOperationException( "The async operation is not supported"); } /** * Makes note of the exception encountered during the execution of a * request. This RN is removed from consideration until the background * thread resolves it once again. */ abstract void noteException(Exception e); } /** * Synchronous implementation based on RMI. */ private class SyncReqHandlerRef extends ReqHandlerRef { /* used to coordinate updates to the handler. */ private final Semaphore semaphore = new Semaphore(1,true); /** * The remote request handler (an RMI reference) for the RN. It's * volatile, since any request thread can invalidate it upon * encountering an exception and is read asynchronously by needs * resolution. */ private volatile RequestHandlerAPI requestHandler; /** * It's non-null if there is an error that resulted in the * requestHandler iv above being nulled. It's volatile because it's * read without acquiring the semaphore by needsRepair() as part of a * request dispatch. */ private volatile ExceptionSummary exceptionSummary; private SyncReqHandlerRef() { requestHandler = null; exceptionSummary = null; } @Override void reset() { try { semaphore.acquire(); } catch (InterruptedException e) { throw new OperationFaultException("Unexpected interrupt", e); } try { requestHandler = null; /* TODO: Set to SerialVersion.UNKNOWN to catch errors. */ serialVersion = SerialVersion.CURRENT; exceptionSummary = null; } finally { semaphore.release(); } } @Override boolean needsResolution() { return (requestHandler == null); } /** * Note that, in this implementation, the exception summary is read * outside the semaphore and that's ok, we just need an approximate * answer. */ @Override boolean needsRepair() { return (exceptionSummary != null); } @Override void resolve(RegistryUtils registryUtils, long timeoutMs, ResultHandler handler) { try { handler.onResult(resolve(registryUtils, timeoutMs) != null, null); } catch (Exception e) { handler.onResult(null, e); } } /** * Resolves the remote reference based upon its Topology in * RegistryUtils. * * This method tries to "fail fast" so that it does not run down the * request timeout limit, when it's invoked in a request context. * * It implements this "fail fast" strategy by noting whether it * acquired the lock with contention. If it acquired the lock with * contention and the state is still unresolved, it means that the * immediately preceding attempt to repair it failed and there is no * reason to retry resolving it immediately, particularly since * resolving it may involved getting stuck in a connect open timeout * which would eat into the request timeout limit and prevent retries * at other nodes which may be available. The first thread that * acquires the lock uncontended will be the one that attempts to * resolve the state. If it fails, a retry at a higher level, either by * the RepNodeStateUpdateThread, or RequestDispatcherImpl, depending on * which thread gets there first, will resolve it. * * @param registryUtils used to resolve the remote reference * * @param timeoutMs the amount of time to be spent trying to resolve * the handler * * @return the handle or null if one could not be established due to a * timeout, a remote exception, a recently failed attempt by a * different thread, etc. */ private RequestHandlerAPI resolve(RegistryUtils registryUtils, long timeoutMs) { { /* Try getting it contention-free first. */ RequestHandlerAPI refNonVolatile = requestHandler; if (refNonVolatile != null) { return refNonVolatile; } } /** * First try to acquire an uncontended lock */ boolean waited = false; try { if (!semaphore.tryAcquire()) { /* * If not, try waiting. The wait could be as long as the SO * connect timeout for a node that was down, if some other * thread was trying to resolve the reference and had the * lock. */ if (!semaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { /* Could not acquire after waiting. */ return null; } waited = true; } } catch (InterruptedException e) { throw new OperationFaultException("Unexpected interrupt", e); } if (waited) { /* Acquired semaphore after waiting. */ boolean needsRepair = true; try { needsRepair = needsRepair(); if (needsRepair) { /* * Don't bother retrying immediately, since another * thread just failed. Retrying in this case may have * an adverse impact in that it could run a request out * of time, which could otherwise be used to retry a * request at some other node. */ return null; } } finally { if (needsRepair) { /* Exiting method via return or exception. */ semaphore.release(); } } /* Semaphore acquired after waiting. */ } /* Semaphore acquired by this thread. */ try { if (requestHandler != null) { return requestHandler; } try { /* * registryUtils can be null if the RN has not finished * initialization. */ if (registryUtils == null) { return null; } requestHandler = registryUtils.getRequestHandler(rnId); /* * There is a possibility that the requested RN is not yet * in the topology. In this case requestHandler will be * null. This can happen during some elasticity operations. */ if (requestHandler == null) { noteExceptionInternal( new IllegalArgumentException(rnId + " not in topology")); return null; } serialVersion = requestHandler.getSerialVersion(); exceptionSummary = null; return requestHandler; } catch (RemoteException e) { noteExceptionInternal(e); } catch (NotBoundException e) { /* * The service has not yet appeared in the registry, it may * be down, or is in the process of coming up. */ noteExceptionInternal(e); } catch (AuthenticationFailureException e) { noteExceptionInternal(e); } return null; } finally { semaphore.release(); } } @Override RequestHandlerAPI getSync(RegistryUtils registryUtils, long timeoutMs) { final RequestHandlerAPI refNonVolatile = requestHandler; if (refNonVolatile != null) { /* The fast path. NO synchronization. */ return refNonVolatile; } /* * Never been resolved, resolve it. */ if (exceptionSummary == null) { return resolve(registryUtils, timeoutMs); } try { semaphore.acquire(); } catch (InterruptedException e) { throw new OperationFaultException("Unexpected interrupt", e); } try { if (exceptionSummary == null) { return null; } /* * If last resolve failed because of * AuthenticationFailureException, throw the exception here * since it cannot be fixed automatically by the next * resolution. */ final Exception lastException = exceptionSummary.getException(); if (lastException instanceof AuthenticationFailureException) { exceptionSummary = null; throw (AuthenticationFailureException) lastException; } } finally { semaphore.release(); } return null; } @Override void noteException(Exception e) { try { semaphore.acquire(); } catch (InterruptedException ie) { throw new OperationFaultException("Unexpected interrupt", ie); } try { noteExceptionInternal(e); } finally { semaphore.release(); } } /** * Assumes that the caller has already set the semaphore */ private void noteExceptionInternal(Exception e) { assert semaphore.availablePermits() == 0; requestHandler = null; if (exceptionSummary == null) { exceptionSummary = new ExceptionSummary(); } exceptionSummary.noteException(e); } } /** * Asynchronous implementation based on the dialog layer using * AsyncRequestHandlerAPI. */ private class AsyncReqHandlerRef extends ReqHandlerRef { /* * Access to all fields must be protected by synchronization on this * instance. */ /** The remote request handler for the RN. */ private AsyncRequestHandlerAPI requestHandler; /** * Non-null if there is an error that resulted in the requestHandler * field being nulled. */ private ExceptionSummary exceptionSummary; /** Whether a resolve operation is underway. */ private boolean resolving; /** Handlers for results of resolve calls. */ private final List handlers = new ArrayList(); /** * Handle results of resolve calls, including delivering a null result * on timeout. */ private class ResolveHandler implements Runnable { /* * Synchronize on the containing AsyncRequestHandlerAPI instance * when accessing these fields */ /** The handler to receive the request handler, or null if done. */ private ResultHandler handler; /** The future that is waiting for the timeout, or null if done. */ private Future future; /** Whether the result delivery is done. */ private boolean done; ResolveHandler(ResultHandler handler, ScheduledExecutorService executor, long timeoutMs) { if (logger.isLoggable(Level.FINEST)) { logger.finest( "AsyncReqHandlerRef.ResolveHandler create" + " handler=" + handler + " timeoutMs=" + timeoutMs); } assert handler != null; final Future f = executor.schedule(this, timeoutMs, MILLISECONDS); boolean cancel = false; synchronized (AsyncReqHandlerRef.this) { /* * Check for done in case onResult was called (via the * executor timeout processing) before we got to this * point. */ if (done) { cancel = true; } else { this.handler = handler; future = f; handlers.add(this); } } if (cancel) { /* Should already be canceled, but just to be sure */ f.cancel(false); if (logger.isLoggable(Level.FINEST)) { logger.finest( "AsyncReqHandlerRef.ResolveHandler cancel" + " handler=" + handler); } } } /** Deliver null result on timeout. */ @Override public void run() { onResult(null, null); } /** Deliver the request handler or exception to the handler. */ void onResult(AsyncRequestHandlerAPI rh, Throwable e) { final ResultHandler h; final Future f; synchronized (AsyncReqHandlerRef.this) { if (done) { return; } done = true; h = handler; f = future; handler = null; future = null; handlers.remove(this); } if (logger.isLoggable(Level.FINEST)) { logger.finest( "AsyncReqHandlerRef.ResolveHandler onResult" + " handler=" + handler + " rh=" + rh + " e=" + e); } /* * Check for nulls on the off chance that onResult was called * (via the executor timeout processing) before the handler and * future fields were set. */ if (h != null) { h.onResult(rh, e); } if (f != null) { f.cancel(false); } } } @Override synchronized void reset() { requestHandler = null; /* TODO: Set to SerialVersion.UNKNOWN to catch errors. */ serialVersion = SerialVersion.CURRENT; exceptionSummary = null; } @Override synchronized boolean needsResolution() { return (requestHandler == null); } @Override synchronized boolean needsRepair() { return (exceptionSummary != null); } @Override void resolve(RegistryUtils registryUtils, long timeoutMs, final ResultHandler handler) { class ResolveResultHandler implements ResultHandler { @Override public void onResult(AsyncRequestHandlerAPI ref, Throwable e) { handler.onResult(ref != null, e); } } resolveInternal(registryUtils, timeoutMs, new ResolveResultHandler(), true); } @Override void getAsync(RegistryUtils registryUtils, long timeoutMs, ResultHandler handler) { resolveInternal(registryUtils, timeoutMs, handler, false); } /** * Implement resolution. If tryResolve is false, then only attempt to * resolve the reference if we don't already have information about a * previous failure. */ private void resolveInternal( RegistryUtils registryUtils, long timeoutMs, ResultHandler handler, boolean tryResolve) { final AsyncRequestHandlerAPI rh; Exception exception = null; synchronized (this) { rh = requestHandler; /* Check if we need to get a new request handler */ if ((rh == null) && (registryUtils != null) && (tryResolve || !needsRepair())) { /* * If there is no request handler, registryUtils are * available (they might not be if the RN has not finished * initialization), and either we are trying to resolve or * there is no exception recorded for a failed resolve, * then get an executor and use it to create a handler to * receive the result when it becomes available. */ final ScheduledExecutorService execService = AsyncRegistryUtils.getEndpointGroup() .getSchedExecService(); /* * Suppress unused warnings -- the handler is stored as a * side effect */ @SuppressWarnings("unused") final ResolveHandler h = new ResolveHandler(handler, execService, timeoutMs); /* If a resolution attempt is not underway, start one */ if (!resolving) { resolving = true; getRequestHandler(registryUtils, timeoutMs); } /* Done -- the handler will get called back */ return; } /* * If last resolve failed because of * AuthenticationFailureException, throw that exception rather * than just returning null since the problem won't be fixed * automatically by the next resolution. */ if (exceptionSummary != null) { final Exception lastEx = exceptionSummary.getException(); if (lastEx instanceof AuthenticationFailureException) { exception = lastEx; exceptionSummary = null; } } } /* Return the current handler or exception */ handler.onResult(rh, exception); } /** Make a remote request for the request handler. */ private void getRequestHandler(final RegistryUtils registryUtils, final long timeoutMs) { AsyncRegistryUtils.getRequestHandler( registryUtils.getTopology(), rnId, trackerId, timeoutMs, logger, new ResultHandler() { @Override public void onResult(AsyncRequestHandlerAPI r, Throwable e) { deliverResult(r, e); } }); } /** * Deliver the results of a remote call to obtain the request handler. * If a request handler was obtained successfully, supply it to all * waiting handlers. If the request handler was not available, note * the exception and deliver the failure results immediately. Retrying * in that case might have an adverse impact because it could run a * request out of time which could otherwise be used to retry the * request on another node. */ private void deliverResult(AsyncRequestHandlerAPI rh, Throwable e) { synchronized (this) { assert resolving; requestHandler = rh; if (rh != null) { serialVersion = rh.getSerialVersion(); exceptionSummary = null; } else if (e == null) { /* * There is a possibility that the requested RN is not yet * in the topology. In this case requestHandler will be * null. This can happen during some elasticity operations. * Note the exception but return null so that the caller * can retry. */ noteException(new IllegalArgumentException( rnId + " not in topology")); } else if (e instanceof Exception) { noteException((Exception) e); } else if (e instanceof Error) { throw (Error) e; } else { throw new AssertionError(e); } } /* Deliver the results */ while (true) { final ResolveHandler handler; synchronized (this) { if (handlers.isEmpty()) { resolving = false; break; } handler = handlers.remove(0); } handler.onResult(rh, e); } } @Override synchronized void noteException(Exception e) { requestHandler = null; if (exceptionSummary == null) { exceptionSummary = new ExceptionSummary(); } exceptionSummary.noteException(e); } } /** * Tracks errors encountered when trying to create the reference. */ private static class ExceptionSummary { /** * Track errors resulting from attempts to create a remote reference. * If the value is > 0 then exception must be non null and denotes the * last exception that resulted in the reference being unresolved. */ private int errorCount = 0; private Exception exception = null; private long exceptionTimeMs = 0; private void noteException(Exception e) { exception = e; errorCount++; exceptionTimeMs = System.currentTimeMillis(); } private Exception getException() { return exception; } @SuppressWarnings("unused") private long getExceptionTimeMs() { return exceptionTimeMs; } @SuppressWarnings("unused") private int getErrorCount() { return errorCount; } } /** * Returns true if the version based consistency requirements can be * satisfied at time timeMs based upon the historical rate of * progress associated with the node. * * @param timeMs the time at which the consistency requirement is to be * satisfied * * @param consistency the version consistency requirement */ boolean inConsistencyRange(long timeMs, Consistency.Version consistency) { assert consistency != null; final VLSN consistencyVLSN = new VLSN(consistency.getVersion().getVLSN()); return vlsnState.vlsnAt(timeMs).compareTo(consistencyVLSN) >= 0; } /** * Returns true if the time based consistency requirements can be satisfied * at time timeMs based upon the historical rate of progress * associated with the node. * * @param timeMs the time at which the consistency requirement is to be * satisfied * * @param consistency the time consistency requirement */ boolean inConsistencyRange(long timeMs, Consistency.Time consistency, RepNodeState master) { assert consistency != null; if (master == null) { /* * No master information, assume it is. The information will get * updated as a result of the request directed to it. */ return true; } final long lagMs = consistency.getPermissibleLag(TimeUnit.MILLISECONDS); final VLSN consistencyVLSN = master.vlsnState.vlsnAt(timeMs - lagMs); return vlsnState.vlsnAt(timeMs).compareTo(consistencyVLSN) >= 0; } /** * VLSNState encapsulates the handling of all VLSN state associated with * this node. *

* The VLSN state is typically updated based upon status * information returned in a {@link Response}. It's read during a request * dispatch for requests that specify a consistency requirement to help * direct the request to a node that's in a good position to satisfy the * constraint. *

* The state is also updated by {@link RepNodeStateUpdateThread} on a * periodic basis through the use of {@link NOP} requests. */ private static class VLSNState { /** * The last known vlsn */ private volatile VLSN vlsn; /** * The time at which the vlsn above was last updated. */ private long lastUpdateMs; /** * The starting vlsn used to compute the next iteration of the * vlsnsPerSec. * * Invariant: intervalStart <= vlsn */ private VLSN intervalStart; /** * The time at which the intervalStart was updated. * * Invariant: intervalStartMs <= lastUpdateMs */ private long intervalStartMs; /* The vlsn rate: vlsns/sec */ private long vlsnsPerSec; private final int rateIntervalMs; private VLSNState(int rateIntervalMs) { vlsn = VLSN.NULL_VLSN; intervalStart = VLSN.NULL_VLSN; intervalStartMs = 0l; vlsnsPerSec = 0; this.rateIntervalMs = rateIntervalMs; } /** * Returns the current VLSN associated with this RN */ private VLSN getVLSN() { return vlsn; } /** * Returns true if the state information has not been updated over an * interval exceeding MAX_RATE_INTERVAL_MS */ private synchronized boolean isObsolete() { return (lastUpdateMs + rateIntervalMs) < System.currentTimeMillis(); } /** * Updates the VLSN and maintains the vlsn rate associated with it. * * @param newVLSN the new VLSN */ private synchronized void updateVLSN(VLSN newVLSN) { if ((newVLSN == null) || newVLSN.isNull()) { return; } lastUpdateMs = System.currentTimeMillis(); if (newVLSN.compareTo(vlsn) > 0) { vlsn = newVLSN; } final long intervalMs = (lastUpdateMs - intervalStartMs); if (intervalMs <= rateIntervalMs) { return; } if (intervalStartMs == 0) { resetRate(lastUpdateMs); return; } final long vlsnDelta = (vlsn.getSequence() - intervalStart.getSequence()); if (vlsnDelta < 0) { /* Possible hard recovery */ resetRate(lastUpdateMs); } else { intervalStart = vlsn; intervalStartMs = lastUpdateMs; vlsnsPerSec = (vlsnDelta * 1000) / intervalMs; } } /** * Returns the VLSN that the RN will have progressed to at * timeMs based upon its current known state and its rate * of progress. * * @param timeMs the time in ms * * @return the predicted VLSN at timeMs or NULL_VLSN if the state has * not yet been initialized. */ private synchronized VLSN vlsnAt(long timeMs) { if (vlsn.isNull()) { return VLSN.NULL_VLSN; } final long deltaMs = (timeMs - lastUpdateMs); /* deltaMs can be negative. */ final long vlsnAt = vlsn.getSequence() + ((deltaMs * vlsnsPerSec) / 1000); /* * Return the zero VLSN if the extrapolation yields a negative * value since vlsns cannot be negative. */ return vlsnAt < 0 ? new VLSN(0) : new VLSN(vlsnAt); } private void resetRate(long now) { intervalStart = vlsn; intervalStartMs = now; vlsnsPerSec = 0; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy