Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public static BackoffPolicy noBackoff() {
}

/**
* Creates an new constant backoff policy with the provided configuration.
* Creates a new constant backoff policy with the provided configuration.
*
* @param delay The delay defines how long to wait between retry attempts. Must not be null.
* Must be &lt;= <code>Integer.MAX_VALUE</code> ms.
Expand All @@ -68,7 +68,7 @@ public static BackoffPolicy constantBackoff(Long delay, int maxNumberOfRetries)
}

/**
* Creates an new exponential backoff policy with a default configuration of 50 ms initial wait period and 8 retries taking
* Creates a new exponential backoff policy with a default configuration of 50 ms initial wait period and 8 retries taking
* roughly 5.1 seconds in total.
*
* @return A backoff policy with an exponential increase in wait time for retries. The returned instance is thread safe but each
Expand All @@ -79,7 +79,7 @@ public static BackoffPolicy exponentialBackoff() {
}

/**
* Creates an new exponential backoff policy with the provided configuration.
* Creates a new exponential backoff policy with the provided configuration.
*
* @param initialDelay The initial delay defines how long to wait for the first retry attempt. Must not be null.
* Must be &lt;= <code>Integer.MAX_VALUE</code> ms.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public class DefaultTransportOptions implements TransportOptions {
private final Map<String, String> parameters;
private final Function<List<String>, Boolean> onWarnings;
private boolean keepResponseBodyOnException;
private RetryConfig retryConfig;

public static final DefaultTransportOptions EMPTY = new DefaultTransportOptions();

Expand All @@ -51,8 +52,19 @@ public DefaultTransportOptions(
@Nullable Function<List<String>, Boolean> onWarnings,
boolean keepResponseBodyOnException
) {
this(headers,parameters,onWarnings);
this(headers, parameters, onWarnings, keepResponseBodyOnException, RetryConfig.disabled());
}

public DefaultTransportOptions(
@Nullable HeaderMap headers,
@Nullable Map<String, String> parameters,
@Nullable Function<List<String>, Boolean> onWarnings,
boolean keepResponseBodyOnException,
@Nullable RetryConfig retryConfig
) {
this(headers, parameters, onWarnings);
this.keepResponseBodyOnException = keepResponseBodyOnException;
this.retryConfig = retryConfig == null ? RetryConfig.disabled() : retryConfig;
}

public DefaultTransportOptions(
Expand All @@ -65,10 +77,11 @@ public DefaultTransportOptions(
Collections.emptyMap() : Collections.unmodifiableMap(parameters);
this.onWarnings = onWarnings;
this.keepResponseBodyOnException = false;
this.retryConfig = RetryConfig.disabled();
}

protected DefaultTransportOptions(AbstractBuilder<?> builder) {
this(builder.headers, builder.parameters, builder.onWarnings, builder.keepResponseBodyOnException);
this(builder.headers, builder.parameters, builder.onWarnings, builder.keepResponseBodyOnException, builder.retryConfig);
}

public static DefaultTransportOptions of(@Nullable TransportOptions options) {
Expand Down Expand Up @@ -111,6 +124,11 @@ public boolean keepResponseBodyOnException() {
return keepResponseBodyOnException;
}

@Override
public RetryConfig retryConfig() {
return retryConfig;
}

@Override
public Builder toBuilder() {
return new Builder(this);
Expand All @@ -135,6 +153,7 @@ public abstract static class AbstractBuilder<BuilderT extends AbstractBuilder<Bu
private Map<String, String> parameters;
private Function<List<String>, Boolean> onWarnings;
private boolean keepResponseBodyOnException;
private RetryConfig retryConfig = RetryConfig.disabled();

public AbstractBuilder() {
}
Expand All @@ -144,6 +163,7 @@ public AbstractBuilder(DefaultTransportOptions options) {
this.parameters = copyOrNull(options.parameters);
this.onWarnings = options.onWarnings;
this.keepResponseBodyOnException = options.keepResponseBodyOnException;
this.retryConfig = options.retryConfig == null ? RetryConfig.disabled() : options.retryConfig;
}

protected abstract BuilderT self();
Expand All @@ -154,6 +174,12 @@ public BuilderT keepResponseBodyOnException(boolean value) {
return self();
}

@Override
public BuilderT retryConfig(RetryConfig config) {
this.retryConfig = config == null ? RetryConfig.disabled() : config;
return self();
}

@Override
public BuilderT addHeader(String name, String value) {
if (name.equalsIgnoreCase(HeaderMap.CLIENT_META)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import co.elastic.clients.transport.endpoints.TextEndpoint;
import co.elastic.clients.transport.http.HeaderMap;
import co.elastic.clients.transport.http.RepeatableBodyResponse;
import co.elastic.clients.transport.http.RetryingHttpClient;
import co.elastic.clients.transport.http.TransportHttpClient;
import co.elastic.clients.transport.instrumentation.Instrumentation;
import co.elastic.clients.transport.instrumentation.NoopInstrumentation;
Expand Down Expand Up @@ -80,6 +81,14 @@ public abstract class ElasticsearchTransportBase implements ElasticsearchTranspo
}

protected final TransportHttpClient httpClient;
// Single retry wrapper, shared by every request that uses retries (whether configured on the client or per
// request) and reading the effective RetryConfig from each call's TransportOptions. Created lazily on first
// use, so clients that never use retries keep the exact same object graph as before and never allocate a
// retry scheduler. Creation and close() are serialized on `this` so a wrapper can't be created after close
// and leak its scheduler.
private volatile RetryingHttpClient retryingHttpClient;
// Guarded by `this`
private boolean closed;
protected final Instrumentation instrumentation;
protected final JsonpMapper mapper;
protected final TransportOptions transportOptions;
Expand Down Expand Up @@ -120,7 +129,18 @@ protected ElasticsearchTransportBase cloneWith(

@Override
public void close() throws IOException {
httpClient.close();
// The retry wrapper, if it was ever created, owns the delegate and the retry scheduler, so closing it
// also closes httpClient and shuts down the scheduler. Otherwise close httpClient directly.
RetryingHttpClient retrying;
synchronized (this) {
closed = true;
retrying = retryingHttpClient;
}
if (retrying != null) {
retrying.close();
} else {
httpClient.close();
}
}

@Override
Expand All @@ -137,6 +157,38 @@ public TransportHttpClient httpClient() {
return httpClient;
}

// Returns the shared retry wrapper when this request uses retries, the bare http client otherwise. The
// wrapper is created lazily on first use, so clients that never retry keep the non-retry path untouched.
private TransportHttpClient httpClientFor(TransportOptions options) {
RetryConfig retryConfig = options.retryConfig();
if (retryConfig == null || !retryConfig.isEnabled()) {
return httpClient;
}
// If the user supplied a client that already retries, don't wrap it a second time: both layers would
// read the same RetryConfig and multiply attempts.
if (httpClient instanceof RetryingHttpClient) {
return httpClient;
}
return retryingHttpClient();
}

private RetryingHttpClient retryingHttpClient() {
RetryingHttpClient client = retryingHttpClient;
if (client == null) {
synchronized (this) {
if (closed) {
throw new IllegalStateException("Transport has been closed");
}
client = retryingHttpClient;
if (client == null) {
client = new RetryingHttpClient(httpClient);
retryingHttpClient = client;
}
}
}
return client;
}

@Override
public final <RequestT, ResponseT, ErrorT> ResponseT performRequest(
RequestT request,
Expand All @@ -150,7 +202,7 @@ public final <RequestT, ResponseT, ErrorT> ResponseT performRequest(
TransportHttpClient.Request req = prepareTransportRequest(request, endpoint);
ctx.beforeSendingHttpRequest(req, options);

TransportHttpClient.Response resp = httpClient.performRequest(endpoint.id(), null, req, opts);
TransportHttpClient.Response resp = httpClientFor(opts).performRequest(endpoint.id(), null, req, opts);
ctx.afterReceivingHttpResponse(resp);

ResponseT apiResponse = getApiResponse(resp, endpoint);
Expand Down Expand Up @@ -189,7 +241,7 @@ public final <RequestT, ResponseT, ErrorT> CompletableFuture<ResponseT> performR
// Propagate required property checks to the thread that will decode the response
boolean disableRequiredChecks = ApiTypeHelper.requiredPropertiesCheckDisabled();

CompletableFuture<TransportHttpClient.Response> clientFuture = httpClient.performRequestAsync(
CompletableFuture<TransportHttpClient.Response> clientFuture = httpClientFor(opts).performRequestAsync(
endpoint.id(), null, clientReq, opts
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;

public abstract class ElasticsearchTransportConfig {
Expand Down Expand Up @@ -326,6 +327,20 @@ public BuilderT transportOptions(Function<TransportOptions.Builder, TransportOpt
return self();
}

/**
* Configure transport-level retries.
*/
public BuilderT retryConfig(RetryConfig retryConfig) {
return transportOptions(o -> o.retryConfig(retryConfig));
}

/**
* Configure transport-level retries with a builder lambda.
*/
public BuilderT retryConfig(Consumer<RetryConfig.Builder> fn) {
return transportOptions(o -> o.retryConfig(fn));
}

protected void checkNull(Object value, String name, String other) {
if (value != null) {
throw new IllegalArgumentException("Cannot set both " + other + " and " + name + ".");
Expand Down
Loading
Loading