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

com.hazelcast.client.proxy.ClientPNCounterProxy Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2008-2020, Hazelcast, Inc. 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.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hazelcast.client.proxy;

import com.hazelcast.client.impl.protocol.ClientMessage;
import com.hazelcast.client.impl.protocol.codec.PNCounterAddCodec;
import com.hazelcast.client.impl.protocol.codec.PNCounterGetCodec;
import com.hazelcast.client.impl.protocol.codec.PNCounterGetConfiguredReplicaCountCodec;
import com.hazelcast.client.spi.ClientContext;
import com.hazelcast.client.spi.ClientProxy;
import com.hazelcast.cluster.impl.VectorClock;
import com.hazelcast.cluster.memberselector.MemberSelectors;
import com.hazelcast.core.HazelcastException;
import com.hazelcast.core.Member;
import com.hazelcast.crdt.pncounter.PNCounter;
import com.hazelcast.internal.util.ThreadLocalRandomProvider;
import com.hazelcast.logging.ILogger;
import com.hazelcast.nio.Address;
import com.hazelcast.partition.NoDataMemberInClusterException;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

/**
 * Client proxy implementation for a {@link PNCounter}.
 */
public class ClientPNCounterProxy extends ClientProxy implements PNCounter {

    /**
     * Atomic field updater for the observed clock field
     */
    private static final AtomicReferenceFieldUpdater OBSERVED_TIMESTAMPS_UPDATER =
            AtomicReferenceFieldUpdater.newUpdater(ClientPNCounterProxy.class, VectorClock.class, "observedClock");
    private static final List
EMPTY_ADDRESS_LIST = Collections.emptyList(); private final ILogger logger; private volatile Address currentTargetReplicaAddress; private final Object targetSelectionMutex = new Object(); private volatile int maxConfiguredReplicaCount; /** * The last vector clock observed by this proxy. It is used for maintaining * session consistency guarantees when reading from different replicas. */ private volatile VectorClock observedClock; /** * Creates a client {@link PNCounter} proxy * * @param serviceName the service name * @param objectName the PNCounter name * @param context the client context containing references to services * and configuration */ public ClientPNCounterProxy(String serviceName, String objectName, ClientContext context) { super(serviceName, objectName, context); this.logger = getContext().getLoggingService().getLogger(ClientPNCounterProxy.class); this.observedClock = new VectorClock(); } @Override public String toString() { return "PNCounter{name='" + name + "\'}"; } @Override public long get() { final Address target = getCRDTOperationTarget(EMPTY_ADDRESS_LIST); if (target == null) { throw new NoDataMemberInClusterException( "Cannot invoke operations on a CRDT because the cluster does not contain any data members"); } final ClientMessage response = invokeGetInternal(EMPTY_ADDRESS_LIST, null, target); final PNCounterGetCodec.ResponseParameters resultParameters = PNCounterGetCodec.decodeResponse(response); updateObservedReplicaTimestamps(resultParameters.replicaTimestamps); return resultParameters.value; } @Override public long getAndAdd(long delta) { final Address target = getCRDTOperationTarget(EMPTY_ADDRESS_LIST); if (target == null) { throw new NoDataMemberInClusterException( "Cannot invoke operations on a CRDT because the cluster does not contain any data members"); } final ClientMessage response = invokeAddInternal(delta, true, EMPTY_ADDRESS_LIST, null, target); final PNCounterAddCodec.ResponseParameters resultParameters = PNCounterAddCodec.decodeResponse(response); updateObservedReplicaTimestamps(resultParameters.replicaTimestamps); return resultParameters.value; } @Override public long addAndGet(long delta) { final Address target = getCRDTOperationTarget(EMPTY_ADDRESS_LIST); if (target == null) { throw new NoDataMemberInClusterException( "Cannot invoke operations on a CRDT because the cluster does not contain any data members"); } final ClientMessage response = invokeAddInternal(delta, false, EMPTY_ADDRESS_LIST, null, target); final PNCounterAddCodec.ResponseParameters resultParameters = PNCounterAddCodec.decodeResponse(response); updateObservedReplicaTimestamps(resultParameters.replicaTimestamps); return resultParameters.value; } @Override public long getAndSubtract(long delta) { final Address target = getCRDTOperationTarget(EMPTY_ADDRESS_LIST); if (target == null) { throw new NoDataMemberInClusterException( "Cannot invoke operations on a CRDT because the cluster does not contain any data members"); } final ClientMessage response = invokeAddInternal(-delta, true, EMPTY_ADDRESS_LIST, null, target); final PNCounterAddCodec.ResponseParameters resultParameters = PNCounterAddCodec.decodeResponse(response); updateObservedReplicaTimestamps(resultParameters.replicaTimestamps); return resultParameters.value; } @Override public long subtractAndGet(long delta) { final Address target = getCRDTOperationTarget(EMPTY_ADDRESS_LIST); if (target == null) { throw new NoDataMemberInClusterException( "Cannot invoke operations on a CRDT because the cluster does not contain any data members"); } final ClientMessage response = invokeAddInternal(-delta, false, EMPTY_ADDRESS_LIST, null, target); final PNCounterAddCodec.ResponseParameters resultParameters = PNCounterAddCodec.decodeResponse(response); updateObservedReplicaTimestamps(resultParameters.replicaTimestamps); return resultParameters.value; } @Override public long decrementAndGet() { final Address target = getCRDTOperationTarget(EMPTY_ADDRESS_LIST); if (target == null) { throw new NoDataMemberInClusterException( "Cannot invoke operations on a CRDT because the cluster does not contain any data members"); } final ClientMessage response = invokeAddInternal(-1, false, EMPTY_ADDRESS_LIST, null, target); final PNCounterAddCodec.ResponseParameters resultParameters = PNCounterAddCodec.decodeResponse(response); updateObservedReplicaTimestamps(resultParameters.replicaTimestamps); return resultParameters.value; } @Override public long incrementAndGet() { final Address target = getCRDTOperationTarget(EMPTY_ADDRESS_LIST); if (target == null) { throw new NoDataMemberInClusterException( "Cannot invoke operations on a CRDT because the cluster does not contain any data members"); } final ClientMessage response = invokeAddInternal(1, false, EMPTY_ADDRESS_LIST, null, target); final PNCounterAddCodec.ResponseParameters resultParameters = PNCounterAddCodec.decodeResponse(response); updateObservedReplicaTimestamps(resultParameters.replicaTimestamps); return resultParameters.value; } @Override public long getAndDecrement() { final Address target = getCRDTOperationTarget(EMPTY_ADDRESS_LIST); if (target == null) { throw new NoDataMemberInClusterException( "Cannot invoke operations on a CRDT because the cluster does not contain any data members"); } final ClientMessage response = invokeAddInternal(-1, true, EMPTY_ADDRESS_LIST, null, target); final PNCounterAddCodec.ResponseParameters resultParameters = PNCounterAddCodec.decodeResponse(response); updateObservedReplicaTimestamps(resultParameters.replicaTimestamps); return resultParameters.value; } @Override public long getAndIncrement() { final Address target = getCRDTOperationTarget(EMPTY_ADDRESS_LIST); if (target == null) { throw new NoDataMemberInClusterException( "Cannot invoke operations on a CRDT because the cluster does not contain any data members"); } final ClientMessage response = invokeAddInternal(1, true, EMPTY_ADDRESS_LIST, null, target); final PNCounterAddCodec.ResponseParameters resultParameters = PNCounterAddCodec.decodeResponse(response); updateObservedReplicaTimestamps(resultParameters.replicaTimestamps); return resultParameters.value; } @Override public void reset() { this.observedClock = new VectorClock(); } /** * Transforms the list of replica logical timestamps to a vector clock instance. * * @param replicaLogicalTimestamps the logical timestamps * @return a vector clock instance */ private VectorClock toVectorClock(List> replicaLogicalTimestamps) { final VectorClock timestamps = new VectorClock(); for (Entry replicaTimestamp : replicaLogicalTimestamps) { timestamps.setReplicaTimestamp(replicaTimestamp.getKey(), replicaTimestamp.getValue()); } return timestamps; } /** * Adds the {@code delta} and returns the value of the counter before the * update if {@code getBeforeUpdate} is {@code true} or the value after * the update if it is {@code false}. * It will invoke client messages recursively on viable replica addresses * until successful or the list of viable replicas is exhausted. * Replicas with addresses contained in the {@code excludedAddresses} are * skipped. If there are no viable replicas, this method will throw the * {@code lastException} if not {@code null} or a * {@link NoDataMemberInClusterException} if the {@code lastException} is * {@code null}. * * @param delta the delta to add to the counter value, can be negative * @param getBeforeUpdate {@code true} if the operation should return the * counter value before the addition, {@code false} * if it should return the value after the addition * @param excludedAddresses the addresses to exclude when choosing a replica * address, must not be {@code null} * @param lastException the exception thrown from the last invocation of * the {@code request} on a replica, may be {@code null} * @return the result of the request invocation on a replica * @throws NoDataMemberInClusterException if there are no replicas and the * {@code lastException} is {@code null} */ private ClientMessage invokeAddInternal(long delta, boolean getBeforeUpdate, List
excludedAddresses, HazelcastException lastException, Address target) { if (target == null) { throw lastException != null ? lastException : new NoDataMemberInClusterException( "Cannot invoke operations on a CRDT because the cluster does not contain any data members"); } try { final ClientMessage request = PNCounterAddCodec.encodeRequest( name, delta, getBeforeUpdate, observedClock.entrySet(), target); return invokeOnAddress(request, target); } catch (HazelcastException e) { logger.fine("Unable to provide session guarantees when sending operations to " + target + ", choosing different target"); if (excludedAddresses == EMPTY_ADDRESS_LIST) { excludedAddresses = new ArrayList
(); } excludedAddresses.add(target); final Address newTarget = getCRDTOperationTarget(excludedAddresses); return invokeAddInternal(delta, getBeforeUpdate, excludedAddresses, e, newTarget); } } /** * Returns the current value of the counter. * It will invoke client messages recursively on viable replica addresses * until successful or the list of viable replicas is exhausted. * Replicas with addresses contained in the {@code excludedAddresses} are * skipped. If there are no viable replicas, this method will throw the * {@code lastException} if not {@code null} or a * {@link NoDataMemberInClusterException} if the {@code lastException} is * {@code null}. * * @param excludedAddresses the addresses to exclude when choosing a replica * address, must not be {@code null} * @param lastException the exception thrown from the last invocation of * the {@code request} on a replica, may be {@code null} * @return the result of the request invocation on a replica * @throws NoDataMemberInClusterException if there are no replicas and the * {@code lastException} is false */ private ClientMessage invokeGetInternal(List
excludedAddresses, HazelcastException lastException, Address target) { if (target == null) { throw lastException != null ? lastException : new NoDataMemberInClusterException( "Cannot invoke operations on a CRDT because the cluster does not contain any data members"); } try { final ClientMessage request = PNCounterGetCodec.encodeRequest(name, observedClock.entrySet(), target); return invokeOnAddress(request, target); } catch (HazelcastException e) { logger.fine("Exception occurred while invoking operation on target " + target + ", choosing different target", e); if (excludedAddresses == EMPTY_ADDRESS_LIST) { excludedAddresses = new ArrayList
(); } excludedAddresses.add(target); final Address newTarget = getCRDTOperationTarget(excludedAddresses); return invokeGetInternal(excludedAddresses, e, newTarget); } } /** * Returns the target on which this proxy should invoke a CRDT operation. * On first invocation of this method, the method will choose a target * address and return that address on future invocations. Replicas with * addresses contained in the {@code excludedAddresses} list are excluded * and if the chosen replica is in this list, a new replica is chosen and * returned on future invocations. * The method may return {@code null} if there are no viable target addresses. * * @param excludedAddresses the addresses to exclude when choosing a replica * address, must not be {@code null} * @return a CRDT replica address or {@code null} if there are no viable * addresses */ private Address getCRDTOperationTarget(Collection
excludedAddresses) { if (currentTargetReplicaAddress != null && !excludedAddresses.contains(currentTargetReplicaAddress)) { return currentTargetReplicaAddress; } synchronized (targetSelectionMutex) { if (currentTargetReplicaAddress == null || excludedAddresses.contains(currentTargetReplicaAddress)) { currentTargetReplicaAddress = chooseTargetReplica(excludedAddresses); } } return currentTargetReplicaAddress; } /** * Chooses and returns a CRDT replica address. Replicas with addresses * contained in the {@code excludedAddresses} list are excluded and the * method chooses randomly between the collection of viable target addresses. *

* The method may return {@code null} if there are no viable addresses. * * @param excludedAddresses the addresses to exclude when choosing a replica * address, must not be {@code null} * @return a CRDT replica address or {@code null} if there are no viable addresses */ private Address chooseTargetReplica(Collection

excludedAddresses) { final List
replicaAddresses = getReplicaAddresses(excludedAddresses); if (replicaAddresses.isEmpty()) { return null; } final int randomReplicaIndex = ThreadLocalRandomProvider.get().nextInt(replicaAddresses.size()); return replicaAddresses.get(randomReplicaIndex); } /** * Returns the addresses of the CRDT replicas from the current state of the * local membership list. Addresses contained in the {@code excludedAddresses} * collection are excluded. * * @param excludedAddresses the addresses to exclude when choosing a replica * address, must not be {@code null} * @return list of possible CRDT replica addresses */ private List
getReplicaAddresses(Collection
excludedAddresses) { final Collection dataMembers = getContext().getClusterService() .getMembers(MemberSelectors.DATA_MEMBER_SELECTOR); final int maxConfiguredReplicaCount = getMaxConfiguredReplicaCount(); final int currentReplicaCount = Math.min(maxConfiguredReplicaCount, dataMembers.size()); final ArrayList
replicaAddresses = new ArrayList
(currentReplicaCount); final Iterator dataMemberIterator = dataMembers.iterator(); for (int i = 0; i < currentReplicaCount; i++) { final Address dataMemberAddress = dataMemberIterator.next().getAddress(); if (!excludedAddresses.contains(dataMemberAddress)) { replicaAddresses.add(dataMemberAddress); } } return replicaAddresses; } /** * Returns the max configured replica count. * When invoked for the first time, this method will fetch the * configuration from a cluster member. * * @return the maximum configured replica count */ private int getMaxConfiguredReplicaCount() { if (maxConfiguredReplicaCount > 0) { return maxConfiguredReplicaCount; } else { final ClientMessage request = PNCounterGetConfiguredReplicaCountCodec.encodeRequest(name); final ClientMessage response = invoke(request); final PNCounterGetConfiguredReplicaCountCodec.ResponseParameters resultParameters = PNCounterGetConfiguredReplicaCountCodec.decodeResponse(response); maxConfiguredReplicaCount = resultParameters.response; } return maxConfiguredReplicaCount; } /** * Updates the locally observed CRDT vector clock atomically. This method * is thread safe and can be called concurrently. The method will only * update the clock if the {@code receivedLogicalTimestamps} is higher than * the currently observed vector clock. * * @param receivedLogicalTimestamps logical timestamps received from a replica state read */ private void updateObservedReplicaTimestamps(List> receivedLogicalTimestamps) { final VectorClock received = toVectorClock(receivedLogicalTimestamps); for (; ; ) { final VectorClock currentClock = this.observedClock; if (currentClock.isAfter(received)) { break; } if (OBSERVED_TIMESTAMPS_UPDATER.compareAndSet(this, currentClock, received)) { break; } } } /** * Returns the current target replica address to which this proxy is * sending invocations. */ // public for testing purposes public Address getCurrentTargetReplicaAddress() { return currentTargetReplicaAddress; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy