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

net.spy.memcached.EVCacheMemcachedClient Maven / Gradle / Ivy

The newest version!
package net.spy.memcached;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiPredicate;
import java.util.function.Consumer;

import com.netflix.archaius.api.PropertyRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.netflix.archaius.api.Property;
import com.netflix.evcache.EVCacheGetOperationListener;
import com.netflix.evcache.EVCacheLatch;
import com.netflix.evcache.metrics.EVCacheMetricsFactory;
import com.netflix.evcache.operation.EVCacheAsciiOperationFactory;
import com.netflix.evcache.operation.EVCacheBulkGetFuture;
import com.netflix.evcache.operation.EVCacheItem;
import com.netflix.evcache.operation.EVCacheItemMetaData;
import com.netflix.evcache.operation.EVCacheLatchImpl;
import com.netflix.evcache.operation.EVCacheOperationFuture;
import com.netflix.evcache.pool.EVCacheClient;
import com.netflix.evcache.pool.EVCacheClientUtil;
import com.netflix.evcache.util.EVCacheConfig;
import com.netflix.spectator.api.BasicTag;
import com.netflix.spectator.api.DistributionSummary;
import com.netflix.spectator.api.Tag;
import com.netflix.spectator.api.Timer;
import com.netflix.spectator.ipc.IpcStatus;

import net.spy.memcached.internal.GetFuture;
import net.spy.memcached.internal.OperationFuture;
import net.spy.memcached.ops.ConcatenationType;
import net.spy.memcached.ops.DeleteOperation;
import net.spy.memcached.ops.GetAndTouchOperation;
import net.spy.memcached.ops.GetOperation;
import net.spy.memcached.ops.Mutator;
import net.spy.memcached.ops.Operation;
import net.spy.memcached.ops.OperationCallback;
import net.spy.memcached.ops.OperationStatus;
import net.spy.memcached.ops.StatsOperation;
import net.spy.memcached.ops.StatusCode;
import net.spy.memcached.ops.StoreOperation;
import net.spy.memcached.ops.StoreType;
import net.spy.memcached.protocol.binary.BinaryOperationFactory;
import net.spy.memcached.transcoders.Transcoder;
import net.spy.memcached.util.StringUtils;
import net.spy.memcached.protocol.ascii.ExecCmdOperation;
import net.spy.memcached.protocol.ascii.MetaDebugOperation;
import net.spy.memcached.protocol.ascii.MetaGetOperation;

@edu.umd.cs.findbugs.annotations.SuppressFBWarnings({ "PRMC_POSSIBLY_REDUNDANT_METHOD_CALLS",
"SIC_INNER_SHOULD_BE_STATIC_ANON" })
public class EVCacheMemcachedClient extends MemcachedClient {

    private static final Logger log = LoggerFactory.getLogger(EVCacheMemcachedClient.class);
    private final String appName;
    private final Property readTimeout;

    private final EVCacheClient client;
    private final Map timerMap = new ConcurrentHashMap();
    private final Map distributionSummaryMap = new ConcurrentHashMap();

    private Property mutateOperationTimeout;
    private final ConnectionFactory connectionFactory;
    private final Property maxReadDuration, maxWriteDuration;
    private final Property enableDebugLogsOnWrongKey;

    private volatile boolean alwaysDecodeSync;

    public EVCacheMemcachedClient(ConnectionFactory cf, List addrs,
                                  Property readTimeout, EVCacheClient client) throws IOException {
        super(cf, addrs);
        this.connectionFactory = cf;
        this.readTimeout = readTimeout;
        this.client = client;
        this.appName = client.getAppName();
        final PropertyRepository props = EVCacheConfig.getInstance().getPropertyRepository();
        this.maxWriteDuration = props.get(appName + ".max.write.duration.metric", Integer.class).orElseGet("evcache.max.write.duration.metric").orElse(50);
        this.maxReadDuration = props.get(appName + ".max.read.duration.metric", Integer.class).orElseGet("evcache.max.read.duration.metric").orElse(20);
        this.enableDebugLogsOnWrongKey = props.get(appName + ".enable.debug.logs.on.wrongkey", Boolean.class).orElse(false);

        // TODO in future remove this flag so that decode does not block the IO loop
        // the default/legacy behavior (true) is effectively to decode on the IO loop, set to false to use the transcode threads
        Property alwaysDecodeSyncProperty = props
                .get(appName + ".get.alwaysDecodeSync", Boolean.class)
                .orElseGet("evcache.get.alwaysDecodeSync")
                .orElse(true);
        this.alwaysDecodeSync = alwaysDecodeSyncProperty.get();
        alwaysDecodeSyncProperty.subscribe(v -> alwaysDecodeSync = v);
    }

    public NodeLocator getNodeLocator() {
        return this.mconn.getLocator();
    }

    public MemcachedNode getEVCacheNode(String key) {
        return this.mconn.getLocator().getPrimary(key);
    }

    public  GetFuture asyncGet(final String key, final Transcoder tc) {
        throw new UnsupportedOperationException("asyncGet");
    }

    // Returns 'true' if keys don't match and logs & reports the error.
    // Returns 'false' if keys match.
    // TODO: Consider removing this code once we've fixed the Wrong key bug(s)
    private boolean isWrongKeyReturned(String original_key, String returned_key) {
        if (!original_key.equals(returned_key)) {
            // If they keys don't match, log the error along with the key owning host's information and stack trace.
            final String original_host = getHostNameByKey(original_key);
            final String returned_host = getHostNameByKey(returned_key);
            log.error("Wrong key returned. Key - " + original_key + " (Host: " + original_host + ") ; Returned Key "
                        + returned_key + " (Host: " + returned_host + ")", new Exception());
            client.reportWrongKeyReturned(original_host);

            // If we are configured to dynamically switch log levels to DEBUG on a wrong key error, do so here.
            if (enableDebugLogsOnWrongKey.get()) {
                System.setProperty("log4j.logger.net.spy.memcached", "DEBUG");
            }
            return true;
        }
        return false;
    }

