|
|
@@ -29,6 +29,7 @@ import org.apache.logging.log4j.LogManager;
|
|
|
import org.apache.logging.log4j.Logger;
|
|
|
import org.elasticsearch.ExceptionsHelper;
|
|
|
import org.elasticsearch.action.ActionListener;
|
|
|
+import org.elasticsearch.action.support.ContextPreservingActionListener;
|
|
|
import org.elasticsearch.common.Strings;
|
|
|
import org.elasticsearch.common.bytes.ReleasableBytesReference;
|
|
|
import org.elasticsearch.common.network.ThreadWatchdog;
|
|
|
@@ -271,45 +272,59 @@ public class Netty4HttpPipeliningHandler extends ChannelDuplexHandler {
|
|
|
writeSequence++;
|
|
|
finishingWrite.combiner().finish(finishingWrite.onDone());
|
|
|
} else {
|
|
|
+ final var threadContext = serverTransport.getThreadPool().getThreadContext();
|
|
|
+ assert Transports.assertDefaultThreadContext(threadContext);
|
|
|
final var channel = finishingWrite.onDone().channel();
|
|
|
- ActionListener.run(ActionListener.assertOnce(new ActionListener<>() {
|
|
|
- @Override
|
|
|
- public void onResponse(ChunkedRestResponseBodyPart continuation) {
|
|
|
- channel.writeAndFlush(
|
|
|
- new Netty4ChunkedHttpContinuation(writeSequence, continuation, finishingWrite.combiner()),
|
|
|
- finishingWrite.onDone() // pass the terminal listener/promise along the line
|
|
|
- );
|
|
|
- checkShutdown();
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void onFailure(Exception e) {
|
|
|
- logger.error(
|
|
|
- Strings.format("failed to get continuation of HTTP response body for [%s], closing connection", channel),
|
|
|
- e
|
|
|
- );
|
|
|
- channel.close().addListener(ignored -> {
|
|
|
- finishingWrite.combiner().add(channel.newFailedFuture(e));
|
|
|
- finishingWrite.combiner().finish(finishingWrite.onDone());
|
|
|
- });
|
|
|
- checkShutdown();
|
|
|
- }
|
|
|
-
|
|
|
- private void checkShutdown() {
|
|
|
- if (channel.eventLoop().isShuttingDown()) {
|
|
|
- // The event loop is shutting down, and https://github.com/netty/netty/issues/8007 means that we cannot know if the
|
|
|
- // preceding activity made it onto its queue before shutdown or whether it will just vanish without a trace, so
|
|
|
- // to avoid a leak we must double-check that the final listener is completed once the event loop is terminated.
|
|
|
- // Note that the final listener came from Netty4Utils#safeWriteAndFlush so its executor is an ImmediateEventExecutor
|
|
|
- // which means this completion is not subject to the same issue, it still works even if the event loop has already
|
|
|
- // terminated.
|
|
|
- channel.eventLoop()
|
|
|
- .terminationFuture()
|
|
|
- .addListener(ignored -> finishingWrite.onDone().tryFailure(new ClosedChannelException()));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- }), finishingWriteBodyPart::getNextPart);
|
|
|
+ ActionListener.run(
|
|
|
+ new ContextPreservingActionListener<>(
|
|
|
+ threadContext.newRestorableContext(false),
|
|
|
+ ActionListener.assertOnce(new ActionListener<>() {
|
|
|
+ @Override
|
|
|
+ public void onResponse(ChunkedRestResponseBodyPart continuation) {
|
|
|
+ // always fork a fresh task to avoid stack overflow
|
|
|
+ assert Transports.assertDefaultThreadContext(threadContext);
|
|
|
+ channel.eventLoop()
|
|
|
+ .execute(
|
|
|
+ () -> channel.writeAndFlush(
|
|
|
+ new Netty4ChunkedHttpContinuation(writeSequence, continuation, finishingWrite.combiner()),
|
|
|
+ finishingWrite.onDone() // pass the terminal listener/promise along the line
|
|
|
+ )
|
|
|
+ );
|
|
|
+ checkShutdown();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onFailure(Exception e) {
|
|
|
+ assert Transports.assertDefaultThreadContext(threadContext);
|
|
|
+ logger.error(
|
|
|
+ Strings.format("failed to get continuation of HTTP response body for [%s], closing connection", channel),
|
|
|
+ e
|
|
|
+ );
|
|
|
+ channel.close().addListener(ignored -> {
|
|
|
+ finishingWrite.combiner().add(channel.newFailedFuture(e));
|
|
|
+ finishingWrite.combiner().finish(finishingWrite.onDone());
|
|
|
+ });
|
|
|
+ checkShutdown();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void checkShutdown() {
|
|
|
+ if (channel.eventLoop().isShuttingDown()) {
|
|
|
+ // The event loop is shutting down, and https://github.com/netty/netty/issues/8007 means that we cannot know
|
|
|
+ // if the preceding activity made it onto its queue before shutdown or whether it will just vanish without a
|
|
|
+ // trace, so to avoid a leak we must double-check that the final listener is completed once the event loop
|
|
|
+ // is terminated. Note that the final listener came from Netty4Utils#safeWriteAndFlush so its executor is an
|
|
|
+ // ImmediateEventExecutor which means this completion is not subject to the same issue, it still works even
|
|
|
+ // if the event loop has already terminated.
|
|
|
+ channel.eventLoop()
|
|
|
+ .terminationFuture()
|
|
|
+ .addListener(ignored -> finishingWrite.onDone().tryFailure(new ClosedChannelException()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ })
|
|
|
+ ),
|
|
|
+ finishingWriteBodyPart::getNextPart
|
|
|
+ );
|
|
|
}
|
|
|
}
|
|
|
|