|
25 | 25 | import org.elasticsearch.common.collect.Tuple;
|
26 | 26 | import org.elasticsearch.common.io.Streams;
|
27 | 27 | import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
| 28 | +import org.elasticsearch.common.lease.Releasable; |
28 | 29 | import org.elasticsearch.common.settings.Settings;
|
29 | 30 | import org.elasticsearch.common.util.PageCacheRecycler;
|
30 | 31 | import org.elasticsearch.common.util.concurrent.ThreadContext;
|
|
34 | 35 | import java.util.ArrayList;
|
35 | 36 | import java.util.List;
|
36 | 37 | import java.util.Objects;
|
| 38 | +import java.util.concurrent.atomic.AtomicBoolean; |
37 | 39 | import java.util.function.BiConsumer;
|
38 | 40 |
|
39 | 41 | import static org.hamcrest.Matchers.instanceOf;
|
@@ -171,6 +173,53 @@ public void testPipelineHandling() throws IOException {
|
171 | 173 | }
|
172 | 174 | }
|
173 | 175 |
|
| 176 | + public void testEnsureBodyIsNotPrematurelyReleased() throws IOException { |
| 177 | + final PageCacheRecycler recycler = PageCacheRecycler.NON_RECYCLING_INSTANCE; |
| 178 | + BiConsumer<TcpChannel, InboundMessage> messageHandler = (c, m) -> {}; |
| 179 | + BiConsumer<TcpChannel, Tuple<Header, Exception>> errorHandler = (c, e) -> {}; |
| 180 | + final InboundPipeline pipeline = new InboundPipeline(Version.CURRENT, recycler, messageHandler, errorHandler); |
| 181 | + |
| 182 | + try (BytesStreamOutput streamOutput = new BytesStreamOutput()) { |
| 183 | + String actionName = "actionName"; |
| 184 | + final Version version = Version.CURRENT; |
| 185 | + final String value = randomAlphaOfLength(1000); |
| 186 | + final boolean isRequest = randomBoolean(); |
| 187 | + final long requestId = randomNonNegativeLong(); |
| 188 | + |
| 189 | + OutboundMessage message; |
| 190 | + if (isRequest) { |
| 191 | + message = new OutboundMessage.Request(threadContext, new TestRequest(value), |
| 192 | + version, actionName, requestId, false, false); |
| 193 | + } else { |
| 194 | + message = new OutboundMessage.Response(threadContext, new TestResponse(value), |
| 195 | + version, requestId, false, false); |
| 196 | + } |
| 197 | + |
| 198 | + final BytesReference reference = message.serialize(streamOutput); |
| 199 | + final int fixedHeaderSize = TcpHeader.headerSize(Version.CURRENT); |
| 200 | + final int variableHeaderSize = reference.getInt(fixedHeaderSize - 4); |
| 201 | + final int totalHeaderSize = fixedHeaderSize + variableHeaderSize; |
| 202 | + final AtomicBoolean bodyReleased = new AtomicBoolean(false); |
| 203 | + for (int i = 0; i < totalHeaderSize - 1; ++i) { |
| 204 | + try (ReleasableBytesReference slice = ReleasableBytesReference.wrap(reference.slice(i, 1))) { |
| 205 | + pipeline.handleBytes(new FakeTcpChannel(), slice); |
| 206 | + } |
| 207 | + } |
| 208 | + |
| 209 | + final Releasable releasable = () -> bodyReleased.set(true); |
| 210 | + final int from = totalHeaderSize - 1; |
| 211 | + final BytesReference partHeaderPartBody = reference.slice(from, reference.length() - from - 1); |
| 212 | + try (ReleasableBytesReference slice = new ReleasableBytesReference(partHeaderPartBody, releasable)) { |
| 213 | + pipeline.handleBytes(new FakeTcpChannel(), slice); |
| 214 | + } |
| 215 | + assertFalse(bodyReleased.get()); |
| 216 | + try (ReleasableBytesReference slice = new ReleasableBytesReference(reference.slice(reference.length() - 1, 1), releasable)) { |
| 217 | + pipeline.handleBytes(new FakeTcpChannel(), slice); |
| 218 | + } |
| 219 | + assertTrue(bodyReleased.get()); |
| 220 | + } |
| 221 | + } |
| 222 | + |
174 | 223 | private static class MessageData {
|
175 | 224 |
|
176 | 225 | private final Version version;
|
|
0 commit comments