A, TimeoutException

First of all, timeout exception is a very basic exception type in RPC exception. I used to think that timeout exception must be thrown by the server, but when we really study Dubbo’s RPC call, timeout exception is not handled by the server, but by the consumer side. When the consumer sends a request to the server, whether synchronous or asynchronous, the call result will be obtained through defaultfeature.get (). If the result is not obtained within the timeout period, a timeout exception will be thrown.

Refer to my original article on timeouts: juejin.cn/post/684490…

Second, the RemotingException

Remote call exception, this exception is thrown at the communication layer, such as Netty, Dubbo’s channle, Feature, etc., is disabled or some other problem, resulting in the message cannot be sent or processed properly, will be thrown remote call exception.

Third, RpcException

All exception exceptions are wrapped as rpcExceptions either timeoutExceptions or RemotingExceptions are wrapped as rpcExceptions during service invocation. So RpcException is a very important exception for Dubbo.

  • Exception type of RpcException:
    public /**final**/ class RpcException extends RuntimeException { public static final int UNKNOWN_EXCEPTION = 0; Public static final int NETWORK_EXCEPTION = 1; Public static final int TIMEOUT_EXCEPTION = 2; Public static final int BIZ_EXCEPTION = 3; Public static final int FORBIDDEN_EXCEPTION = 4; Public static final int SERIALIZATION_EXCEPTION = 5; Public static final int NO_INVOKER_AVAILABLE_AFTER_FILTER = 6; // No invoker available // omit}Copy the code

Four, InvocationTargetException

A service exception corresponds to BIZ_EXCEPTION in RpcException.

5. Exception handling

After the consumer generates the proxy class for the interface, it selects through Cluster and Loadbalance to invoke Invoker. Finally, a request is sent to the service provider via DubboInvoker.

public class DubboInvoker<T> extends AbstractInvoker<T> {
    
    private final ExchangeClient[] clients;
    
    protected Result doInvoke(final Invocation invocation) throws Throwable { RpcInvocation inv = (RpcInvocation) invocation; final String methodName = RpcUtils.getMethodName(invocation); // Set path and version to inv.setAttachment(Constants.PATH_KEY, getUrl().getPath()); inv.setAttachment(Constants.VERSION_KEY, version); ExchangeClient currentClient;if(clients.length == 1) {// Get ExchangeClient currentClient = clients[0]; }else{ currentClient = clients[index.getAndIncrement() % clients.length]; } Try {// Get async configuration Boolean isAsync = RPCutils. isAsync(getUrl(), Invocation); / / isOneway totrueBoolean isOneway = RPCutils. isOneway(getUrl(), Invocation); int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT); Async has no return valueif (isOneway) {
                boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false); Currentclient. send(inv, isSent); Rpccontext.getcontext ().setFuture(null); // Return an empty RpcResultreturnnew RpcResult(); } // Async has a return valueelse if(isAsync) {ResponseFuture future = currentClient.request(inv, timeout); Rpccontext.getcontext ().setFuture(new FutureAdapter<Object>(future)); // Temporarily return an empty resultreturnnew RpcResult(); } // synchronous callelse{ RpcContext.getContext().setFuture(null); // send the request, get an instance of ResponseFuture, and call that instance's get method to waitreturn(Result) currentClient.request(inv, timeout).get(); } } catch (TimeoutException e) { throw new RpcException(... ."Invoke remote method timeout...."); } catch (RemotingException e) { throw new RpcException(... ."Failed to invoke remote method: ..."); }} // omit other methods}Copy the code

In DubboInover timeoutExceptions and RemotingExceptions are wrapped as rpcExceptions of the corresponding type and thrown upwards.

Let’s look directly at what the service provider does if an exception occurs. Upon receiving a Request from a consumer, the service provider first decodes the Request and deserializes it into a Request object. The Request object

ChannelEventRunnable#run()- > DecodeHandler#received(Channel, Object)- > HeaderExchangeHandler#received(Channel, Object)// Response is created here, and the service's proxy class returns the RpcResultd object and sets it into Response. - > HeaderExchangeHandler#handleRequest(ExchangeChannel, Request)- > DubboProtocol requestHandler#reply(ExchangeChannel, Object)// Fiter is processed first -- > Filter#invoke(Invoker, Invocation)AbstractProxyInvoker > AbstractProxyInvoker > AbstractProxyInvoker#invoke(Invocation)- > Wrapper0#invokeMethod(Object, String, Class[], Object[])- > DemoServiceImpl#sayHello(String)
Copy the code

The AbstractProxyInvoker#invoke(Invocation) method here:

public Result invoke(Invocation invocation) throws RpcException { RpcContext rpcContext = RpcContext.getContext(); Try {// template method, created by avassistProxyFactory, the real service call logic Object obj =doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments()); // If the operation is asynchronousif (RpcUtils.isReturnTypeFuture(invocation)) {
                return new AsyncRpcResult((CompletableFuture<Object>) obj);
            } else if (rpcContext.isAsyncStarted()) { // ignore obj in case of RpcContext.startAsync()? always rely on user to write back.
                return new AsyncRpcResult(((AsyncContextImpl)(rpcContext.getAsyncContext())).getInternalFuture());
            } else {
                returnnew RpcResult(obj); }} the catch (InvocationTargetException e) {/ / service call anomaly happened / / TODO async throw exception before async thread write back, should stop asyncContextif(rpcContext.isAsyncStarted() && ! rpcContext.stopAsync()) { logger.error("Provider async started, but got an exception from the original method, cannot write the exception back to consumer because an async result may have returned the new thread.", e); } // Set the true exception typereturn new RpcResult(e.getTargetException());
        } catch (Throwable e) {
            throw new RpcException("Failed to invoke remote proxy method " + invocation.getMethodName() + " to " + getUrl() + ", cause: "+ e.getMessage(), e); }}Copy the code

The proxy class

/** Wrapper0 is generated at runtime, */ public class Wrapper0 extends Wrapper implements classGenerator. DC {public static String[] PNS; public static Map pts; public static String[] mns; public static String[] dmns; public static Class[] mts0; Public Object invokeMethod(Object Object, String String, Class[] arrClass, Object[] arrobject) throws InvocationTargetException { DemoService demoService; Try {// Type conversion demoService = (demoService)object; } catch (Throwable throwable) { throw new IllegalArgumentException(throwable); } try {// call the specified method based on the method nameif ("sayHello".equals(string) && arrclass.length == 1) {
                returndemoService.sayHello((String)arrobject[0]); }} the catch (Throwable Throwable) {/ / create a InvocationTargetException throw new InvocationTargetException (Throwable); } throw new NoSuchMethodException(new StringBuffer().append("Not found method \"").append(string).append("\" in class com.alibaba.dubbo.demo.DemoService.").toString()); }}Copy the code