    public  EVCacheOperationFuture asyncGet(final String key, final Transcoder tc, EVCacheGetOperationListener listener) {
        // we should only complete the latch when decode AND complete have completed
        final CountDownLatch latch = new CountDownLatch(2);

        final EVCacheOperationFuture rv = new EVCacheOperationFuture<>(
                key, latch, new AtomicReference(null), readTimeout.get(), executorService, client);

        final DistributionSummary dataSizeDS = getDataSizeDistributionSummary(
                EVCacheMetricsFactory.GET_OPERATION,
                EVCacheMetricsFactory.READ,
                EVCacheMetricsFactory.IPC_SIZE_INBOUND);

        @SuppressWarnings("unchecked")
        final Transcoder transcoder = (tc == null) ? (Transcoder) getTranscoder() : tc;
        final boolean shouldLog = log.isDebugEnabled() && client.getPool().getEVCacheClientPoolManager().shouldLog(appName);

        final Operation op = opFact.get(key, new GetOperation.Callback() {
            // not volatile since only ever used from memcached loop callbacks
            private boolean asyncDecodeIssued = false;

            // both volatile to ensure sync across transcode threads and memcached loop
            private volatile T value;
            private volatile OperationStatus status = null;

            public void gotData(String k, int flags, byte[] data) {
                if (isWrongKeyReturned(key, k)) {
                    return;
                }

                if (shouldLog) {
                    log.debug("Read data : key {}; flags : {}; data : {}", key, flags, data);
                    if (data != null) {
                        log.debug("Key : {}; val size : {}", key, data.length);
                    } else {
                        log.debug("Key : {}; val is null", key);
                    }
                }

                if (data != null)  {
                    dataSizeDS.record(data.length);

                    if (tcService == null) {
                        log.error("tcService is null, will not be able to decode");
                        throw new RuntimeException("TranscoderSevice is null. Not able to decode");
                    }

                    CachedData chunk = new CachedData(flags, data, transcoder.getMaxSize());
                    boolean doSync = alwaysDecodeSync || (!transcoder.asyncDecode(chunk));

                    if (doSync) {
                        value = transcoder.decode(chunk);
                        rv.set(value, status);
                    } else {
                        asyncDecodeIssued = true;
                        final Transcoder wrappedTranscoder = decodeAndThen(transcoder, (decoded) -> {
                            value = decoded;
                            rv.set(decoded, status);
                            latch.countDown();
                        });
                        tcService.decode(wrappedTranscoder, chunk);
                    }
                }
            }

            public void receivedStatus(OperationStatus status) {
                this.status = status;

                // On rare occasion, it might be possible that transcoder finishes and starts to call rv.set(),
                // at the exact time that receivedStatus here does a set(). This means that through unlucky timing
                // here we might drop the decoded value that was set by the transcoder.
                //
                // We add a simple if check to see if the transcode thread has changed the value after we did rv.set(),
                // and if it has, we will do it again. Since value is only set once (after decode), we need only a single
                // check here. It is important that it is a separate volatile read from value.

                T before = value;
                rv.set(before, status);
                T after = value;

                if (after != before) {
                    rv.set(after, status);
                }
            }

            public void complete() {
                // if an async decode was never issued, issue an extra countdown, since 2 latch values were set
                if (!asyncDecodeIssued) {
                    latch.countDown();
                }

                latch.countDown();

                final String metricHit = (asyncDecodeIssued || value != null) ? EVCacheMetricsFactory.YES : EVCacheMetricsFactory.NO;
                final String host = ((rv.getStatus().getStatusCode().equals(StatusCode.TIMEDOUT) && rv.getOperation() != null) ? getHostName(rv.getOperation().getHandlingNode().getSocketAddress()) : null);
                getTimer(EVCacheMetricsFactory.GET_OPERATION, EVCacheMetricsFactory.READ, rv.getStatus(), metricHit, host, getReadMetricMaxValue()).record((System.currentTimeMillis() - rv.getStartTime()), TimeUnit.MILLISECONDS);
                rv.signalComplete();
            }
        });
        rv.setOperation(op);
        if (listener != null) rv.addListener(listener);
        mconn.enqueueOperation(key, op);
        return rv;
    }

    // A Transcode wrapper to allow an action to be performed after decode has completed.
    static  Transcoder decodeAndThen(Transcoder transcoder, Consumer completed) {
        return new Transcoder() {
            @Override
            public boolean asyncDecode(CachedData d) {
                return transcoder.asyncDecode(d);
            }

            @Override
            public CachedData encode(T o) {
                throw new UnsupportedOperationException("encode");
            }

            @Override
            public T decode(CachedData d) {
                T decoded = null;
                try {
                    decoded = transcoder.decode(d);
                    return decoded;
                } finally {
                    completed.accept(decoded);
                }
            }

            @Override
            public int getMaxSize() {
                return transcoder.getMaxSize();
            }
        };
    }

    public  EVCacheBulkGetFuture asyncGetBulk(Collection keys,
                                                    final Transcoder tc,
                                                    EVCacheGetOperationListener listener) {
        return asyncGetBulk(keys, tc, listener, (node, key) -> true);
    }

