/*
* Copyright 2017 LINE Corporation
*
* LINE Corporation 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 com.linecorp.armeria.client.retry;
import static com.linecorp.armeria.common.util.Functions.voidFunction;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
import com.linecorp.armeria.client.Client;
import com.linecorp.armeria.client.ClientRequestContext;
import com.linecorp.armeria.common.DefaultRpcResponse;
import com.linecorp.armeria.common.RpcRequest;
import com.linecorp.armeria.common.RpcResponse;
import com.linecorp.armeria.common.util.Exceptions;
import io.netty.channel.EventLoop;
/**
* A {@link Client} decorator that handles failures of an invocation and retries RPC requests.
*/
public class RetryingRpcClient extends RetryingClient<RpcRequest, RpcResponse> {
private static final RetryException RETRY_EXCEPTION = Exceptions.clearTrace(new RetryException());
/**
* Creates a new {@link Client} decorator that handles failures of an invocation and retries RPC requests.
*/
public static Function<Client<? super RpcRequest, ? extends RpcResponse>, RetryingRpcClient>
newDecorator(RetryRequestStrategy<RpcRequest, RpcResponse> retryRequestStrategy) {
return delegate -> new RetryingRpcClient(delegate, retryRequestStrategy,
Backoff::withoutDelay);
}
/**
* Creates a new {@link Client} decorator that handles failures of an invocation and retries RPC requests.
*/
public static Function<Client<? super RpcRequest, ? extends RpcResponse>, RetryingRpcClient>
newDecorator(RetryRequestStrategy<RpcRequest, RpcResponse> retryRequestStrategy,
Supplier<? extends Backoff> backoffSupplier) {
return delegate -> new RetryingRpcClient(delegate, retryRequestStrategy, backoffSupplier);
}
/**
* Creates a new instance that decorates the specified {@link Client}.
*/
public RetryingRpcClient(Client<? super RpcRequest, ? extends RpcResponse> delegate,
RetryRequestStrategy<RpcRequest, RpcResponse> retryStrategy,
Supplier<? extends Backoff> backoffSupplier) {
super(delegate, retryStrategy, backoffSupplier);
}
@Override
public RpcResponse execute(ClientRequestContext ctx, RpcRequest req) throws Exception {
DefaultRpcResponse responseFuture = new DefaultRpcResponse();
Backoff backoff = newBackoff();
retry(1, backoff, ctx, req, () -> {
try {
return delegate().execute(ctx, req);
} catch (Exception e) {
return new DefaultRpcResponse(e);
}
}, responseFuture);
return responseFuture;
}
private void retry(int currentAttemptNo, Backoff backoff, ClientRequestContext ctx, RpcRequest req,
Supplier<RpcResponse> action, DefaultRpcResponse responseFuture) {
RpcResponse response = action.get();
response.handle(voidFunction((result, thrown) -> {
final Throwable exception;
if (thrown != null) {
if (!retryStrategy().shouldRetry(req, thrown)) {
responseFuture.completeExceptionally(thrown);
return;
}
exception = thrown;
} else if (retryStrategy().shouldRetry(req, response)) {
exception = RETRY_EXCEPTION;
} else {
responseFuture.complete(result);
return;
}
long nextInterval = backoff.nextIntervalMillis(currentAttemptNo);
if (nextInterval < 0) {
responseFuture.completeExceptionally(exception);
} else {
EventLoop eventLoop = ctx.contextAwareEventLoop();
if (nextInterval <= 0) {
eventLoop.submit(() -> retry(currentAttemptNo + 1, backoff, ctx, req, action,
responseFuture));
} else {
eventLoop.schedule(
() -> retry(currentAttemptNo + 1, backoff, ctx, req, action, responseFuture),
nextInterval, TimeUnit.MILLISECONDS);
}
}
}));
}
private static final class RetryException extends RuntimeException {
private static final long serialVersionUID = -3816065469543230534L;
}
}