com.linkedin.restli.client.ParSeqRestClient Maven / Gradle / Ivy
/*
* Copyright 2016 LinkedIn, Inc
*
* 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.linkedin.restli.client;
import com.linkedin.parseq.Exceptions;
import com.linkedin.parseq.function.Failure;
import com.linkedin.parseq.function.Success;
import com.linkedin.parseq.function.Try;
import com.linkedin.parseq.internal.TimeUnitHelper;
import com.linkedin.r2.filter.R2Constants;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.linkedin.common.callback.Callback;
import com.linkedin.parseq.Task;
import com.linkedin.parseq.batching.Batch;
import com.linkedin.parseq.batching.BatchingStrategy;
import com.linkedin.parseq.internal.ArgumentUtil;
import com.linkedin.parseq.promise.Promise;
import com.linkedin.parseq.promise.Promises;
import com.linkedin.parseq.promise.SettablePromise;
import com.linkedin.r2.message.RequestContext;
import com.linkedin.restli.client.config.ConfigValue;
import com.linkedin.restli.client.config.RequestConfig;
import com.linkedin.restli.client.config.RequestConfigBuilder;
import com.linkedin.restli.client.config.RequestConfigOverrides;
import com.linkedin.restli.client.config.RequestConfigProvider;
import com.linkedin.restli.client.metrics.BatchingMetrics;
import com.linkedin.restli.client.metrics.Metrics;
import com.linkedin.restli.common.OperationNameGenerator;
/**
* A ParSeq client that creates a ParSeq task from a rest.li {@link Request} by sending the request to underlying rest.li
* {@link Client}. ParSeqRestClient delegates task execution to Rest.li Client {@link Client#sendRequest(Request, Callback)}
* method that takes a {@link PromiseCallbackAdapter}. ParSeq task created from {@link ParSeqRestClient} may fail when
* {@link PromiseCallbackAdapter} receives the following error conditions:
*
* 1. @{link RestLiResponseExcepion}: Request has reached Rest.li server and rest.li server throws RestLiServiceException.
* 2. @{link RemoteInvocationException}: Request failed before reaching rest.li server, for example, RestException thrown
* from request filters, {@link javax.naming.ServiceUnavailableException} when client cannot find available server instance
* that could serve the request, etc.
* 3. @{link TimeoutException}: Request times out after configured timeoutMs.
*
*
* @author Jaroslaw Odzga ([email protected])
* @author Min Chen ([email protected])
*
*/
public class ParSeqRestClient extends BatchingStrategy>
implements ParSeqRestliClient {
private static final Logger LOGGER = LoggerFactory.getLogger(ParSeqRestClient.class);
private final Client _client;
private final BatchingMetrics _batchingMetrics = new BatchingMetrics();
private final RequestConfigProvider _requestConfigProvider;
private final boolean _d2RequestTimeoutEnabled;
private final Function, RequestContext> _requestContextProvider;
ParSeqRestClient(final Client client, final RequestConfigProvider requestConfigProvider,
Function, RequestContext> requestContextProvider, final boolean d2RequestTimeoutEnabled) {
ArgumentUtil.requireNotNull(client, "client");
ArgumentUtil.requireNotNull(requestConfigProvider, "requestConfigProvider");
ArgumentUtil.requireNotNull(requestContextProvider, "requestContextProvider");
_client = client;
_requestConfigProvider = requestConfigProvider;
_requestContextProvider = requestContextProvider;
_d2RequestTimeoutEnabled = d2RequestTimeoutEnabled;
}
/**
* Creates new ParSeqRestClient with default configuration.
*
* @deprecated Please use {@link ParSeqRestliClientBuilder} to create instances.
*/
@Deprecated
public ParSeqRestClient(final Client client) {
ArgumentUtil.requireNotNull(client, "client");
_client = client;
_requestConfigProvider = RequestConfigProvider.build(new ParSeqRestliClientConfigBuilder().build(), () -> Optional.empty());
_requestContextProvider = request -> new RequestContext();
_d2RequestTimeoutEnabled = false;
}
/**
* Creates new ParSeqRestClient with default configuration.
*
* @deprecated Please use {@link ParSeqRestliClientBuilder} to create instances.
*/
@Deprecated
public ParSeqRestClient(final RestClient client) {
ArgumentUtil.requireNotNull(client, "client");
_client = client;
_requestConfigProvider = RequestConfigProvider.build(new ParSeqRestliClientConfigBuilder().build(), () -> Optional.empty());
_requestContextProvider = request -> new RequestContext();
_d2RequestTimeoutEnabled = false;
}
@Override
@Deprecated
public Promise> sendRequest(final Request request) {
return sendRequest(request, _requestContextProvider.apply(request));
}
@Override
@Deprecated
public Promise> sendRequest(final Request request, final RequestContext requestContext) {
final SettablePromise> promise = Promises.settable();
_client.sendRequest(request, requestContext, new PromiseCallbackAdapter(promise));
return promise;
}
static class PromiseCallbackAdapter implements Callback> {
private final SettablePromise> _promise;
public PromiseCallbackAdapter(final SettablePromise> promise) {
this._promise = promise;
}
@Override
public void onSuccess(final Response result) {
try {
_promise.done(result);
} catch (Exception e) {
onError(e);
}
}
@Override
public void onError(final Throwable e) {
_promise.fail(e);
}
}
@Override
public Task> createTask(final Request request) {
return createTask(request, _requestContextProvider.apply(request));
}
@Override
public Task> createTask(final Request request, final RequestContext requestContext) {
return createTask(generateTaskName(request), request, requestContext, _requestConfigProvider.apply(request));
}
/**
* @deprecated ParSeqRestClient generates consistent names for tasks based on request parameters and it is
* recommended to us default names.
*/
@Deprecated
public Task> createTask(final String name, final Request request, final RequestContext requestContext) {
return createTask(name, request, requestContext, _requestConfigProvider.apply(request));
}
@Override
public Task> createTask(Request request, RequestConfigOverrides configOverrides) {
return createTask(request, _requestContextProvider.apply(request), configOverrides);
}
@Override
public Task> createTask(Request request, RequestContext requestContext,
RequestConfigOverrides configOverrides) {
RequestConfig config = _requestConfigProvider.apply(request);
RequestConfigBuilder configBuilder = new RequestConfigBuilder(config);
RequestConfig effectiveConfig = configBuilder.applyOverrides(configOverrides).build();
return createTask(generateTaskName(request), request, requestContext, effectiveConfig);
}
/**
* Generates a task name for the request.
* @param request
* @return a task name
*/
static String generateTaskName(final Request> request) {
return request.getBaseUriTemplate() + " "
+ OperationNameGenerator.generate(request.getMethod(), request.getMethodName());
}
private Task> withTimeout(final Task> task, ConfigValue timeout) {
if (timeout.getSource().isPresent()) {
return task.withTimeout("src: " + timeout.getSource().get(), timeout.getValue(), TimeUnit.MILLISECONDS);
} else {
return task.withTimeout(timeout.getValue(), TimeUnit.MILLISECONDS);
}
}
private Task> withD2Timeout(final Task> task, ConfigValue timeout) {
String srcDesc = timeout.getSource().map(src -> " src: " + src).orElse("");
String timeoutTaskName = "withTimeout " + timeout.getValue().intValue() + TimeUnitHelper.toString(TimeUnit.MILLISECONDS)
+ srcDesc;
// make sure that we throw the same exception to maintain backward compatibility with current withTimeout implementation.
return task.transform(timeoutTaskName, (Try> tryGet) -> {
if (tryGet.isFailed() && tryGet.getError() instanceof TimeoutException) {
String timeoutExceptionMessage = "task: '" + task.getName() + "' " + timeoutTaskName;
return Failure.of(Exceptions.timeoutException(timeoutExceptionMessage));
} else {
return tryGet;
}
});
}
private Task> createTask(final String name, final Request request,
final RequestContext requestContext, RequestConfig config) {
LOGGER.debug("createTask, name: '{}', config: {}", name, config);
if (_d2RequestTimeoutEnabled) {
return createTaskWithD2Timeout(name, request, requestContext, config);
} else {
return createTaskWithTimeout(name, request, requestContext, config);
}
}
// Check whether per-request timeout is specified in the given request context.
private boolean hasRequestContextTimeout(RequestContext requestContext) {
Object requestTimeout = requestContext.getLocalAttr(R2Constants.REQUEST_TIMEOUT);
return (requestTimeout instanceof Number) && (((Number)requestTimeout).intValue() > 0);
}
// check whether we need to apply timeout to a rest.li request task.
private boolean needApplyTaskTimeout(RequestContext requestContext, ConfigValue timeout) {
// if no timeout configured or per-request timeout already specified in request context
return timeout.getValue() != null && timeout.getValue() > 0 && !hasRequestContextTimeout(requestContext);
}
// Apply timeout to a ParSeq rest.li request task through parseq timer task.
private Task> createTaskWithTimeout(final String name, final Request request,
final RequestContext requestContext, RequestConfig config) {
ConfigValue timeout = config.getTimeoutMs();
Task> requestTask;
if (RequestGroup.isBatchable(request, config)) {
requestTask = createBatchableTask(name, request, requestContext, config);
} else {
requestTask = Task.async(name, () -> sendRequest(request, requestContext));
}
if (!needApplyTaskTimeout(requestContext, timeout)) {
return requestTask;
} else {
return withTimeout(requestTask, timeout);
}
}
/**
* We will distinguish two cases in applying timeout to a ParSeq rest.li request task through D2 request timeout.
* Case 1: There is no per request timeout specified in request context of rest.li request, timeout is configured
* through ParSeqRestClient configuration. For this case, we will update request context as:
* REQUEST_TIMEOUT = configured timeout value
* REQUEST_TIMEOUT_IGNORE_IF_HIGHER_THAN_DEFAULT = true
* since in this case, ParSeqRestClient just wants to timeout this request from client side within configured timeout
* without disturbing any lower layer load balancing behaviors.
*
* Case 2: There is per request timeout specified in rest.li request, and there may or may not have timeout specified
* through ParSeqRestClient configuration. For this case, per request timeout specified in rest.li request always
* takes precedence, ParSeq will interpret that users would like to use this to impact lower layer LB behavior, and
* thus will pass down request context unchanged down.
*/
private Task> createTaskWithD2Timeout(final String name, final Request request,
final RequestContext requestContext, RequestConfig config) {
ConfigValue timeout = config.getTimeoutMs();
boolean taskNeedTimeout = needApplyTaskTimeout(requestContext, timeout);
if (taskNeedTimeout) {
// configure request context before creating parseq task from the request
requestContext.putLocalAttr(R2Constants.REQUEST_TIMEOUT, timeout.getValue().intValue());
requestContext.putLocalAttr(R2Constants.REQUEST_TIMEOUT_IGNORE_IF_HIGHER_THAN_DEFAULT, true);
}
Task> requestTask;
if (RequestGroup.isBatchable(request, config)) {
requestTask = createBatchableTask(name, request, requestContext, config);
} else {
requestTask = Task.async(name, () -> sendRequest(request, requestContext));
}
if (!taskNeedTimeout) {
return requestTask;
} else {
return withD2Timeout(requestTask, timeout);
}
}
private RestRequestBatchKey createKey(Request
© 2015 - 2025 Weber Informatics LLC | Privacy Policy