Skip to content

Commit 1866299

Browse files
authored
Remove HTTP content copies (#117303)
1 parent b22d185 commit 1866299

File tree

32 files changed

+141
-145
lines changed

32 files changed

+141
-145
lines changed

build-tools-internal/src/main/resources/forbidden/es-server-signatures.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,5 +167,3 @@ org.elasticsearch.cluster.SnapshotDeletionsInProgress$Entry#<init>(java.lang.Str
167167
@defaultMessage Use a Thread constructor with a name, anonymous threads are more difficult to debug
168168
java.lang.Thread#<init>(java.lang.Runnable)
169169
java.lang.Thread#<init>(java.lang.ThreadGroup, java.lang.Runnable)
170-
171-
org.elasticsearch.common.bytes.BytesReference#copyBytes(org.elasticsearch.common.bytes.BytesReference) @ This method is a subject for removal. Copying bytes is prone to performance regressions and unnecessary allocations.

docs/changelog/117303.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 117303
2+
summary: Remove HTTP content copies
3+
area: Network
4+
type: enhancement
5+
issues: []

modules/transport-netty4/src/internalClusterTest/java/org/elasticsearch/http/netty4/Netty4TrashingAllocatorIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public List<Route> routes() {
8989

9090
@Override
9191
protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
92-
var content = request.releasableContent();
92+
var content = request.content();
9393
var iter = content.iterator();
9494
return (chan) -> {
9595
request.getHttpRequest().release();

qa/system-indices/src/main/java/org/elasticsearch/system/indices/SystemIndicesQA.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
package org.elasticsearch.system.indices;
1212

13+
import org.elasticsearch.action.ActionListener;
1314
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
1415
import org.elasticsearch.action.index.IndexRequest;
1516
import org.elasticsearch.client.internal.node.NodeClient;
@@ -177,12 +178,12 @@ public List<Route> routes() {
177178

178179
@Override
179180
protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
181+
var content = request.requiredContent();
180182
IndexRequest indexRequest = new IndexRequest(".net-new-system-index-primary");
181-
indexRequest.source(request.requiredContent(), request.getXContentType());
183+
indexRequest.source(content, request.getXContentType());
182184
indexRequest.id(request.param("id"));
183185
indexRequest.setRefreshPolicy(request.param("refresh"));
184-
185-
return channel -> client.index(indexRequest, new RestToXContentListener<>(channel));
186+
return channel -> client.index(indexRequest, ActionListener.withRef(new RestToXContentListener<>(channel), content));
186187
}
187188

188189
@Override

server/src/main/java/org/elasticsearch/action/ActionListener.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,4 +475,12 @@ static <T, R extends AutoCloseable> void runWithResource(
475475
ActionListener.run(ActionListener.runBefore(listener, resource::close), l -> action.accept(l, resource));
476476
}
477477

478+
/**
479+
* Increments ref count and returns a listener that will decrement ref count on listener completion.
480+
*/
481+
static <Response> ActionListener<Response> withRef(ActionListener<Response> listener, RefCounted ref) {
482+
ref.mustIncRef();
483+
return releaseAfter(listener, ref::decRef);
484+
}
485+
478486
}

server/src/main/java/org/elasticsearch/common/bytes/BytesReference.java

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -74,29 +74,6 @@ static ByteBuffer[] toByteBuffers(BytesReference reference) {
7474
}
7575
}
7676

77-
/**
78-
* Allocates new buffer and copy bytes from given BytesReference.
79-
*
80-
* @deprecated copying bytes is a right place for performance regression and unnecessary allocations.
81-
* This method exists to serve very few places that struggle to handle reference counted buffers.
82-
*/
83-
@Deprecated(forRemoval = true)
84-
static BytesReference copyBytes(BytesReference bytesReference) {
85-
byte[] arr = new byte[bytesReference.length()];
86-
int offset = 0;
87-
final BytesRefIterator iterator = bytesReference.iterator();
88-
try {
89-
BytesRef slice;
90-
while ((slice = iterator.next()) != null) {
91-
System.arraycopy(slice.bytes, slice.offset, arr, offset, slice.length);
92-
offset += slice.length;
93-
}
94-
return new BytesArray(arr);
95-
} catch (IOException e) {
96-
throw new AssertionError(e);
97-
}
98-
}
99-
10077
/**
10178
* Returns BytesReference composed of the provided ByteBuffers.
10279
*/

server/src/main/java/org/elasticsearch/http/HttpTracer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ HttpTracer maybeLogRequest(RestRequest restRequest, @Nullable Exception e) {
9494

9595
private void logFullContent(RestRequest restRequest) {
9696
try (var stream = HttpBodyTracer.getBodyOutputStream(restRequest.getRequestId(), HttpBodyTracer.Type.REQUEST)) {
97-
restRequest.releasableContent().writeTo(stream);
97+
restRequest.content().writeTo(stream);
9898
} catch (Exception e2) {
9999
assert false : e2; // no real IO here
100100
}

server/src/main/java/org/elasticsearch/rest/RestRequest.java

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import org.elasticsearch.core.CheckedConsumer;
2424
import org.elasticsearch.core.Nullable;
2525
import org.elasticsearch.core.RestApiVersion;
26-
import org.elasticsearch.core.SuppressForbidden;
2726
import org.elasticsearch.core.TimeValue;
2827
import org.elasticsearch.core.Tuple;
2928
import org.elasticsearch.http.HttpBody;
@@ -303,22 +302,13 @@ public boolean isFullContent() {
303302
return httpRequest.body().isFull();
304303
}
305304

306-
/**
307-
* Returns a copy of HTTP content. The copy is GC-managed and does not require reference counting.
308-
* Please use {@link #releasableContent()} to avoid content copy.
309-
*/
310-
@SuppressForbidden(reason = "temporarily support content copy while migrating RestHandlers to ref counted pooled buffers")
311-
public BytesReference content() {
312-
return BytesReference.copyBytes(releasableContent());
313-
}
314-
315305
/**
316306
* Returns a direct reference to the network buffer containing the request body. The HTTP layers will release their references to this
317307
* buffer as soon as they have finished the synchronous steps of processing the request on the network thread, which will by default
318308
* release the buffer back to the pool where it may be re-used for another request. If you need to keep the buffer alive past the end of
319309
* these synchronous steps, acquire your own reference to this buffer and release it once it's no longer needed.
320310
*/
321-
public ReleasableBytesReference releasableContent() {
311+
public ReleasableBytesReference content() {
322312
this.contentConsumed = true;
323313
var bytes = httpRequest.body().asFull().bytes();
324314
if (bytes.hasReferences() == false) {
@@ -338,32 +328,19 @@ public HttpBody.Stream contentStream() {
338328
return httpRequest.body().asStream();
339329
}
340330

341-
private void ensureContent() {
331+
/**
332+
* Returns reference to the network buffer of HTTP content or throw an exception if the body or content type is missing.
333+
* See {@link #content()}.
334+
*/
335+
public ReleasableBytesReference requiredContent() {
342336
if (hasContent() == false) {
343337
throw new ElasticsearchParseException("request body is required");
344338
} else if (xContentType.get() == null) {
345339
throwValidationException("unknown content type");
346340
}
347-
}
348-
349-
/**
350-
* @return copy of the request body or throw an exception if the body or content type is missing.
351-
* See {@link #content()}. Please use {@link #requiredReleasableContent()} to avoid content copy.
352-
*/
353-
public final BytesReference requiredContent() {
354-
ensureContent();
355341
return content();
356342
}
357343

358-
/**
359-
* Returns reference to the network buffer of HTTP content or throw an exception if the body or content type is missing.
360-
* See {@link #releasableContent()}. It's a recommended method to handle HTTP content without copying it.
361-
*/
362-
public ReleasableBytesReference requiredReleasableContent() {
363-
ensureContent();
364-
return releasableContent();
365-
}
366-
367344
private static void throwValidationException(String msg) {
368345
ValidationException unknownContentType = new ValidationException();
369346
unknownContentType.addValidationError(msg);
@@ -596,7 +573,7 @@ public final boolean hasContentOrSourceParam() {
596573
* if you need to handle the absence request content gracefully.
597574
*/
598575
public final XContentParser contentOrSourceParamParser() throws IOException {
599-
Tuple<XContentType, BytesReference> tuple = contentOrSourceParam();
576+
Tuple<XContentType, ReleasableBytesReference> tuple = contentOrSourceParam();
600577
return XContentHelper.createParserNotCompressed(parserConfig, tuple.v2(), tuple.v1().xContent().type());
601578
}
602579

@@ -607,7 +584,7 @@ public final XContentParser contentOrSourceParamParser() throws IOException {
607584
*/
608585
public final void withContentOrSourceParamParserOrNull(CheckedConsumer<XContentParser, IOException> withParser) throws IOException {
609586
if (hasContentOrSourceParam()) {
610-
Tuple<XContentType, BytesReference> tuple = contentOrSourceParam();
587+
Tuple<XContentType, ReleasableBytesReference> tuple = contentOrSourceParam();
611588
try (XContentParser parser = XContentHelper.createParserNotCompressed(parserConfig, tuple.v2(), tuple.v1())) {
612589
withParser.accept(parser);
613590
}
@@ -620,7 +597,7 @@ public final void withContentOrSourceParamParserOrNull(CheckedConsumer<XContentP
620597
* Get the content of the request or the contents of the {@code source} param or throw an exception if both are missing.
621598
* Prefer {@link #contentOrSourceParamParser()} or {@link #withContentOrSourceParamParserOrNull(CheckedConsumer)} if you need a parser.
622599
*/
623-
public final Tuple<XContentType, BytesReference> contentOrSourceParam() {
600+
public final Tuple<XContentType, ReleasableBytesReference> contentOrSourceParam() {
624601
if (hasContentOrSourceParam() == false) {
625602
throw new ElasticsearchParseException("request body or source parameter is required");
626603
} else if (hasContent()) {
@@ -636,7 +613,7 @@ public final Tuple<XContentType, BytesReference> contentOrSourceParam() {
636613
if (xContentType == null) {
637614
throwValidationException("Unknown value for source_content_type [" + typeParam + "]");
638615
}
639-
return new Tuple<>(xContentType, bytes);
616+
return new Tuple<>(xContentType, ReleasableBytesReference.wrap(bytes));
640617
}
641618

642619
public ParsedMediaType getParsedAccept() {

server/src/main/java/org/elasticsearch/rest/RestRequestFilter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ public boolean hasContent() {
4545
}
4646

4747
@Override
48-
public ReleasableBytesReference releasableContent() {
48+
public ReleasableBytesReference content() {
4949
if (filteredBytes == null) {
5050
Tuple<XContentType, Map<String, Object>> result = XContentHelper.convertToMap(
51-
restRequest.requiredReleasableContent(),
51+
restRequest.requiredContent(),
5252
true,
5353
restRequest.getXContentType()
5454
);

server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestPutStoredScriptAction.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99
package org.elasticsearch.rest.action.admin.cluster;
1010

11+
import org.elasticsearch.action.ActionListener;
1112
import org.elasticsearch.action.admin.cluster.storedscripts.PutStoredScriptRequest;
1213
import org.elasticsearch.action.admin.cluster.storedscripts.TransportPutStoredScriptAction;
1314
import org.elasticsearch.client.internal.node.NodeClient;
@@ -57,6 +58,10 @@ public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client
5758
request.getXContentType(),
5859
StoredScriptSource.parse(content, xContentType)
5960
);
60-
return channel -> client.execute(TransportPutStoredScriptAction.TYPE, putRequest, new RestToXContentListener<>(channel));
61+
return channel -> client.execute(
62+
TransportPutStoredScriptAction.TYPE,
63+
putRequest,
64+
ActionListener.withRef(new RestToXContentListener<>(channel), content)
65+
);
6166
}
6267
}

0 commit comments

Comments
 (0)