    public  EVCacheBulkGetFuture asyncGetBulk(Collection keys,
                                                    final Transcoder tc,
                                                    EVCacheGetOperationListener listener,
                                                    BiPredicate nodeValidator) {
        final Map> m = new ConcurrentHashMap>();

        // Break the gets down into groups by key
        final Map> chunks = new HashMap>();
        final NodeLocator locator = mconn.getLocator();

        //Populate Node and key Map
        for (String key : keys) {
            EVCacheClientUtil.validateKey(key, opFact instanceof BinaryOperationFactory);
            final MemcachedNode primaryNode = locator.getPrimary(key);
            if (primaryNode.isActive() && nodeValidator.test(primaryNode, key)) {
                Collection ks = chunks.computeIfAbsent(primaryNode, k -> new ArrayList<>());
                ks.add(key);
            }
        }

        final AtomicInteger pendingChunks = new AtomicInteger(chunks.size());
        int initialLatchCount = chunks.isEmpty() ? 0 : 1;
        final CountDownLatch latch = new CountDownLatch(initialLatchCount);
        final Collection ops = new ArrayList(chunks.size());
        final EVCacheBulkGetFuture rv = new EVCacheBulkGetFuture(m, ops, latch, executorService, client);
        rv.setExpectedCount(chunks.size());

        final DistributionSummary dataSizeDS = getDataSizeDistributionSummary(
                        EVCacheMetricsFactory.BULK_OPERATION,
                        EVCacheMetricsFactory.READ,
                        EVCacheMetricsFactory.IPC_SIZE_INBOUND);

        class EVCacheBulkGetSingleFutureCallback implements GetOperation.Callback {
            final int thisOpId;
            GetOperation op = null;
            public EVCacheBulkGetSingleFutureCallback(int thisOpId) {
                this.thisOpId = thisOpId;
            }

            void bindOp(GetOperation op) {
                assert this.op == null;
                assert op != null;

                this.op = op;
            }

            @Override
            public void receivedStatus(OperationStatus status) {
                if (log.isDebugEnabled()) log.debug("GetBulk Keys : " + keys + "; Status : " + status.getStatusCode().name() + "; Message : " + status.getMessage() + "; Elapsed Time - " + (System.currentTimeMillis() - rv.getStartTime()));
                rv.setStatus(status);
            }

            @Override
            public void gotData(String k, int flags, byte[] data) {
                if (data != null)  {
                    dataSizeDS.record(data.length);
                }
                m.put(k, tcService.decode(tc, new CachedData(flags, data, tc.getMaxSize())));
            }

            @Override
            public void complete() {
                assert op != null;

                rv.signalSingleOpComplete(thisOpId, op);
                if (pendingChunks.decrementAndGet() <= 0) {
                    latch.countDown();
                    getTimer(EVCacheMetricsFactory.BULK_OPERATION, EVCacheMetricsFactory.READ, rv.getStatus(), (m.size() == keys.size() ? EVCacheMetricsFactory.YES : EVCacheMetricsFactory.NO), null, getReadMetricMaxValue()).record((System.currentTimeMillis() - rv.getStartTime()), TimeUnit.MILLISECONDS);
                    rv.signalComplete();
                }
            }
        };

        // Now that we know how many servers it breaks down into, and the latch
        // is all set up, convert all of these strings collections to operations
        final Map mops = new HashMap();
        int thisOpId = 0;
        for (Map.Entry> me : chunks.entrySet()) {
            EVCacheBulkGetSingleFutureCallback cb = new EVCacheBulkGetSingleFutureCallback(thisOpId);
            GetOperation op = opFact.get(me.getValue(), cb);
            cb.bindOp(op);
            mops.put(me.getKey(), op);
            ops.add(op);
            thisOpId++;
        }
        assert mops.size() == chunks.size();
        mconn.checkState();
        mconn.addOperations(mops);
        return rv;
    }

    public  EVCacheOperationFuture> asyncGetAndTouch(final String key, final int exp, final Transcoder tc) {
        final CountDownLatch latch = new CountDownLatch(1);
        final EVCacheOperationFuture> rv = new EVCacheOperationFuture>(key, latch, new AtomicReference>(null), operationTimeout, executorService, client);
        Operation op = opFact.getAndTouch(key, exp, new GetAndTouchOperation.Callback() {
            private CASValue val = null;

            public void receivedStatus(OperationStatus status) {
                if (log.isDebugEnabled()) log.debug("GetAndTouch Key : " + key + "; Status : " + status.getStatusCode().name()
                        + (log.isTraceEnabled() ?  " Node : " + getEVCacheNode(key) : "")
                        + "; Message : " + status.getMessage() + "; Elapsed Time - " + (System.currentTimeMillis() - rv.getStartTime()));
                rv.set(val, status);
            }

            public void complete() {
                latch.countDown();
                final String host = ((rv.getStatus().getStatusCode().equals(StatusCode.TIMEDOUT) && rv.getOperation() != null) ? getHostName(rv.getOperation().getHandlingNode().getSocketAddress()) : null);
                getTimer(EVCacheMetricsFactory.GET_AND_TOUCH_OPERATION, EVCacheMetricsFactory.READ, rv.getStatus(), (val != null ? EVCacheMetricsFactory.YES : EVCacheMetricsFactory.NO), host, getReadMetricMaxValue()).record((System.currentTimeMillis() - rv.getStartTime()), TimeUnit.MILLISECONDS);
                rv.signalComplete();
            }

            public void gotData(String k, int flags, long cas, byte[] data) {
                if (isWrongKeyReturned(key, k)) return;

                if (data != null) getDataSizeDistributionSummary(EVCacheMetricsFactory.GET_AND_TOUCH_OPERATION, EVCacheMetricsFactory.READ, EVCacheMetricsFactory.IPC_SIZE_INBOUND).record(data.length);
                val = new CASValue(cas, tc.decode(new CachedData(flags, data, tc.getMaxSize())));
            }
        });
        rv.setOperation(op);
        mconn.enqueueOperation(key, op);
        return rv;
    }

