Skip to content

Commit 4c63481

Browse files
authored
Fixed ValuesBytesRefGroupingAggregator CB leak (#119121) (#119211)
- Fixed a leak if the breaker breaks on `ValuesBytesRefGroupingAggregator` - Improved aggregators Cranky test to show a better error with the failing stacktrace
1 parent 2f865a6 commit 4c63481

File tree

5 files changed

+156
-6
lines changed

5 files changed

+156
-6
lines changed

x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/aggregation/ValuesBytesRefAggregator.java

Lines changed: 14 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/X-ValuesAggregator.java.st

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,20 @@ $endif$
244244
$if(long||double)$
245245
values = new LongLongHash(1, bigArrays);
246246
$elseif(BytesRef)$
247-
values = new LongLongHash(1, bigArrays);
248-
bytes = new BytesRefHash(1, bigArrays);
247+
LongLongHash _values = null;
248+
BytesRefHash _bytes = null;
249+
try {
250+
_values = new LongLongHash(1, bigArrays);
251+
_bytes = new BytesRefHash(1, bigArrays);
252+
253+
values = _values;
254+
bytes = _bytes;
255+
256+
_values = null;
257+
_bytes = null;
258+
} finally {
259+
Releasables.closeExpectNoException(_values, _bytes);
260+
}
249261
$elseif(int||float)$
250262
values = new LongHash(1, bigArrays);
251263
$endif$
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.compute.aggregation;
9+
10+
import org.apache.lucene.util.BytesRef;
11+
import org.elasticsearch.compute.data.Block;
12+
import org.elasticsearch.compute.data.BlockFactory;
13+
import org.elasticsearch.compute.data.BlockUtils;
14+
import org.elasticsearch.compute.operator.SequenceBytesRefBlockSourceOperator;
15+
import org.elasticsearch.compute.operator.SourceOperator;
16+
17+
import java.util.Arrays;
18+
import java.util.List;
19+
import java.util.TreeSet;
20+
import java.util.stream.Collectors;
21+
import java.util.stream.IntStream;
22+
23+
import static org.hamcrest.Matchers.containsInAnyOrder;
24+
25+
public class ValuesBytesRefAggregatorFunctionTests extends AggregatorFunctionTestCase {
26+
@Override
27+
protected SourceOperator simpleInput(BlockFactory blockFactory, int size) {
28+
return new SequenceBytesRefBlockSourceOperator(
29+
blockFactory,
30+
IntStream.range(0, size).mapToObj(l -> new BytesRef(randomAlphaOfLengthBetween(0, 100)))
31+
);
32+
}
33+
34+
@Override
35+
protected AggregatorFunctionSupplier aggregatorFunction(List<Integer> inputChannels) {
36+
return new ValuesBytesRefAggregatorFunctionSupplier(inputChannels);
37+
}
38+
39+
@Override
40+
protected String expectedDescriptionOfAggregator() {
41+
return "values of bytes";
42+
}
43+
44+
@Override
45+
public void assertSimpleOutput(List<Block> input, Block result) {
46+
TreeSet<?> set = new TreeSet<>((List<?>) BlockUtils.toJavaObject(result, 0));
47+
Object[] values = input.stream()
48+
.flatMap(AggregatorFunctionTestCase::allBytesRefs)
49+
.collect(Collectors.toSet())
50+
.toArray(Object[]::new);
51+
if (false == set.containsAll(Arrays.asList(values))) {
52+
assertThat(set, containsInAnyOrder(values));
53+
}
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.compute.aggregation;
9+
10+
import org.apache.lucene.util.BytesRef;
11+
import org.elasticsearch.compute.data.Block;
12+
import org.elasticsearch.compute.data.BlockFactory;
13+
import org.elasticsearch.compute.data.BlockUtils;
14+
import org.elasticsearch.compute.data.Page;
15+
import org.elasticsearch.compute.operator.LongBytesRefTupleBlockSourceOperator;
16+
import org.elasticsearch.compute.operator.SourceOperator;
17+
import org.elasticsearch.core.Tuple;
18+
19+
import java.util.Arrays;
20+
import java.util.List;
21+
import java.util.TreeSet;
22+
import java.util.stream.Collectors;
23+
import java.util.stream.IntStream;
24+
25+
import static org.hamcrest.Matchers.containsInAnyOrder;
26+
import static org.hamcrest.Matchers.equalTo;
27+
import static org.hamcrest.Matchers.nullValue;
28+
29+
public class ValuesBytesRefGroupingAggregatorFunctionTests extends GroupingAggregatorFunctionTestCase {
30+
@Override
31+
protected AggregatorFunctionSupplier aggregatorFunction(List<Integer> inputChannels) {
32+
return new ValuesBytesRefAggregatorFunctionSupplier(inputChannels);
33+
}
34+
35+
@Override
36+
protected String expectedDescriptionOfAggregator() {
37+
return "values of bytes";
38+
}
39+
40+
@Override
41+
protected SourceOperator simpleInput(BlockFactory blockFactory, int size) {
42+
return new LongBytesRefTupleBlockSourceOperator(
43+
blockFactory,
44+
IntStream.range(0, size).mapToObj(l -> Tuple.tuple(randomLongBetween(0, 4), new BytesRef(randomAlphaOfLengthBetween(0, 100))))
45+
);
46+
}
47+
48+
@Override
49+
public void assertSimpleGroup(List<Page> input, Block result, int position, Long group) {
50+
Object[] values = input.stream().flatMap(p -> allBytesRefs(p, group)).collect(Collectors.toSet()).toArray(Object[]::new);
51+
Object resultValue = BlockUtils.toJavaObject(result, position);
52+
switch (values.length) {
53+
case 0 -> assertThat(resultValue, nullValue());
54+
case 1 -> assertThat(resultValue, equalTo(values[0]));
55+
default -> {
56+
TreeSet<?> set = new TreeSet<>((List<?>) resultValue);
57+
if (false == set.containsAll(Arrays.asList(values))) {
58+
assertThat(set, containsInAnyOrder(values));
59+
}
60+
}
61+
}
62+
}
63+
}

x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/OperatorTestCase.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,23 +143,31 @@ public final void testSimpleWithCranky() {
143143

144144
DriverContext driverContext = crankyDriverContext();
145145

146+
Exception exception = null;
146147
boolean driverStarted = false;
147148
try {
148149
Operator operator = simple().get(driverContext);
149150
driverStarted = true;
150151
drive(operator, input.iterator(), driverContext);
151152
// Either we get lucky and cranky doesn't throw and the test completes or we don't and it throws
152153
} catch (CircuitBreakingException e) {
153-
logger.info("broken", e);
154154
assertThat(e.getMessage(), equalTo(CrankyCircuitBreakerService.ERROR_MESSAGE));
155+
exception = e;
155156
}
156157
if (driverStarted == false) {
157158
// if drive hasn't even started then we need to release the input pages
158159
Releasables.closeExpectNoException(Releasables.wrap(() -> Iterators.map(input.iterator(), p -> p::releaseBlocks)));
159160
}
160161

161162
// Note the lack of try/finally here - we're asserting that when the driver throws an exception we clear the breakers.
162-
assertThat(inputFactoryContext.breaker().getUsed(), equalTo(0L));
163+
long inputUsedBytes = inputFactoryContext.breaker().getUsed();
164+
if (inputUsedBytes != 0L) {
165+
fail(exception, "Expected no used bytes for input, found: " + inputUsedBytes);
166+
}
167+
long driverUsedBytes = driverContext.breaker().getUsed();
168+
if (driverUsedBytes != 0L) {
169+
fail(exception, "Expected no used bytes for driver, found: " + driverUsedBytes);
170+
}
163171
}
164172

165173
/**

0 commit comments

Comments
 (0)