Skip to content

Commit 92c533e

Browse files
authored
Suppress response headers in AllocationActionMultiListener (#93777)
Today `AllocationActionMultiListener` allows response headers to pass between listeners, but this should not be allowed. This commit stops preserving response headers. Closes #93773
1 parent a3eb70b commit 92c533e

File tree

3 files changed

+61
-39
lines changed

3 files changed

+61
-39
lines changed

docs/changelog/93777.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 93777
2+
summary: Suppress response headers in `AllocationActionMultiListener`
3+
area: Allocation
4+
type: bug
5+
issues:
6+
- 93773

server/src/main/java/org/elasticsearch/cluster/routing/allocation/allocator/AllocationActionMultiListener.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,12 @@
99
package org.elasticsearch.cluster.routing.allocation.allocator;
1010

1111
import org.elasticsearch.action.ActionListener;
12+
import org.elasticsearch.action.support.ContextPreservingActionListener;
1213
import org.elasticsearch.common.util.concurrent.ThreadContext;
1314

1415
import java.util.ArrayList;
1516
import java.util.List;
1617

17-
import static org.elasticsearch.action.support.ContextPreservingActionListener.wrapPreservingContext;
18-
1918
/**
2019
* This event listener might be needed to delay execution of multiple distinct tasks until followup reroute is complete.
2120
*/
@@ -30,7 +29,7 @@ public AllocationActionMultiListener(ThreadContext context) {
3029
}
3130

3231
public ActionListener<T> delay(ActionListener<T> delegate) {
33-
final var wrappedDelegate = wrapPreservingContext(delegate, context);
32+
final var wrappedDelegate = new ContextPreservingActionListener<>(context.newRestorableContext(false), delegate);
3433
return new ActionListener<T>() {
3534
@Override
3635
public void onResponse(T response) {

server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/AllocationActionMultiListenerTests.java

Lines changed: 53 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,22 @@
99
package org.elasticsearch.cluster.routing.allocation.allocator;
1010

1111
import org.elasticsearch.action.ActionListener;
12+
import org.elasticsearch.action.support.RefCountingRunnable;
1213
import org.elasticsearch.action.support.master.AcknowledgedResponse;
1314
import org.elasticsearch.common.settings.Settings;
1415
import org.elasticsearch.common.util.concurrent.ThreadContext;
15-
import org.elasticsearch.core.Tuple;
1616
import org.elasticsearch.test.ESTestCase;
1717
import org.elasticsearch.threadpool.TestThreadPool;
1818
import org.elasticsearch.threadpool.ThreadPool;
19+
import org.junit.Assert;
1920

21+
import java.util.ArrayList;
2022
import java.util.List;
23+
import java.util.Set;
2124
import java.util.concurrent.CountDownLatch;
2225
import java.util.concurrent.TimeUnit;
2326
import java.util.concurrent.atomic.AtomicBoolean;
2427
import java.util.concurrent.atomic.AtomicInteger;
25-
import java.util.concurrent.atomic.AtomicReference;
2628

2729
import static org.hamcrest.Matchers.equalTo;
2830

@@ -121,45 +123,60 @@ private static void awaitQuietly(CountDownLatch latch) {
121123

122124
public void testShouldExecuteWithCorrectContext() {
123125

124-
var context = new ThreadContext(Settings.EMPTY);
125-
var listener = new AllocationActionMultiListener<Integer>(context);
126-
127-
context.putHeader("header", "root");
128-
var r1 = new AtomicReference<String>();
129-
var r2 = new AtomicReference<String>();
130-
var l1 = listener.delay(
131-
ActionListener.wrap(
132-
response -> r1.set(context.getHeader("header")),
133-
exception -> { throw new AssertionError("Should not fail in test"); }
134-
)
135-
);
136-
var l2 = listener.delay(
137-
ActionListener.wrap(
138-
response -> r2.set(context.getHeader("header")),
139-
exception -> { throw new AssertionError("Should not fail in test"); }
140-
)
141-
);
126+
final var requestHeaderName = "header";
127+
final var responseHeaderName = "responseHeader";
142128

143-
executeInRandomOrder(
144-
context,
145-
List.of(
146-
new Tuple<>("clusterStateUpdate1", () -> l1.onResponse(1)),
147-
new Tuple<>("clusterStateUpdate2", () -> l2.onResponse(2)),
148-
new Tuple<>("reroute", () -> listener.reroute().onResponse(null))
149-
)
150-
);
129+
final var expectedRequestHeader = randomAlphaOfLength(10);
130+
final var expectedResponseHeader = randomAlphaOfLength(10);
151131

152-
assertThat(r1.get(), equalTo("root"));
153-
assertThat(r2.get(), equalTo("root"));
154-
}
132+
var context = new ThreadContext(Settings.EMPTY);
133+
var listener = new AllocationActionMultiListener<>(context);
134+
135+
context.putHeader(requestHeaderName, expectedRequestHeader);
136+
context.addResponseHeader(responseHeaderName, expectedResponseHeader);
137+
138+
var isComplete = new AtomicBoolean();
139+
try (var refs = new RefCountingRunnable(() -> assertTrue(isComplete.compareAndSet(false, true)))) {
140+
141+
List<Runnable> actions = new ArrayList<>();
142+
143+
for (int i = between(0, 5); i > 0; i--) {
144+
var expectedVal = new Object();
145+
var delayedListener = listener.delay(
146+
ActionListener.releaseAfter(ActionListener.wrap(Assert::fail).delegateFailure((l, val) -> {
147+
assertSame(expectedVal, val);
148+
assertEquals(expectedRequestHeader, context.getHeader(requestHeaderName));
149+
assertEquals(List.of(expectedResponseHeader), context.getResponseHeaders().get(responseHeaderName));
150+
context.addResponseHeader(responseHeaderName, randomAlphaOfLength(10));
151+
}), refs.acquire())
152+
);
153+
actions.add(() -> delayedListener.onResponse(expectedVal));
154+
}
155155

156-
private static void executeInRandomOrder(ThreadContext context, List<Tuple<String, Runnable>> actions) {
157-
for (var action : shuffledList(actions)) {
158-
try (var ignored = context.stashContext()) {
159-
context.putHeader("header", action.v1());
160-
action.v2().run();
156+
final var additionalResponseHeader = randomAlphaOfLength(10);
157+
context.addResponseHeader(responseHeaderName, additionalResponseHeader);
158+
159+
actions.add(() -> listener.reroute().onResponse(null));
160+
161+
for (var action : shuffledList(actions)) {
162+
try (var ignored = context.stashContext()) {
163+
final var localRequestHeader = randomAlphaOfLength(10);
164+
final var localResponseHeader = randomAlphaOfLength(10);
165+
context.putHeader(requestHeaderName, localRequestHeader);
166+
context.addResponseHeader(responseHeaderName, localResponseHeader);
167+
action.run();
168+
assertEquals(localRequestHeader, context.getHeader(requestHeaderName));
169+
assertEquals(List.of(localResponseHeader), context.getResponseHeaders().get(responseHeaderName));
170+
}
161171
}
172+
173+
assertEquals(
174+
Set.of(expectedResponseHeader, additionalResponseHeader),
175+
Set.copyOf(context.getResponseHeaders().get(responseHeaderName))
176+
);
162177
}
178+
179+
assertTrue(isComplete.get());
163180
}
164181

165182
private static ThreadContext createEmptyThreadContext() {

0 commit comments

Comments
 (0)