    public  OperationFuture set(String key, int exp, T o, final Transcoder tc) {
        return asyncStore(StoreType.set, key, exp, o, tc, null);
    }

    public OperationFuture set(String key, int exp, Object o) {
        return asyncStore(StoreType.set, key, exp, o, transcoder, null);
    }

    @SuppressWarnings("unchecked")
    public  OperationFuture set(String key, int exp, T o, final Transcoder tc, EVCacheLatch latch) {
        Transcoder t = (Transcoder) ((tc == null) ? transcoder : tc);
        return asyncStore(StoreType.set, key, exp, o, t, latch);
    }

    @SuppressWarnings("unchecked")
    public  OperationFuture replace(String key, int exp, T o, final Transcoder tc, EVCacheLatch latch) {
        Transcoder t = (Transcoder) ((tc == null) ? transcoder : tc);
        return asyncStore(StoreType.replace, key, exp, o, t, latch);
    }

    public  OperationFuture add(String key, int exp, T o, Transcoder tc) {
        return asyncStore(StoreType.add, key, exp, o, tc, null);
    }

    public OperationFuture delete(String key, EVCacheLatch evcacheLatch) {
        final CountDownLatch latch = new CountDownLatch(1);
        final EVCacheOperationFuture rv = new EVCacheOperationFuture(key, latch, new AtomicReference(null), operationTimeout, executorService, client);
        final DeleteOperation op = opFact.delete(key, new DeleteOperation.Callback() {
            @Override
            public void receivedStatus(OperationStatus status) {
                rv.set(Boolean.TRUE, status);
            }

            @Override
            public void gotData(long cas) {
                rv.setCas(cas);
            }

            @Override
            public void complete() {
                latch.countDown();
                final String host = ((rv.getStatus().getStatusCode().equals(StatusCode.TIMEDOUT) && rv.getOperation() != null) ? getHostName(rv.getOperation().getHandlingNode().getSocketAddress()) : null);
                getTimer(EVCacheMetricsFactory.DELETE_OPERATION, EVCacheMetricsFactory.WRITE, rv.getStatus(), null, host, getWriteMetricMaxValue()).record((System.currentTimeMillis() - rv.getStartTime()), TimeUnit.MILLISECONDS);
                rv.signalComplete();
            }
        });

        rv.setOperation(op);
        if (evcacheLatch != null && evcacheLatch instanceof EVCacheLatchImpl && !client.isInWriteOnly()) ((EVCacheLatchImpl) evcacheLatch).addFuture(rv);
        mconn.enqueueOperation(key, op);
        return rv;
    }

    public  OperationFuture touch(final String key, final int exp, EVCacheLatch evcacheLatch) {
        final CountDownLatch latch = new CountDownLatch(1);
        final EVCacheOperationFuture rv = new EVCacheOperationFuture(key, latch, new AtomicReference(null), operationTimeout, executorService, client);
        final Operation op = opFact.touch(key, exp, new OperationCallback() {
            @Override
            public void receivedStatus(OperationStatus status) {
                rv.set(status.isSuccess(), status);
            }

            @Override
            public void complete() {
                latch.countDown();
                final String host = ((rv.getStatus().getStatusCode().equals(StatusCode.TIMEDOUT) && rv.getOperation() != null) ? getHostName(rv.getOperation().getHandlingNode().getSocketAddress()) : null);
                getTimer(EVCacheMetricsFactory.TOUCH_OPERATION, EVCacheMetricsFactory.WRITE, rv.getStatus(), null, host, getWriteMetricMaxValue()).record((System.currentTimeMillis() - rv.getStartTime()), TimeUnit.MILLISECONDS);
                rv.signalComplete();
            }
        });
        rv.setOperation(op);
        if (evcacheLatch != null && evcacheLatch instanceof EVCacheLatchImpl && !client.isInWriteOnly()) ((EVCacheLatchImpl) evcacheLatch).addFuture(rv);
        mconn.enqueueOperation(key, op);
        return rv;
    }


