/* * Copyright (c) 2008-2017, 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.spi.impl.operationservice.impl; import com.hazelcast.core.OperationTimeoutException; import com.hazelcast.nio.Packet; import com.hazelcast.nio.serialization.Data; import com.hazelcast.spi.impl.AbstractInvocationFuture; import com.hazelcast.spi.impl.operationservice.impl.responses.NormalResponse; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import static com.hazelcast.spi.impl.operationservice.impl.InvocationConstant.CALL_TIMEOUT; import static com.hazelcast.spi.impl.operationservice.impl.InvocationConstant.HEARTBEAT_TIMEOUT; import static com.hazelcast.spi.impl.operationservice.impl.InvocationConstant.INTERRUPTED; import static com.hazelcast.util.Clock.currentTimeMillis; import static com.hazelcast.util.ExceptionUtil.fixAsyncStackTrace; import static com.hazelcast.util.StringUtil.timeToString; /** * The InvocationFuture is the {@link com.hazelcast.spi.InternalCompletableFuture} that waits on the completion * of a {@link Invocation}. The Invocation executes an operation. * <p> * In the past the InvocationFuture.get logic was also responsible for detecting the heartbeat for blocking operations * using the CONTINUE_WAIT and detecting if an operation is still running using the IsStillRunning functionality. This * has been removed from the future and moved into the {@link InvocationMonitor}. * * @param <E> */ final class InvocationFuture<E> extends AbstractInvocationFuture<E> { volatile boolean interrupted; final Invocation invocation; private final boolean deserialize; InvocationFuture(Invocation invocation, boolean deserialize) { super(invocation.context.asyncExecutor, invocation.context.logger); this.invocation = invocation; this.deserialize = deserialize; } @Override protected String invocationToString() { return invocation.toString(); } @Override protected TimeoutException newTimeoutException(long timeout, TimeUnit unit) { return new TimeoutException(String.format("%s failed to complete within %d %s. %s", invocation.op.getClass().getSimpleName(), timeout, unit, invocation)); } @Override protected void onInterruptDetected() { interrupted = true; } @Override protected E resolveAndThrowIfException(Object unresolved) throws ExecutionException, InterruptedException { Object value = resolve(unresolved); if (value == null || !(value instanceof Throwable)) { return (E) value; } else if (value instanceof CancellationException) { throw (CancellationException) value; } else if (value instanceof ExecutionException) { throw (ExecutionException) value; } else if (value instanceof InterruptedException) { throw (InterruptedException) value; } else if (value instanceof Error) { throw (Error) value; } else { throw new ExecutionException((Throwable) value); } } @SuppressWarnings("checkstyle:npathcomplexity") @Override protected Object resolve(Object unresolved) { if (unresolved == null) { return null; } else if (unresolved == INTERRUPTED) { return new InterruptedException(invocation.op.getClass().getSimpleName() + " was interrupted. " + invocation); } else if (unresolved == CALL_TIMEOUT) { return newOperationTimeoutException(false); } else if (unresolved == HEARTBEAT_TIMEOUT) { return newOperationTimeoutException(true); } else if (unresolved.getClass() == Packet.class) { NormalResponse response = invocation.context.serializationService.toObject(unresolved); unresolved = response.getValue(); } Object value = unresolved; if (deserialize && value instanceof Data) { value = invocation.context.serializationService.toObject(value); if (value == null) { return null; } } if (value instanceof Throwable) { Throwable throwable = ((Throwable) value); fixAsyncStackTrace((Throwable) value, Thread.currentThread().getStackTrace()); return throwable; } return value; } private Object newOperationTimeoutException(boolean heartbeatTimeout) { StringBuilder sb = new StringBuilder(); if (heartbeatTimeout) { sb.append(invocation.op.getClass().getSimpleName()) .append(" invocation failed to complete due to operation-heartbeat-timeout. "); sb.append("Current time: ").append(timeToString(currentTimeMillis())).append(". "); sb.append("Start time: ").append(timeToString(invocation.firstInvocationTimeMillis)).append(". "); sb.append("Total elapsed time: ") .append(currentTimeMillis() - invocation.firstInvocationTimeMillis).append(" ms. "); long lastHeartbeatMillis = invocation.lastHeartbeatMillis; sb.append("Last operation heartbeat: "); appendHeartbeat(sb, lastHeartbeatMillis); long lastHeartbeatFromMemberMillis = invocation.context.invocationMonitor .getLastMemberHeartbeatMillis(invocation.invTarget); sb.append("Last operation heartbeat from member: "); appendHeartbeat(sb, lastHeartbeatFromMemberMillis); } else { sb.append(invocation.op.getClass().getSimpleName()) .append(" got rejected before execution due to not starting within the operation-call-timeout of: ") .append(invocation.callTimeoutMillis).append(" ms. "); sb.append("Current time: ").append(timeToString(currentTimeMillis())).append(". "); sb.append("Start time: ").append(timeToString(invocation.firstInvocationTimeMillis)).append(". "); sb.append("Total elapsed time: ") .append(currentTimeMillis() - invocation.firstInvocationTimeMillis).append(" ms. "); } sb.append(invocation); String msg = sb.toString(); return new ExecutionException(msg, new OperationTimeoutException(msg)); } private static void appendHeartbeat(StringBuilder sb, long lastHeartbeatMillis) { if (lastHeartbeatMillis == 0) { sb.append("never. "); } else { sb.append(timeToString(lastHeartbeatMillis)).append(". "); } } }