Skip to content

Commit 9e9bfd0

Browse files
authored
Remove ThreadLocal from RequestHandlerRetryAdvice (#8650)
Related to #8644 With virtual threads it is recommended to stay away from `ThreadLocal` variables to avoid memory exhausting with too many virtual threads * Fix `RequestHandlerRetryAdvice` to transfer the message context via internal `IntegrationRetryCallback` implementation. * Cast to this `IntegrationRetryCallback` in a newly introduced internal `IntegrationRetryListener` to extract `messageToTry` and set it into a `RetryContext` `ErrorMessageUtils.FAILED_MESSAGE_CONTEXT_KEY` attribute * Deprecate a usage of an external `RetryListener` implementation of the `RequestHandlerRetryAdvice`
1 parent 8a29c1e commit 9e9bfd0

File tree

1 file changed

+50
-27
lines changed

1 file changed

+50
-27
lines changed

spring-integration-core/src/main/java/org/springframework/integration/handler/advice/RequestHandlerRetryAdvice.java

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -43,14 +43,14 @@
4343
public class RequestHandlerRetryAdvice extends AbstractRequestHandlerAdvice
4444
implements RetryListener {
4545

46-
private static final ThreadLocal<Message<?>> MESSAGE_HOLDER = new ThreadLocal<>();
46+
private static final IntegrationRetryListener INTEGRATION_RETRY_LISTENER = new IntegrationRetryListener();
4747

4848
private RetryTemplate retryTemplate = new RetryTemplate();
4949

5050
private RecoveryCallback<Object> recoveryCallback;
5151

5252
// Stateless unless a state generator is provided
53-
private volatile RetryStateGenerator retryStateGenerator = message -> null;
53+
private RetryStateGenerator retryStateGenerator = message -> null;
5454

5555
/**
5656
* Set the retry template. Cause traversal should be enabled in the retry policy
@@ -74,48 +74,71 @@ public void setRetryStateGenerator(RetryStateGenerator retryStateGenerator) {
7474
@Override
7575
protected void onInit() {
7676
super.onInit();
77-
this.retryTemplate.registerListener(this);
77+
this.retryTemplate.registerListener(INTEGRATION_RETRY_LISTENER);
7878
}
7979

8080
@Override
81-
protected Object doInvoke(final ExecutionCallback callback, Object target, final Message<?> message) {
81+
protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) {
82+
IntegrationRetryCallback retryCallback = new IntegrationRetryCallback(message, callback);
8283
RetryState retryState = this.retryStateGenerator.determineRetryState(message);
83-
MESSAGE_HOLDER.set(message);
84-
8584
try {
86-
return this.retryTemplate.execute(context -> callback.cloneAndExecute(), this.recoveryCallback, retryState);
85+
return this.retryTemplate.execute(retryCallback, this.recoveryCallback, retryState);
8786
}
88-
catch (MessagingException e) {
89-
if (e.getFailedMessage() == null) {
90-
throw new MessagingException(message, "Failed to invoke handler", e);
87+
catch (MessagingException ex) {
88+
if (ex.getFailedMessage() == null) {
89+
throw new MessagingException(message, "Failed to invoke handler", ex);
9190
}
92-
throw e;
93-
}
94-
catch (ThrowableHolderException e) { // NOSONAR catch and rethrow
95-
throw e;
91+
throw ex;
9692
}
97-
catch (Exception e) {
98-
throw new ThrowableHolderException(e);
93+
catch (ThrowableHolderException ex) { // NOSONAR catch and rethrow
94+
throw ex;
9995
}
100-
finally {
101-
MESSAGE_HOLDER.remove();
96+
catch (Exception ex) {
97+
throw new ThrowableHolderException(ex);
10298
}
10399
}
104100

101+
/**
102+
* Set a {@link ErrorMessageUtils#FAILED_MESSAGE_CONTEXT_KEY} attribute into context.
103+
* @param context the current {@link RetryContext}.
104+
* @param callback the current {@link RetryCallback}.
105+
* @param <T> the type of object returned by the callback
106+
* @param <E> the type of exception it declares may be thrown
107+
* @return the open state.
108+
* @deprecated since 6.2 in favor of an internal {@link RetryListener} implementation.
109+
* The {@link RequestHandlerRetryAdvice} must not be used as a listener for external {@link RetryTemplate}
110+
* instances.
111+
*/
112+
@Deprecated(since = "6.2", forRemoval = true)
105113
@Override
106114
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
107-
context.setAttribute(ErrorMessageUtils.FAILED_MESSAGE_CONTEXT_KEY, MESSAGE_HOLDER.get());
108-
return true;
115+
return INTEGRATION_RETRY_LISTENER.open(context, callback);
109116
}
110117

111-
@Override
112-
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback,
113-
Throwable throwable) {
118+
private static class IntegrationRetryListener implements RetryListener {
119+
120+
IntegrationRetryListener() {
121+
}
122+
123+
@Override
124+
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
125+
Assert.state(callback instanceof IntegrationRetryCallback,
126+
"A 'RequestHandlerRetryAdvice' cannot be used as a 'RetryListener'");
127+
context.setAttribute(ErrorMessageUtils.FAILED_MESSAGE_CONTEXT_KEY,
128+
((IntegrationRetryCallback) callback).messageToTry);
129+
return true;
130+
}
131+
114132
}
115133

116-
@Override
117-
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback,
118-
Throwable throwable) {
134+
private record IntegrationRetryCallback(Message<?> messageToTry, ExecutionCallback callback)
135+
implements RetryCallback<Object, Exception> {
136+
137+
@Override
138+
public Object doWithRetry(RetryContext context) {
139+
return this.callback.cloneAndExecute();
140+
}
141+
119142
}
120143

121144
}

0 commit comments

Comments
 (0)