    public  OperationFuture asyncAppendOrAdd(final String key, int exp, CachedData co, EVCacheLatch evcacheLatch) {
        final CountDownLatch latch = new CountDownLatch(1);
        if(co != null && co.getData() != null) getDataSizeDistributionSummary(EVCacheMetricsFactory.AOA_OPERATION, EVCacheMetricsFactory.WRITE, EVCacheMetricsFactory.IPC_SIZE_OUTBOUND).record(co.getData().length);
        final EVCacheOperationFuture rv = new EVCacheOperationFuture(key, latch, new AtomicReference(null), operationTimeout, executorService, client);
        final Operation opAppend = opFact.cat(ConcatenationType.append, 0, key, co.getData(), new OperationCallback() {
            boolean appendSuccess = false;
            @Override
            public void receivedStatus(OperationStatus val) {
                if (log.isDebugEnabled() && client.getPool().getEVCacheClientPoolManager().shouldLog(appName)) log.debug("AddOrAppend Key (Append Operation): " + key + "; Status : " + val.getStatusCode().name()
                        + "; Message : " + val.getMessage() + "; Elapsed Time - " + (System.currentTimeMillis() - rv.getStartTime()));
                if (val.getStatusCode().equals(StatusCode.SUCCESS)) {
                    rv.set(Boolean.TRUE, val);
                    appendSuccess = true;

                }
            }

            @Override
            public void complete() {
                if(appendSuccess)  {
                    final String host = ((rv.getStatus().getStatusCode().equals(StatusCode.TIMEDOUT) && rv.getOperation() != null) ? getHostName(rv.getOperation().getHandlingNode().getSocketAddress()) : null);
                    getTimer(EVCacheMetricsFactory.AOA_OPERATION_APPEND, EVCacheMetricsFactory.WRITE, rv.getStatus(), EVCacheMetricsFactory.YES, host, getWriteMetricMaxValue()).record((System.currentTimeMillis() - rv.getStartTime()), TimeUnit.MILLISECONDS);;
                    latch.countDown();
                    rv.signalComplete();
                } else {
                    Operation opAdd = opFact.store(StoreType.add, key, co.getFlags(), exp, co.getData(), new StoreOperation.Callback() {
                        @Override
                        public void receivedStatus(OperationStatus addStatus) {
                            if (log.isDebugEnabled() && client.getPool().getEVCacheClientPoolManager().shouldLog(appName)) log.debug("AddOrAppend Key (Add Operation): " + key + "; Status : " + addStatus.getStatusCode().name()
                                    + "; Message : " + addStatus.getMessage() + "; Elapsed Time - " + (System.currentTimeMillis() - rv.getStartTime()));
                            if(addStatus.isSuccess()) {
                                appendSuccess = true;
                                rv.set(addStatus.isSuccess(), addStatus);
                            } else {
                                Operation opReappend = opFact.cat(ConcatenationType.append, 0, key, co.getData(), new OperationCallback() {
                                    public void receivedStatus(OperationStatus retryAppendStatus) {
                                        if (retryAppendStatus.getStatusCode().equals(StatusCode.SUCCESS)) {
                                            rv.set(Boolean.TRUE, retryAppendStatus);
                                            if (log.isDebugEnabled()) log.debug("AddOrAppend Retry append Key (Append Operation): " + key + "; Status : " + retryAppendStatus.getStatusCode().name()
                                                    + "; Message : " + retryAppendStatus.getMessage() + "; Elapsed Time - " + (System.currentTimeMillis() - rv.getStartTime()));
                                        } else {
                                            rv.set(Boolean.FALSE, retryAppendStatus);
                                        }
                                    }
                                    public void complete() {
                                        final String host = ((rv.getStatus().getStatusCode().equals(StatusCode.TIMEDOUT) && rv.getOperation() != null) ? getHostName(rv.getOperation().getHandlingNode().getSocketAddress()) : null);
                                        getTimer(EVCacheMetricsFactory.AOA_OPERATION_REAPPEND, EVCacheMetricsFactory.WRITE, rv.getStatus(), EVCacheMetricsFactory.YES, host, getWriteMetricMaxValue()).record((System.currentTimeMillis() - rv.getStartTime()), TimeUnit.MILLISECONDS);
                                        latch.countDown();
                                        rv.signalComplete();
                                    }
                                });
                                rv.setOperation(opReappend);
                                mconn.enqueueOperation(key, opReappend);
                            }
                        }

                        @Override
                        public void gotData(String key, long cas) {
                            rv.setCas(cas);
                        }

                        @Override
                        public void complete() {
                            if(appendSuccess) {
                                final String host = ((rv.getStatus().getStatusCode().equals(StatusCode.TIMEDOUT) && rv.getOperation() != null) ? getHostName(rv.getOperation().getHandlingNode().getSocketAddress()) : null);
                                getTimer(EVCacheMetricsFactory.AOA_OPERATION_ADD, EVCacheMetricsFactory.WRITE, rv.getStatus(), EVCacheMetricsFactory.YES, host, getWriteMetricMaxValue()).record((System.currentTimeMillis() - rv.getStartTime()), TimeUnit.MILLISECONDS);
                                latch.countDown();
                                rv.signalComplete();
                            }
                        }
                    });
                    rv.setOperation(opAdd);
                    mconn.enqueueOperation(key, opAdd);
                }
            }
        });
        rv.setOperation(opAppend);
        mconn.enqueueOperation(key, opAppend);
        if (evcacheLatch != null && evcacheLatch instanceof EVCacheLatchImpl && !client.isInWriteOnly()) ((EVCacheLatchImpl) evcacheLatch).addFuture(rv);
        return rv;
    }

    private Timer getTimer(String operation, String operationType, OperationStatus status, String hit, String host, long maxDuration) {
        String name = ((status != null) ? operation + status.getMessage() : operation );
        if(hit != null) name = name + hit;

        Timer timer = timerMap.get(name);
        if(timer != null) return timer;

        final List tagList = new ArrayList(client.getTagList().size() + 4 + (host == null ? 0 : 1));
        tagList.addAll(client.getTagList());
        if(operation != null) tagList.add(new BasicTag(EVCacheMetricsFactory.CALL_TAG, operation));
        if(operationType != null) tagList.add(new BasicTag(EVCacheMetricsFactory.CALL_TYPE_TAG, operationType));
        if(status != null) {
            if(status.getStatusCode() == StatusCode.SUCCESS || status.getStatusCode() == StatusCode.ERR_NOT_FOUND || status.getStatusCode() == StatusCode.ERR_EXISTS) {
                tagList.add(new BasicTag(EVCacheMetricsFactory.IPC_RESULT, EVCacheMetricsFactory.SUCCESS));
            } else {
                tagList.add(new BasicTag(EVCacheMetricsFactory.IPC_RESULT, EVCacheMetricsFactory.FAIL));
            }
            tagList.add(new BasicTag(EVCacheMetricsFactory.IPC_STATUS, getStatusCode(status.getStatusCode())));
        }
        if(hit != null) tagList.add(new BasicTag(EVCacheMetricsFactory.CACHE_HIT, hit));
        if(host != null) tagList.add(new BasicTag(EVCacheMetricsFactory.FAILED_HOST, host));

        timer = EVCacheMetricsFactory.getInstance().getPercentileTimer(EVCacheMetricsFactory.IPC_CALL, tagList, Duration.ofMillis(maxDuration));
        timerMap.put(name, timer);
        return timer;
    }
    
    private String getStatusCode(StatusCode sc) {
        return EVCacheMetricsFactory.getInstance().getStatusCode(sc);
    }