DoInvoke is an abstract method that needs to be implemented by a concrete Invoker instance. The Invoker instance is created at run time using JavassistProxyFactory, where Dubbo generates a proxy class for the service through the Javassist framework and implements the invokeMethod method, which ultimately invokes the specific service based on the invocation information. Specific service will not be thrown when the call real exceptions (targetException), but will be packed exceptions for InvocationTargetException throw up. In AbstractProxyInvoker# invoke (Invocation) method and will get to targetExceptions Settings to RpcResult from InvocationTargetException returned to the consumer end.

At this point we may wonder how the exception for the real service invocation logic (server side) is thrown. The answer is ExceptionFilter.

Org. Apache. Dubbo, RPC protocol. The ProtocolFilterWrapper# buildInvokerChain filter chain will be in the build up, The AbstractProxyInvoker#invoke(Invocation) filter is invoked before the Invocation.

private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
        Invoker<T> last = invoker;
        List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
        if(! filters.isEmpty()) {for (int i = filters.size() - 1; i >= 0; i--) {
                final Filter filter = filters.get(i);
                final Invoker<T> next = last;
                last = new Invoker<T>() {

                    @Override
                    public Class<T> getInterface() {
                        return invoker.getInterface();
                    }

                    @Override
                    public URL getUrl() {
                        return invoker.getUrl();
                    }

                    @Override
                    public boolean isAvailable() {
                        return invoker.isAvailable();
                    }

                    @Override
                    public Result invoke(Invocation invocation) throws RpcException {
                        logger.info("--filter " + filter.getClass().getSimpleName() + " invoke execute");
                        Result result = filter.invoke(next, invocation);
                        if (result instanceof AsyncRpcResult) {
                            AsyncRpcResult asyncResult = (AsyncRpcResult) result;
                            asyncResult.thenApplyWithContext(r -> filter.onResponse(r, invoker, invocation));
                            return asyncResult;
                        } else {
                            logger.info("--filter " + filter.getClass().getSimpleName() + " response execute");
                            result = filter.onResponse(result, invoker, invocation);
                            return result;
                        }

                    }

                    @Override
                    public void destroy() {
                        invoker.destroy();
                    }

                    @Override
                    public String toString() {
                        returninvoker.toString(); }}; }}return last;
    }
Copy the code

Two lines of log are added to clearly see the flow of the filter. Our focus is to observe how ExceptionFilter#onResponse works when the service invocation logic throws an exception.

public Result onResponse(Result result, Invoker<? > invoker, Invocation invocation) {if(result.hasException() && GenericService.class ! = invoker.getInterface()) { try { Throwable exception = result.getException(); // directly throwifIts Checked Exception // If the check exception is thrown directlyif(! (exception instanceof RuntimeException) && (exception instanceof Exception)) {return result;
                }
                // directly throw if the exception appears inInvocation {Method Method = Invoker.getInterface ().getMethod(invocation. Invocation. invocation.getParameterTypes()); Class<? >[] exceptionClassses = method.getExceptionTypes();for(Class<? > exceptionClass : exceptionClassses) {if (exception.getClass().equals(exceptionClass)) {
                            return result;
                        }
                    }
                } catch (NoSuchMethodException e) {
                    returnresult; } / /for the exception not found in methods signature, print ERROR message in// Servers log. // Method signature no declared exception, error log logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
                        + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
                        + ", exception: " + exception.getClass().getName() + ":" + exception.getMessage(), exception);

                // directly throw if exception class and interface class are in// If the exception class and the interface class are in the same JAR file, they are thrown directly. String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface()); String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
                    return result;
                }
                // directly throw ifString className = exception.getClass().getName();if (className.startsWith("java.") || className.startsWith("javax.")) {
                    return result;
                }
                // directly throw ifIts dubbo exception // If it is a Dubbo exception, it is thrownif (exception instanceof RpcException) {
                    returnresult; } // Otherwise, wrap with RuntimeException and throw back to the clientreturn new RpcResult(new RuntimeException(StringUtils.toString(exception)));
            } catch (Throwable e) {
                logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost()
                        + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
                        + ", exception: " + e.getClass().getName() + ":" + e.getMessage(), e);
                returnresult; }}return result;
    }
Copy the code

Then return to the client, gain RpcResult InvokerInvocationHandler and call org. Apache. Dubbo. RPC. RpcResult# recreate () method will be printed on the client side.