background

timeoutThis concept is not unique to Dubbo, but exists in other areas such as HTTP, thread execution time, mysql, Redis, etc., and is implemented in much the same way. Dubbo is a time-out capability implemented by the consumer.There will be one on the consumer sideTiming taskRole, according to the different timeout times of the interface to notify the timeout event, run timeout errors to the business code.

Analysis of the

Call link

Let’s review the dubbo client call link.As can be seen from the figure, the entire call link passes throughinvoker,filter,exchange, these three concepts we go to the previous article to understand, here is the main red background of the processDefaultFuture.newFuture()Click inside.

//org.... exchange.support.DefaultFuture#newFuture
DefaultFuture newFuture(Channel channel, Request request, int timeout) {
  final DefaultFuture future = new DefaultFuture(channel, request, timeout);
  timeoutCheck(future);/ / 1
  return future;
}
void timeoutCheck(DefaultFuture future) {
    TimeoutCheckTask task = new TimeoutCheckTask(future.getId());
    future.timeoutCheckTask = TIME_OUT_TIMER.newTimeout(task, future.getTimeout(), TimeUnit.MILLISECONDS);/ / 2
}
public static final Timer TIME_OUT_TIMER = new HashedWheelTimer(
new NamedThreadFactory("".true),30,TimeUnit.MILLISECONDS);/ / 3
Copy the code

Note 1 passes the Future object to the timeout check method, and note 2 initializes the TimeoutCheckTask with future.getid (). This ID comes from the unique number generated by AtomInteger for each request.

private static final AtomicLong INVOKE_ID = new AtomicLong(0);
private static long newId(a) {returnINVOKE_ID.getAndIncrement(); }Copy the code

Timeout task

Each request is uniquely incrementalized, and the task is added to a scheduled task executed every 30 milliseconds (the new version of Dubbo implements a scheduled task with a time wheel). It can also be seen that the scheduled task is at least 30ms off. Let’s look at the task

//org.... exchange.support.DefaultFuture.TimeoutCheckTask#run
public void run(Timeout timeout) {
    DefaultFuture future = DefaultFuture.getFuture(requestID);
    if (future == null || future.isDone()) { return; }/ / 1
    Response timeoutResponse = new Response(future.getId());
    timeoutResponse.setStatus(future.isSent() ? 31 : 30);/ / 2
    timeoutResponse.setErrorMessage(future.getTimeoutMessage(true));
    DefaultFuture.received(future.getChannel(), timeoutResponse, true);/ / 3
}
Copy the code

These very critical, note 1 branch should perform the most common of this kind of case is the server has returned results (that is, without a timeout), 2 is very interesting, isSent () to determine whether a request has been issued, if the network reason didn’t sent out, so is the client timeout, rather than server-side processing timeout, comment 3, we continue to.

org.... exchange.support.DefaultFuture#received(Channel, Response,boolean)
void received(Channel channel, Response response, boolean timeout) {
try {
    DefaultFuture future = FUTURES.remove(response.getId());
    if(future ! =null) {
        Timeout t = future.timeoutCheckTask;
        if(! timeout) {t.cancel(); } future.doReceived(response);// Continue clicking
    } else{}}finally{ CHANNELS.remove(response.getId()); }}//org.apache.dubbo.remoting.exchange.support.DefaultFuture#doReceived
private void doReceived(Response res) {
    if (res.getStatus() == Response.OK) {
        this.complete(res.getResult());
    } else if (res.getStatus() == Response.CLIENT_TIMEOUT || res.getStatus() == Response.SERVER_TIMEOUT) {
        this.completeExceptionally(new TimeoutException(res.getStatus() == Response.SERVER_TIMEOUT, channel, res.getErrorMessage()));//1 timeout logic
    } else {
        this.completeExceptionally(newRemotingException(channel, res.getErrorMessage())); }}Copy the code

At comment 1, the call completeExceptionally() completes the Future and wraps the error message back, and unpark continues to execute the returned result processing logic on the threads on which the business is waiting.

//org.apache.dubbo.rpc.proxy.InvokerInvocationHandler#invoke
public Object invoke(Object proxy, Method method, Object[] args) {
    String methodName = method.getName();
	//....
    return invoker.invoke(new RpcInvocation(method, args)).recreate();
}
//org.apache.dubbo.rpc.AppResponse#recreate
public Object recreate(a) throws Throwable {
if(exception ! =null) {
    try {
        Class clazz = exception.getClass();
        while(! clazz.getName().equals(Throwable.class.getName())) { clazz = clazz.getSuperclass(); } Field stackTraceField = clazz.getDeclaredField("stackTrace");
        stackTraceField.setAccessible(true);
        Object stackTrace = stackTraceField.get(exception);
        if (stackTrace == null) {  exception.setStackTrace(new StackTraceElement[0]); }}catch (Exception e) {
        // ignore
    }
    throw exception;/ / 1
}
return result;
}
Copy the code

In comment 1, if exception is not empty, throw an error to the business, so that the timeout error is handled. You can debug it yourself. What about the client side?

conclusion

Timeouts are inevitable and should be carefully considered during design.