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

com.hazelcast.client.spi.impl.ClientInvocation 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.spi.impl;

import com.hazelcast.client.HazelcastClientNotActiveException;
import com.hazelcast.client.connection.nio.ClientConnection;
import com.hazelcast.client.impl.clientside.HazelcastClientInstanceImpl;
import com.hazelcast.client.impl.protocol.ClientMessage;
import com.hazelcast.client.spi.ClientClusterService;
import com.hazelcast.client.spi.ClientExecutionService;
import com.hazelcast.client.spi.EventHandler;
import com.hazelcast.core.HazelcastInstanceNotActiveException;
import com.hazelcast.core.HazelcastOverloadException;
import com.hazelcast.core.LifecycleService;
import com.hazelcast.core.OperationTimeoutException;
import com.hazelcast.logging.ILogger;
import com.hazelcast.nio.Address;
import com.hazelcast.nio.Connection;
import com.hazelcast.spi.exception.RetryableException;
import com.hazelcast.spi.exception.TargetDisconnectedException;
import com.hazelcast.spi.exception.TargetNotMemberException;
import com.hazelcast.spi.impl.sequence.CallIdSequence;

import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;

import static com.hazelcast.util.Clock.currentTimeMillis;
import static com.hazelcast.util.StringUtil.timeToString;

/**
 * Handles the routing of a request from a Hazelcast client.
 * 

* 1) Where should request be sent?
* 2) Should it be retried?
* 3) How many times is it retried? */ public class ClientInvocation implements Runnable { private static final int MAX_FAST_INVOCATION_COUNT = 5; private static final int UNASSIGNED_PARTITION = -1; private static final AtomicLongFieldUpdater INVOKE_COUNT = AtomicLongFieldUpdater.newUpdater(ClientInvocation.class, "invokeCount"); private final ClientInvocationFuture clientInvocationFuture; private final ILogger logger; private final LifecycleService lifecycleService; private final ClientClusterService clientClusterService; private final AbstractClientInvocationService invocationService; private final ClientExecutionService executionService; private volatile ClientMessage clientMessage; private final CallIdSequence callIdSequence; private final Address address; private final int partitionId; private final Connection connection; private final long startTimeMillis; private final long retryPauseMillis; private final String objectName; private volatile ClientConnection sendConnection; private EventHandler handler; private volatile long invokeCount; private volatile long invocationTimeoutMillis; protected ClientInvocation(HazelcastClientInstanceImpl client, ClientMessage clientMessage, String objectName, int partitionId, Address address, Connection connection) { this.clientClusterService = client.getClientClusterService(); this.lifecycleService = client.getLifecycleService(); this.invocationService = (AbstractClientInvocationService) client.getInvocationService(); this.executionService = client.getClientExecutionService(); this.objectName = objectName; this.clientMessage = clientMessage; this.partitionId = partitionId; this.address = address; this.connection = connection; this.startTimeMillis = System.currentTimeMillis(); this.retryPauseMillis = invocationService.getInvocationRetryPauseMillis(); this.logger = invocationService.invocationLogger; this.callIdSequence = invocationService.getCallIdSequence(); this.clientInvocationFuture = new ClientInvocationFuture(this, executionService, clientMessage, logger, callIdSequence); this.invocationTimeoutMillis = invocationService.getInvocationTimeoutMillis(); } /** * Create an invocation that will be executed on random member. */ public ClientInvocation(HazelcastClientInstanceImpl client, ClientMessage clientMessage, String objectName) { this(client, clientMessage, objectName, UNASSIGNED_PARTITION, null, null); } /** * Create an invocation that will be executed on owner of {@code partitionId}. */ public ClientInvocation(HazelcastClientInstanceImpl client, ClientMessage clientMessage, String objectName, int partitionId) { this(client, clientMessage, objectName, partitionId, null, null); } /** * Create an invocation that will be executed on member with given {@code address}. */ public ClientInvocation(HazelcastClientInstanceImpl client, ClientMessage clientMessage, String objectName, Address address) { this(client, clientMessage, objectName, UNASSIGNED_PARTITION, address, null); } /** * Create an invocation that will be executed on given {@code connection}. */ public ClientInvocation(HazelcastClientInstanceImpl client, ClientMessage clientMessage, String objectName, Connection connection) { this(client, clientMessage, objectName, UNASSIGNED_PARTITION, null, connection); } public int getPartitionId() { return partitionId; } public ClientMessage getClientMessage() { return clientMessage; } public ClientInvocationFuture invoke() { assert (clientMessage != null); clientMessage.setCorrelationId(callIdSequence.next()); invokeOnSelection(); return clientInvocationFuture; } public ClientInvocationFuture invokeUrgent() { assert (clientMessage != null); clientMessage.setCorrelationId(callIdSequence.forceNext()); invokeOnSelection(); return clientInvocationFuture; } private void invokeOnSelection() { INVOKE_COUNT.incrementAndGet(this); try { if (isBindToSingleConnection()) { invocationService.invokeOnConnection(this, (ClientConnection) connection); } else if (partitionId != -1) { invocationService.invokeOnPartitionOwner(this, partitionId); } else if (address != null) { invocationService.invokeOnTarget(this, address); } else { invocationService.invokeOnRandomTarget(this); } } catch (Exception e) { if (e instanceof HazelcastOverloadException) { throw (HazelcastOverloadException) e; } notifyException(e); } } @Override public void run() { retry(); } private void retry() { // retry modifies the client message and should not reuse the client message. // It could be the case that it is in write queue of the connection. clientMessage = clientMessage.copy(); // first we force a new invocation slot because we are going to return our old invocation slot immediately after // It is important that we first 'force' taking a new slot; otherwise it could be that a sneaky invocation gets // through that takes our slot! clientMessage.setCorrelationId(callIdSequence.forceNext()); //we release the old slot callIdSequence.complete(); try { invokeOnSelection(); } catch (Throwable e) { clientInvocationFuture.complete(e); } } public void setInvocationTimeoutMillis(long invocationTimeoutMillis) { this.invocationTimeoutMillis = invocationTimeoutMillis; } public void notify(ClientMessage clientMessage) { if (clientMessage == null) { throw new IllegalArgumentException("response can't be null"); } clientInvocationFuture.complete(clientMessage); } public void notifyException(Throwable exception) { logException(exception); if (!lifecycleService.isRunning()) { clientInvocationFuture.complete(new HazelcastClientNotActiveException("Client is shutting down", exception)); return; } if (isNotAllowedToRetryOnSelection(exception)) { clientInvocationFuture.complete(exception); return; } if (!shouldRetry(exception)) { clientInvocationFuture.complete(exception); return; } long timePassed = System.currentTimeMillis() - startTimeMillis; if (timePassed > invocationTimeoutMillis) { if (logger.isFinestEnabled()) { logger.finest("Exception will not be retried because invocation timed out", exception); } clientInvocationFuture.complete(newOperationTimeoutException(exception)); return; } try { execute(); } catch (RejectedExecutionException e) { clientInvocationFuture.complete(new HazelcastClientNotActiveException("Client is shutting down", exception)); } } private void logException(Throwable exception) { if (logger.isFinestEnabled()) { logger.finest("Invocation got an exception " + this + ", invoke count : " + invokeCount + ", exception : " + exception.getClass() + ", message : " + exception.getMessage() + (exception.getCause() != null ? (", cause :" + exception.getCause()) : "")); } } private void execute() { if (invokeCount < MAX_FAST_INVOCATION_COUNT) { // fast retry for the first few invocations executionService.execute(this); } else { // progressive retry delay long delayMillis = Math.min(1 << (invokeCount - MAX_FAST_INVOCATION_COUNT), retryPauseMillis); executionService.schedule(this, delayMillis, TimeUnit.MILLISECONDS); } } private boolean isNotAllowedToRetryOnSelection(Throwable exception) { if (isBindToSingleConnection() && exception instanceof IOException) { return true; } if (address != null && exception instanceof TargetNotMemberException && clientClusterService.getMember(address) == null) { //when invocation send over address //if exception is target not member and //address is not available in member list , don't retry return true; } return false; } private boolean isBindToSingleConnection() { return connection != null; } public EventHandler getEventHandler() { return handler; } public void setEventHandler(EventHandler handler) { this.handler = handler; } public void setSendConnection(ClientConnection connection) { this.sendConnection = connection; } public ClientConnection getSendConnectionOrWait() throws InterruptedException { while (sendConnection == null && !clientInvocationFuture.isDone()) { Thread.sleep(retryPauseMillis); } return sendConnection; } public ClientConnection getSendConnection() { return sendConnection; } private boolean shouldRetry(Throwable t) { if (isRetrySafeException(t)) { return true; } if (t instanceof TargetDisconnectedException) { return clientMessage.isRetryable() || invocationService.isRedoOperation(); } return false; } public static boolean isRetrySafeException(Throwable t) { return t instanceof IOException || t instanceof HazelcastInstanceNotActiveException || t instanceof RetryableException; } public Executor getUserExecutor() { return executionService.getUserExecutor(); } private Object newOperationTimeoutException(Throwable e) { StringBuilder sb = new StringBuilder(); sb.append(this); sb.append(" timed out because exception occurred after client invocation timeout "); sb.append(invocationService.getInvocationTimeoutMillis()).append(" ms. "); sb.append("Current time: ").append(timeToString(currentTimeMillis())).append(". "); sb.append("Start time: ").append(timeToString(startTimeMillis)).append(". "); sb.append("Total elapsed time: ").append(currentTimeMillis() - startTimeMillis).append(" ms. "); String msg = sb.toString(); return new OperationTimeoutException(msg, e); } @Override public String toString() { String target; if (isBindToSingleConnection()) { target = "connection " + connection; } else if (partitionId != -1) { target = "partition " + partitionId; } else if (address != null) { target = "address " + address; } else { target = "random"; } return "ClientInvocation{" + "clientMessage = " + clientMessage + ", objectName = " + objectName + ", target = " + target + ", sendConnection = " + sendConnection + '}'; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy