org.apache.dubbo.rpc.AsyncRpcResult Maven / Gradle / Ivy
Show all versions of dubbo Show documentation
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.rpc;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.threadpool.ThreadlessExecutor;
import org.apache.dubbo.rpc.model.ConsumerMethodModel;
import org.apache.dubbo.rpc.protocol.dubbo.FutureAdapter;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.BiConsumer;
import java.util.function.Function;
import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_ASYNC_KEY;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROXY_ERROR_ASYNC_RESPONSE;
import static org.apache.dubbo.common.utils.ReflectUtils.defaultReturn;
/**
* This class represents an unfinished RPC call, it will hold some context information for this call, for example RpcContext and Invocation,
* so that when the call finishes and the result returns, it can guarantee all the contexts being recovered as the same as when the call was made
* before any callback is invoked.
*
* TODO if it's reasonable or even right to keep a reference to Invocation?
*
* As {@link Result} implements CompletionStage, {@link AsyncRpcResult} allows you to easily build a async filter chain whose status will be
* driven entirely by the state of the underlying RPC call.
*
* AsyncRpcResult does not contain any concrete value (except the underlying value bring by CompletableFuture), consider it as a status transfer node.
* {@link #getValue()} and {@link #getException()} are all inherited from {@link Result} interface, implementing them are mainly
* for compatibility consideration. Because many legacy {@link Filter} implementation are most possibly to call getValue directly.
*/
public class AsyncRpcResult implements Result {
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(AsyncRpcResult.class);
/**
* RpcContext may already have been changed when callback happens, it happens when the same thread is used to execute another RPC call.
* So we should keep the copy of current RpcContext instance and restore it before callback being executed.
*/
private RpcContext.RestoreContext storedContext;
private Executor executor;
private Invocation invocation;
private final boolean async;
private CompletableFuture responseFuture;
/**
* Whether set future to Thread Local when invocation mode is sync
*/
private static final boolean setFutureWhenSync = Boolean.parseBoolean(System.getProperty(CommonConstants.SET_FUTURE_IN_SYNC_MODE, "true"));
public AsyncRpcResult(CompletableFuture future, Invocation invocation) {
this.responseFuture = future;
this.invocation = invocation;
RpcInvocation rpcInvocation = (RpcInvocation) invocation;
if ((rpcInvocation.get(PROVIDER_ASYNC_KEY) != null || InvokeMode.SYNC != rpcInvocation.getInvokeMode()) && !future.isDone()) {
async = true;
this.storedContext = RpcContext.clearAndStoreContext();
} else {
async = false;
}
}
/**
* Notice the return type of {@link #getValue} is the actual type of the RPC method, not {@link AppResponse}
*
* @return
*/
@Override
public Object getValue() {
return getAppResponse().getValue();
}
/**
* CompletableFuture can only be completed once, so try to update the result of one completed CompletableFuture will
* have no effect. To avoid this problem, we check the complete status of this future before update its value.
*
* But notice that trying to give an uncompleted CompletableFuture a new specified value may face a race condition,
* because the background thread watching the real result will also change the status of this CompletableFuture.
* The result is you may lose the value you expected to set.
*
* @param value
*/
@Override
public void setValue(Object value) {
try {
if (responseFuture.isDone()) {
responseFuture.get().setValue(value);
} else {
AppResponse appResponse = new AppResponse(invocation);
appResponse.setValue(value);
responseFuture.complete(appResponse);
}
} catch (Exception e) {
// This should not happen in normal request process;
logger.error(PROXY_ERROR_ASYNC_RESPONSE, "", "", "Got exception when trying to fetch the underlying result from AsyncRpcResult.");
throw new RpcException(e);
}
}
@Override
public Throwable getException() {
return getAppResponse().getException();
}
@Override
public void setException(Throwable t) {
try {
if (responseFuture.isDone()) {
responseFuture.get().setException(t);
} else {
AppResponse appResponse = new AppResponse(invocation);
appResponse.setException(t);
responseFuture.complete(appResponse);
}
} catch (Exception e) {
// This should not happen in normal request process;
logger.error(PROXY_ERROR_ASYNC_RESPONSE, "", "", "Got exception when trying to fetch the underlying result from AsyncRpcResult.");
throw new RpcException(e);
}
}
@Override
public boolean hasException() {
return getAppResponse().hasException();
}
public CompletableFuture getResponseFuture() {
return responseFuture;
}
public void setResponseFuture(CompletableFuture responseFuture) {
this.responseFuture = responseFuture;
}
public Result getAppResponse() {
try {
if (responseFuture.isDone()) {
return responseFuture.get();
}
} catch (Exception e) {
// This should not happen in normal request process;
logger.error(PROXY_ERROR_ASYNC_RESPONSE, "", "", "Got exception when trying to fetch the underlying result from AsyncRpcResult.");
throw new RpcException(e);
}
return createDefaultValue(invocation);
}
/**
* This method will always return after a maximum 'timeout' waiting:
* 1. if value returns before timeout, return normally.
* 2. if no value returns after timeout, throw TimeoutException.
*
* @return
* @throws InterruptedException
* @throws ExecutionException
*/
@Override
public Result get() throws InterruptedException, ExecutionException {
if (executor != null && executor instanceof ThreadlessExecutor) {
ThreadlessExecutor threadlessExecutor = (ThreadlessExecutor) executor;
threadlessExecutor.waitAndDrain();
}
return responseFuture.get();
}
@Override
public Result get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
if (executor != null && executor instanceof ThreadlessExecutor) {
ThreadlessExecutor threadlessExecutor = (ThreadlessExecutor) executor;
threadlessExecutor.waitAndDrain();
}
return responseFuture.get(timeout, unit);
}
@Override
public Object recreate() throws Throwable {
RpcInvocation rpcInvocation = (RpcInvocation) invocation;
if (InvokeMode.FUTURE == rpcInvocation.getInvokeMode()) {
return RpcContext.getClientAttachment().getFuture();
} else if (InvokeMode.ASYNC == rpcInvocation.getInvokeMode()) {
return createDefaultValue(invocation).recreate();
}
return getAppResponse().recreate();
}
public Result whenCompleteWithContext(BiConsumer fn) {
this.responseFuture = this.responseFuture.whenComplete((v, t) -> {
if (async) {
RpcContext.restoreContext(storedContext);
}
fn.accept(v, t);
});
if (setFutureWhenSync || ((RpcInvocation) invocation).getInvokeMode() != InvokeMode.SYNC) {
// Necessary! update future in context, see https://github.com/apache/dubbo/issues/9461
RpcContext.getServiceContext().setFuture(new FutureAdapter<>(this.responseFuture));
}
return this;
}
@Override
public CompletableFuture thenApply(Function fn) {
return this.responseFuture.thenApply(fn);
}
@Override
@Deprecated
public Map getAttachments() {
return getAppResponse().getAttachments();
}
@Override
public Map getObjectAttachments() {
return getAppResponse().getObjectAttachments();
}
@Override
public void setAttachments(Map map) {
getAppResponse().setAttachments(map);
}
@Override
public void setObjectAttachments(Map map) {
getAppResponse().setObjectAttachments(map);
}
@Deprecated
@Override
public void addAttachments(Map map) {
getAppResponse().addAttachments(map);
}
@Override
public void addObjectAttachments(Map map) {
getAppResponse().addObjectAttachments(map);
}
@Override
public String getAttachment(String key) {
return getAppResponse().getAttachment(key);
}
@Override
public Object getObjectAttachment(String key) {
return getAppResponse().getObjectAttachment(key);
}
@Override
public String getAttachment(String key, String defaultValue) {
return getAppResponse().getAttachment(key, defaultValue);
}
@Override
public Object getObjectAttachment(String key, Object defaultValue) {
return getAppResponse().getObjectAttachment(key, defaultValue);
}
@Override
public void setAttachment(String key, String value) {
setObjectAttachment(key, value);
}
@Override
public void setAttachment(String key, Object value) {
setObjectAttachment(key, value);
}
@Override
public void setObjectAttachment(String key, Object value) {
getAppResponse().setAttachment(key, value);
}
public Executor getExecutor() {
return executor;
}
public void setExecutor(Executor executor) {
this.executor = executor;
}
/**
* Some utility methods used to quickly generate default AsyncRpcResult instance.
*/
public static AsyncRpcResult newDefaultAsyncResult(AppResponse appResponse, Invocation invocation) {
return new AsyncRpcResult(CompletableFuture.completedFuture(appResponse), invocation);
}
public static AsyncRpcResult newDefaultAsyncResult(Invocation invocation) {
return newDefaultAsyncResult(null, null, invocation);
}
public static AsyncRpcResult newDefaultAsyncResult(Object value, Invocation invocation) {
return newDefaultAsyncResult(value, null, invocation);
}
public static AsyncRpcResult newDefaultAsyncResult(Throwable t, Invocation invocation) {
return newDefaultAsyncResult(null, t, invocation);
}
public static AsyncRpcResult newDefaultAsyncResult(Object value, Throwable t, Invocation invocation) {
CompletableFuture future = new CompletableFuture<>();
AppResponse result = new AppResponse(invocation);
if (t != null) {
result.setException(t);
} else {
result.setValue(value);
}
future.complete(result);
return new AsyncRpcResult(future, invocation);
}
private static Result createDefaultValue(Invocation invocation) {
ConsumerMethodModel method = (ConsumerMethodModel) invocation.get(Constants.METHOD_MODEL);
return method != null ? new AppResponse(defaultReturn(method.getReturnClass())) : new AppResponse();
}
}