    private DistributionSummary getDataSizeDistributionSummary(String operation, String type, String metric) {
        DistributionSummary distributionSummary = distributionSummaryMap.get(operation);
        if(distributionSummary != null) return distributionSummary;

        final List tagList = new ArrayList(6);
        tagList.addAll(client.getTagList());
        tagList.add(new BasicTag(EVCacheMetricsFactory.CALL_TAG, operation));
        tagList.add(new BasicTag(EVCacheMetricsFactory.CALL_TYPE_TAG, type));
        distributionSummary = EVCacheMetricsFactory.getInstance().getDistributionSummary(metric, tagList);
        distributionSummaryMap.put(operation, distributionSummary);
        return distributionSummary;
    }

    private  OperationFuture asyncStore(final StoreType storeType, final String key, int exp, T value, Transcoder tc, EVCacheLatch evcacheLatch) {
        final CachedData co;
        if (value instanceof CachedData) {
            co = (CachedData) value;
        } else {
            co = tc.encode(value);
        }
        final CountDownLatch latch = new CountDownLatch(1);
        final String operationStr;
        if (storeType == StoreType.set) {
            operationStr = EVCacheMetricsFactory.SET_OPERATION;
        } else if (storeType == StoreType.add) {
            operationStr = EVCacheMetricsFactory.ADD_OPERATION;
        } else {
            operationStr = EVCacheMetricsFactory.REPLACE_OPERATION;
        }
        if(co != null && co.getData() != null) getDataSizeDistributionSummary(operationStr, EVCacheMetricsFactory.WRITE, EVCacheMetricsFactory.IPC_SIZE_OUTBOUND).record(co.getData().length);

        final EVCacheOperationFuture rv = new EVCacheOperationFuture(key, latch, new AtomicReference(null), operationTimeout, executorService, client);
        final Operation op = opFact.store(storeType, key, co.getFlags(), exp, co.getData(), new StoreOperation.Callback() {
            @Override
            public void receivedStatus(OperationStatus val) {
                if (log.isDebugEnabled()) log.debug("Storing Key : " + key + "; Status : " + val.getStatusCode().name() + (log.isTraceEnabled() ?  " Node : " + getEVCacheNode(key) : "") + "; Message : " + val.getMessage()
                + "; Elapsed Time - " + (System.currentTimeMillis() - rv.getStartTime()));
                rv.set(val.isSuccess(), val);
                if (log.isTraceEnabled() && !val.getStatusCode().equals(StatusCode.SUCCESS)) log.trace(val.getStatusCode().name() + " storing Key : " + key , new Exception());
            }

            @Override
            public void gotData(String key, long cas) {
                rv.setCas(cas);
            }

            @Override
            public void complete() {
                latch.countDown();
                final String host = (((rv.getStatus().getStatusCode().equals(StatusCode.TIMEDOUT) || rv.getStatus().getStatusCode().equals(StatusCode.ERR_NO_MEM)) && rv.getOperation() != null) ? getHostName(rv.getOperation().getHandlingNode().getSocketAddress()) : null);
                getTimer(operationStr, EVCacheMetricsFactory.WRITE, rv.getStatus(), null, host, getWriteMetricMaxValue()).record((System.currentTimeMillis() - rv.getStartTime()), TimeUnit.MILLISECONDS);
                rv.signalComplete();
            }
        });
        rv.setOperation(op);
        if (evcacheLatch != null && evcacheLatch instanceof EVCacheLatchImpl && !client.isInWriteOnly()) ((EVCacheLatchImpl) evcacheLatch).addFuture(rv);
        mconn.enqueueOperation(key, op);
        return rv;
    }

    public String toString() {
        return appName + "-" + client.getZone() + "-" + client.getId();
    }

    @SuppressWarnings("unchecked")
    public  OperationFuture add(String key, int exp, T o, final Transcoder tc, EVCacheLatch latch) {
        Transcoder t = (Transcoder) ((tc == null) ? transcoder : tc);
        return asyncStore(StoreType.add, key, exp, o, t, latch);
    }

    public long incr(String key, long by, long def, int exp) {
        return mutate(Mutator.incr, key, by, def, exp);
    }


    public long decr(String key, long by, long def, int exp) {
        return mutate(Mutator.decr, key, by, def, exp);
    }

    public long mutate(final Mutator m, String key, long by, long def, int exp) {
        final String operationStr = m.name();
        final long start = System.currentTimeMillis();
        final AtomicLong rv = new AtomicLong();
        final CountDownLatch latch = new CountDownLatch(1);
        final List statusList = new ArrayList(1);
        final Operation op = opFact.mutate(m, key, by, def, exp, new OperationCallback() {
            @Override
            public void receivedStatus(OperationStatus s) {
                statusList.add(s);
                rv.set(new Long(s.isSuccess() ? s.getMessage() : "-1"));
            }

            @Override
            public void complete() {
                latch.countDown();
            }
        });
        mconn.enqueueOperation(key, op);
        long retVal = def;
        try {
            if(mutateOperationTimeout == null) {
                mutateOperationTimeout = EVCacheConfig.getInstance().getPropertyRepository().get(appName + ".mutate.timeout", Long.class).orElse(connectionFactory.getOperationTimeout());
            }


            if (!latch.await(mutateOperationTimeout.get(), TimeUnit.MILLISECONDS)) {
                if (log.isDebugEnabled()) log.debug("Mutation operation timeout. Will return -1");
                retVal = -1;
            } else {
                retVal = rv.get();
            }

        } catch (Exception e) {
            log.error("Exception on mutate operation : " + operationStr + " Key : " + key + "; by : " + by + "; default : " + def + "; exp : " + exp
                    + "; val : " + retVal + "; Elapsed Time - " + (System.currentTimeMillis() - start), e);

        }
        final OperationStatus status = statusList.size() > 0 ? statusList.get(0) : null;
        final String host = ((status != null && status.getStatusCode().equals(StatusCode.TIMEDOUT) && op != null) ? getHostName(op.getHandlingNode().getSocketAddress()) : null);
        getTimer(operationStr, EVCacheMetricsFactory.WRITE, status, null, host, getWriteMetricMaxValue()).record((System.currentTimeMillis() - start), TimeUnit.MILLISECONDS);
        if (log.isDebugEnabled() && client.getPool().getEVCacheClientPoolManager().shouldLog(appName)) log.debug(operationStr + " Key : " + key + "; by : " + by + "; default : " + def + "; exp : " + exp
                + "; val : " + retVal + "; Elapsed Time - " + (System.currentTimeMillis() - start));
        return retVal;
    }

    public void reconnectNode(EVCacheNode evcNode ) {
        final long upTime = System.currentTimeMillis() - evcNode.getCreateTime();
        if (log.isDebugEnabled()) log.debug("Reconnecting node : " + evcNode + "; UpTime : " + upTime);
        if(upTime > 30000) { //not more than once every 30 seconds : TODO make this configurable
            final List tagList = new ArrayList(client.getTagList().size() + 2);
            tagList.addAll(client.getTagList());
            tagList.add(new BasicTag(EVCacheMetricsFactory.CONFIG_NAME, EVCacheMetricsFactory.RECONNECT));
            tagList.add(new BasicTag(EVCacheMetricsFactory.FAILED_HOST, evcNode.getHostName()));
            EVCacheMetricsFactory.getInstance().increment(EVCacheMetricsFactory.CONFIG, tagList);
            evcNode.setConnectTime(System.currentTimeMillis());
            mconn.queueReconnect(evcNode);
        }
    }

    public int getWriteMetricMaxValue() {
        return maxWriteDuration.get().intValue();
    }

    public int getReadMetricMaxValue() {
        return maxReadDuration.get().intValue();
    }

    private String getHostNameByKey(String key) {
        MemcachedNode evcNode = getEVCacheNode(key);
        return getHostName(evcNode.getSocketAddress());
    }

    private String getHostName(SocketAddress sa) {
        if (sa == null) return null;
        if(sa instanceof InetSocketAddress) {
            return ((InetSocketAddress)sa).getHostName();
        } else {
            return sa.toString();
        }
    }

    public EVCacheItemMetaData metaDebug(String key) {
        final CountDownLatch latch = new CountDownLatch(1);
        final EVCacheItemMetaData rv = new EVCacheItemMetaData();
        if(opFact instanceof EVCacheAsciiOperationFactory) {
        final Operation op = ((EVCacheAsciiOperationFactory)opFact).metaDebug(key, new MetaDebugOperation.Callback() {
            public void receivedStatus(OperationStatus status) {
                if (!status.isSuccess()) {
                    if (log.isDebugEnabled()) log.debug("Unsuccessful stat fetch: %s", status);
                  }
                if (log.isDebugEnabled()) log.debug("Getting Meta Debug: " + key + "; Status : " + status.getStatusCode().name() + (log.isTraceEnabled() ?  " Node : " + getEVCacheNode(key) : "") + "; Message : " + status.getMessage());
            }

            public void debugInfo(String k, String val) {
                if (log.isDebugEnabled()) log.debug("key " + k + "; val : " + val);
                if(k.equals("exp")) rv.setSecondsLeftToExpire(Long.parseLong(val) * -1);
                else if(k.equals("la")) rv.setSecondsSinceLastAccess(Long.parseLong(val));
                else if(k.equals("cas")) rv.setCas(Long.parseLong(val));
                else if(k.equals("fetch")) rv.setHasBeenFetchedAfterWrite(Boolean.parseBoolean(val));
                else if(k.equals("cls")) rv.setSlabClass(Integer.parseInt(val));
                else if(k.equals("size")) rv.setSizeInBytes(Integer.parseInt(val));
            }

            public void complete() {
                latch.countDown();
            }});
            mconn.enqueueOperation(key, op);
            try {
                if (!latch.await(operationTimeout, TimeUnit.MILLISECONDS)) {
                    if (log.isDebugEnabled()) log.debug("meta debug operation timeout. Will return empty opbject.");
                }
            } catch (Exception e) {
                log.error("Exception on meta debug operation : Key : " + key, e);
            }
            if (log.isDebugEnabled()) log.debug("Meta Debug Data : " + rv);
        }
        return rv;
    }

    public Map execCmd(final String cmd, String[] ips) {
        final Map rv = new HashMap();
        Collection nodes = null;
        if(ips == null || ips.length == 0) {
            nodes = mconn.getLocator().getAll();
        } else {
            nodes = new ArrayList(ips.length);
            for(String ip : ips) {
                for(MemcachedNode node : mconn.getLocator().getAll()) {
                    if(((InetSocketAddress)node.getSocketAddress()).getAddress().getHostAddress().equals(ip)) {
                        nodes.add(node);
                    }
                }
            }
        }
        if(nodes != null && !nodes.isEmpty()) {
            CountDownLatch blatch = broadcastOp(new BroadcastOpFactory() {
                @Override
                public Operation newOp(final MemcachedNode n, final CountDownLatch latch) {
                    final SocketAddress sa = n.getSocketAddress();
                    return ((EVCacheAsciiOperationFactory)opFact).execCmd(cmd, new ExecCmdOperation.Callback() {
    
                        @Override
                        public void receivedStatus(OperationStatus status) {
                            if (log.isDebugEnabled()) log.debug("cmd : " + cmd + "; MemcachedNode : " + n + "; Status : " + status);
                            rv.put(sa, status.getMessage());
                        }
    
                        @Override
                        public void complete() {
                            latch.countDown();
                        }
                    });
                }
            }, nodes);
            try {
                blatch.await(operationTimeout, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                throw new RuntimeException("Interrupted waiting for stats", e);
            }
        }
        return rv;
    }

    public  EVCacheOperationFuture> asyncMetaGet(final String key, final Transcoder tc, EVCacheGetOperationListener listener) {
        final CountDownLatch latch = new CountDownLatch(1);

        final EVCacheOperationFuture> rv = new EVCacheOperationFuture>(key, latch, new AtomicReference>(null), readTimeout.get().intValue(), executorService, client);
        if(opFact instanceof EVCacheAsciiOperationFactory) {
        final Operation op = ((EVCacheAsciiOperationFactory)opFact).metaGet(key, new MetaGetOperation.Callback() {

            private EVCacheItem evItem = new EVCacheItem();

            public void receivedStatus(OperationStatus status) {
                if (log.isDebugEnabled()) log.debug("Getting Key : " + key + "; Status : " + status.getStatusCode().name() + (log.isTraceEnabled() ?  " Node : " + getEVCacheNode(key) : "")
                        + "; Message : " + status.getMessage() + "; Elapsed Time - " + (System.currentTimeMillis() - rv.getStartTime()));
                try {
                    if (evItem.getData() != null) {
                        if (log.isTraceEnabled() && client.getPool().getEVCacheClientPoolManager().shouldLog(appName)) log.trace("Key : " + key + "; val : " + evItem);
                        rv.set(evItem, status);
                    } else {
                        if (log.isTraceEnabled() && client.getPool().getEVCacheClientPoolManager().shouldLog(appName)) log.trace("Key : " + key + "; val is null");
                        rv.set(null, status);
                    }
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                    rv.set(null, status);
                }
            }

            @Override
            public void gotMetaData(String k, char flag, String fVal) {
                if (log.isDebugEnabled()) log.debug("key " + k + "; val : " + fVal + "; flag : " + flag);
                if (isWrongKeyReturned(key, k)) return;

                switch (flag) {
                case 's':
                    evItem.getItemMetaData().setSizeInBytes(Integer.parseInt(fVal));
                    break;

                case 'c':
                    evItem.getItemMetaData().setCas(Long.parseLong(fVal));
                    break;

                case 'f':
                    evItem.setFlag(Integer.parseInt(fVal));
                    break;

                case 'h':
                    evItem.getItemMetaData().setHasBeenFetchedAfterWrite(fVal.equals("1"));
                    break;

                case 'l':
                    evItem.getItemMetaData().setSecondsSinceLastAccess(Long.parseLong(fVal));
                    break;

                case 'O':
                    //opaque = val;
                    break;

                case 't':
                    final int ttlLeft = Integer.parseInt(fVal);
                    evItem.getItemMetaData().setSecondsLeftToExpire(ttlLeft);
                    getDataSizeDistributionSummary(EVCacheMetricsFactory.META_GET_OPERATION, EVCacheMetricsFactory.READ, EVCacheMetricsFactory.INTERNAL_TTL).record(ttlLeft);
                    break;

                default:
                    break;
                }
            }

            @Override
            public void gotData(String k, int flag, byte[] data) {
                if (log.isDebugEnabled() && client.getPool().getEVCacheClientPoolManager().shouldLog(appName)) log.debug("Read data : key " + k + "; flags : " + flag + "; data : " + data);
                if (isWrongKeyReturned(key, k)) return;

                if (data != null)  {
                    if (log.isDebugEnabled() && client.getPool().getEVCacheClientPoolManager().shouldLog(appName)) log.debug("Key : " + k + "; val size : " + data.length);
                    getDataSizeDistributionSummary(EVCacheMetricsFactory.META_GET_OPERATION, EVCacheMetricsFactory.READ, EVCacheMetricsFactory.IPC_SIZE_INBOUND).record(data.length);
                    if (tc == null) {
                        if (tcService == null) {
                            log.error("tcService is null, will not be able to decode");
                            throw new RuntimeException("TranscoderSevice is null. Not able to decode");
                        } else {
                            final Transcoder t = (Transcoder) getTranscoder();
                            final T item = t.decode(new CachedData(flag, data, t.getMaxSize()));
                            evItem.setData(item);
                        }
                    } else {
                        if (tcService == null) {
                            log.error("tcService is null, will not be able to decode");
                            throw new RuntimeException("TranscoderSevice is null. Not able to decode");
                        } else {
                            final T item = tc.decode(new CachedData(flag, data, tc.getMaxSize()));
                            evItem.setData(item);
                        }
                    }
                } else {
                    if (log.isDebugEnabled() && client.getPool().getEVCacheClientPoolManager().shouldLog(appName)) log.debug("Key : " + k + "; val is null" );
                }
            }

            public void complete() {
                latch.countDown();
                final String host = ((rv.getStatus().getStatusCode().equals(StatusCode.TIMEDOUT) && rv.getOperation() != null) ? getHostName(rv.getOperation().getHandlingNode().getSocketAddress()) : null);
                getTimer(EVCacheMetricsFactory.META_GET_OPERATION, EVCacheMetricsFactory.READ, rv.getStatus(), (evItem.getData() != null ? EVCacheMetricsFactory.YES : EVCacheMetricsFactory.NO), host, getReadMetricMaxValue()).record((System.currentTimeMillis() - rv.getStartTime()), TimeUnit.MILLISECONDS);
                rv.signalComplete();
            }

            });
            rv.setOperation(op);
            mconn.enqueueOperation(key, op);
            if (log.isDebugEnabled()) log.debug("Meta_Get Data : " + rv);
        }
        return rv;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy