Skip to content

Commit 67bfb76

Browse files
authored
Refactor Netty4Utils#maybeDie (#33021)
In our Netty layer we have had to take extra precautions against Netty catching throwables which prevents them from reaching the uncaught exception handler. This code has taken on additional uses in NIO layer and now in the scheduler engine because there are other components in stack traces that could catch throwables and suppress them from reaching the uncaught exception handler. This commit is a simple cleanup of the iterative evolution of this code to refactor all uses into a single method in ExceptionsHelper.
1 parent ead198b commit 67bfb76

File tree

15 files changed

+76
-106
lines changed

15 files changed

+76
-106
lines changed

modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpChannel.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@
2121

2222
import io.netty.channel.Channel;
2323
import io.netty.channel.ChannelPromise;
24+
import org.elasticsearch.ExceptionsHelper;
2425
import org.elasticsearch.action.ActionListener;
2526
import org.elasticsearch.common.concurrent.CompletableContext;
2627
import org.elasticsearch.http.HttpChannel;
2728
import org.elasticsearch.http.HttpResponse;
28-
import org.elasticsearch.transport.netty4.Netty4Utils;
2929

3030
import java.net.InetSocketAddress;
3131

@@ -42,7 +42,7 @@ public class Netty4HttpChannel implements HttpChannel {
4242
} else {
4343
Throwable cause = f.cause();
4444
if (cause instanceof Error) {
45-
Netty4Utils.maybeDie(cause);
45+
ExceptionsHelper.maybeDieOnAnotherThread(cause);
4646
closeContext.completeExceptionally(new Exception(cause));
4747
} else {
4848
closeContext.completeExceptionally((Exception) cause);
@@ -59,7 +59,7 @@ public void sendResponse(HttpResponse response, ActionListener<Void> listener) {
5959
listener.onResponse(null);
6060
} else {
6161
final Throwable cause = f.cause();
62-
Netty4Utils.maybeDie(cause);
62+
ExceptionsHelper.maybeDieOnAnotherThread(cause);
6363
if (cause instanceof Error) {
6464
listener.onFailure(new Exception(cause));
6565
} else {

modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpRequestHandler.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import io.netty.handler.codec.http.FullHttpRequest;
2828
import org.elasticsearch.ExceptionsHelper;
2929
import org.elasticsearch.http.HttpPipelinedRequest;
30-
import org.elasticsearch.transport.netty4.Netty4Utils;
3130

3231
@ChannelHandler.Sharable
3332
class Netty4HttpRequestHandler extends SimpleChannelInboundHandler<HttpPipelinedRequest<FullHttpRequest>> {
@@ -58,7 +57,7 @@ protected void channelRead0(ChannelHandlerContext ctx, HttpPipelinedRequest<Full
5857
if (request.decoderResult().isFailure()) {
5958
Throwable cause = request.decoderResult().cause();
6059
if (cause instanceof Error) {
61-
ExceptionsHelper.dieOnError(cause);
60+
ExceptionsHelper.maybeDieOnAnotherThread(cause);
6261
serverTransport.incomingRequestError(httpRequest, channel, new Exception(cause));
6362
} else {
6463
serverTransport.incomingRequestError(httpRequest, channel, (Exception) cause);
@@ -74,7 +73,7 @@ protected void channelRead0(ChannelHandlerContext ctx, HttpPipelinedRequest<Full
7473

7574
@Override
7675
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
77-
Netty4Utils.maybeDie(cause);
76+
ExceptionsHelper.maybeDieOnAnotherThread(cause);
7877
Netty4HttpChannel channel = ctx.channel().attr(Netty4HttpServerTransport.HTTP_CHANNEL_KEY).get();
7978
if (cause instanceof Error) {
8079
serverTransport.onException(channel, new Exception(cause));

modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpServerChannel.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@
2020
package org.elasticsearch.http.netty4;
2121

2222
import io.netty.channel.Channel;
23+
import org.elasticsearch.ExceptionsHelper;
2324
import org.elasticsearch.action.ActionListener;
2425
import org.elasticsearch.common.concurrent.CompletableContext;
2526
import org.elasticsearch.http.HttpServerChannel;
26-
import org.elasticsearch.transport.netty4.Netty4Utils;
2727

2828
import java.net.InetSocketAddress;
2929

@@ -40,7 +40,7 @@ public class Netty4HttpServerChannel implements HttpServerChannel {
4040
} else {
4141
Throwable cause = f.cause();
4242
if (cause instanceof Error) {
43-
Netty4Utils.maybeDie(cause);
43+
ExceptionsHelper.maybeDieOnAnotherThread(cause);
4444
closeContext.completeExceptionally(new Exception(cause));
4545
} else {
4646
closeContext.completeExceptionally((Exception) cause);

modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpServerTransport.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import io.netty.handler.timeout.ReadTimeoutException;
4242
import io.netty.handler.timeout.ReadTimeoutHandler;
4343
import io.netty.util.AttributeKey;
44+
import org.elasticsearch.ExceptionsHelper;
4445
import org.elasticsearch.common.Strings;
4546
import org.elasticsearch.common.network.CloseableChannel;
4647
import org.elasticsearch.common.network.NetworkService;
@@ -338,7 +339,7 @@ protected void initChannel(Channel ch) throws Exception {
338339

339340
@Override
340341
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
341-
Netty4Utils.maybeDie(cause);
342+
ExceptionsHelper.maybeDieOnAnotherThread(cause);
342343
super.exceptionCaught(ctx, cause);
343344
}
344345
}
@@ -354,7 +355,7 @@ private ServerChannelExceptionHandler(Netty4HttpServerTransport transport) {
354355

355356
@Override
356357
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
357-
Netty4Utils.maybeDie(cause);
358+
ExceptionsHelper.maybeDieOnAnotherThread(cause);
358359
Netty4HttpServerChannel httpServerChannel = ctx.channel().attr(HTTP_SERVER_CHANNEL_KEY).get();
359360
if (cause instanceof Error) {
360361
transport.onServerException(httpServerChannel, new Exception(cause));

modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4MessageChannelHandler.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception
6868

6969
@Override
7070
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
71-
Netty4Utils.maybeDie(cause);
71+
ExceptionsHelper.maybeDieOnAnotherThread(cause);
7272
final Throwable unwrapped = ExceptionsHelper.unwrap(cause, ElasticsearchException.class);
7373
final Throwable newCause = unwrapped != null ? unwrapped : cause;
7474
Netty4TcpChannel tcpChannel = ctx.channel().attr(Netty4Transport.CHANNEL_KEY).get();

modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4TcpChannel.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import io.netty.channel.Channel;
2323
import io.netty.channel.ChannelOption;
2424
import io.netty.channel.ChannelPromise;
25+
import org.elasticsearch.ExceptionsHelper;
2526
import org.elasticsearch.action.ActionListener;
2627
import org.elasticsearch.common.bytes.BytesReference;
2728
import org.elasticsearch.common.concurrent.CompletableContext;
@@ -45,7 +46,7 @@ public class Netty4TcpChannel implements TcpChannel {
4546
} else {
4647
Throwable cause = f.cause();
4748
if (cause instanceof Error) {
48-
Netty4Utils.maybeDie(cause);
49+
ExceptionsHelper.maybeDieOnAnotherThread(cause);
4950
closeContext.completeExceptionally(new Exception(cause));
5051
} else {
5152
closeContext.completeExceptionally((Exception) cause);
@@ -97,7 +98,7 @@ public void sendMessage(BytesReference reference, ActionListener<Void> listener)
9798
listener.onResponse(null);
9899
} else {
99100
final Throwable cause = f.cause();
100-
Netty4Utils.maybeDie(cause);
101+
ExceptionsHelper.maybeDieOnAnotherThread(cause);
101102
if (cause instanceof Error) {
102103
listener.onFailure(new Exception(cause));
103104
} else {

modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4TcpServerChannel.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.elasticsearch.transport.netty4;
2121

2222
import io.netty.channel.Channel;
23+
import org.elasticsearch.ExceptionsHelper;
2324
import org.elasticsearch.action.ActionListener;
2425
import org.elasticsearch.common.concurrent.CompletableContext;
2526
import org.elasticsearch.transport.TcpServerChannel;
@@ -41,7 +42,7 @@ public class Netty4TcpServerChannel implements TcpServerChannel {
4142
} else {
4243
Throwable cause = f.cause();
4344
if (cause instanceof Error) {
44-
Netty4Utils.maybeDie(cause);
45+
ExceptionsHelper.maybeDieOnAnotherThread(cause);
4546
closeContext.completeExceptionally(new Exception(cause));
4647
} else {
4748
closeContext.completeExceptionally((Exception) cause);

modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4Transport.java

+6-5
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import io.netty.util.concurrent.Future;
3939
import org.apache.logging.log4j.message.ParameterizedMessage;
4040
import org.apache.logging.log4j.util.Supplier;
41+
import org.elasticsearch.ExceptionsHelper;
4142
import org.elasticsearch.action.ActionListener;
4243
import org.elasticsearch.cluster.node.DiscoveryNode;
4344
import org.elasticsearch.common.SuppressForbidden;
@@ -228,7 +229,7 @@ protected Netty4TcpChannel initiateChannel(DiscoveryNode node, ActionListener<Vo
228229
ChannelFuture channelFuture = bootstrap.connect(address);
229230
Channel channel = channelFuture.channel();
230231
if (channel == null) {
231-
Netty4Utils.maybeDie(channelFuture.cause());
232+
ExceptionsHelper.maybeDieOnAnotherThread(channelFuture.cause());
232233
throw new IOException(channelFuture.cause());
233234
}
234235
addClosedExceptionLogger(channel);
@@ -242,7 +243,7 @@ protected Netty4TcpChannel initiateChannel(DiscoveryNode node, ActionListener<Vo
242243
} else {
243244
Throwable cause = f.cause();
244245
if (cause instanceof Error) {
245-
Netty4Utils.maybeDie(cause);
246+
ExceptionsHelper.maybeDieOnAnotherThread(cause);
246247
listener.onFailure(new Exception(cause));
247248
} else {
248249
listener.onFailure((Exception) cause);
@@ -307,7 +308,7 @@ protected void initChannel(Channel ch) throws Exception {
307308

308309
@Override
309310
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
310-
Netty4Utils.maybeDie(cause);
311+
ExceptionsHelper.maybeDieOnAnotherThread(cause);
311312
super.exceptionCaught(ctx, cause);
312313
}
313314
}
@@ -333,7 +334,7 @@ protected void initChannel(Channel ch) throws Exception {
333334

334335
@Override
335336
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
336-
Netty4Utils.maybeDie(cause);
337+
ExceptionsHelper.maybeDieOnAnotherThread(cause);
337338
super.exceptionCaught(ctx, cause);
338339
}
339340
}
@@ -351,7 +352,7 @@ private class ServerChannelExceptionHandler extends ChannelHandlerAdapter {
351352

352353
@Override
353354
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
354-
Netty4Utils.maybeDie(cause);
355+
ExceptionsHelper.maybeDieOnAnotherThread(cause);
355356
Netty4TcpServerChannel serverChannel = ctx.channel().attr(SERVER_CHANNEL_KEY).get();
356357
if (cause instanceof Error) {
357358
onServerException(serverChannel, new Exception(cause));

modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4Utils.java

-34
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,16 @@
2727
import io.netty.util.NettyRuntime;
2828
import io.netty.util.internal.logging.InternalLogger;
2929
import io.netty.util.internal.logging.InternalLoggerFactory;
30-
import org.apache.logging.log4j.Logger;
3130
import org.apache.lucene.util.BytesRef;
3231
import org.apache.lucene.util.BytesRefIterator;
33-
import org.elasticsearch.ExceptionsHelper;
3432
import org.elasticsearch.common.Booleans;
3533
import org.elasticsearch.common.bytes.BytesReference;
36-
import org.elasticsearch.common.logging.ESLoggerFactory;
3734

3835
import java.io.IOException;
3936
import java.util.ArrayList;
4037
import java.util.Collection;
4138
import java.util.List;
4239
import java.util.Locale;
43-
import java.util.Optional;
4440
import java.util.concurrent.atomic.AtomicBoolean;
4541

4642
public class Netty4Utils {
@@ -161,34 +157,4 @@ public static void closeChannels(final Collection<Channel> channels) throws IOEx
161157
}
162158
}
163159

164-
/**
165-
* If the specified cause is an unrecoverable error, this method will rethrow the cause on a separate thread so that it can not be
166-
* caught and bubbles up to the uncaught exception handler.
167-
*
168-
* @param cause the throwable to test
169-
*/
170-
public static void maybeDie(final Throwable cause) {
171-
final Logger logger = ESLoggerFactory.getLogger(Netty4Utils.class);
172-
final Optional<Error> maybeError = ExceptionsHelper.maybeError(cause, logger);
173-
if (maybeError.isPresent()) {
174-
/*
175-
* Here be dragons. We want to rethrow this so that it bubbles up to the uncaught exception handler. Yet, Netty wraps too many
176-
* invocations of user-code in try/catch blocks that swallow all throwables. This means that a rethrow here will not bubble up
177-
* to where we want it to. So, we fork a thread and throw the exception from there where Netty can not get to it. We do not wrap
178-
* the exception so as to not lose the original cause during exit.
179-
*/
180-
try {
181-
// try to log the current stack trace
182-
final String formatted = ExceptionsHelper.formatStackTrace(Thread.currentThread().getStackTrace());
183-
logger.error("fatal error on the network layer\n{}", formatted);
184-
} finally {
185-
new Thread(
186-
() -> {
187-
throw maybeError.get();
188-
})
189-
.start();
190-
}
191-
}
192-
}
193-
194160
}

plugins/transport-nio/src/main/java/org/elasticsearch/http/nio/HttpReadWriteHandler.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ private void handleRequest(Object msg) {
139139
if (request.decoderResult().isFailure()) {
140140
Throwable cause = request.decoderResult().cause();
141141
if (cause instanceof Error) {
142-
ExceptionsHelper.dieOnError(cause);
142+
ExceptionsHelper.maybeDieOnAnotherThread(cause);
143143
transport.incomingRequestError(httpRequest, nioHttpChannel, new Exception(cause));
144144
} else {
145145
transport.incomingRequestError(httpRequest, nioHttpChannel, (Exception) cause);

plugins/transport-nio/src/main/java/org/elasticsearch/http/nio/NettyAdaptor.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public void close() throws Exception {
7373
closeFuture.await();
7474
if (closeFuture.isSuccess() == false) {
7575
Throwable cause = closeFuture.cause();
76-
ExceptionsHelper.dieOnError(cause);
76+
ExceptionsHelper.maybeDieOnAnotherThread(cause);
7777
throw (Exception) cause;
7878
}
7979
}
@@ -84,7 +84,7 @@ public void addCloseListener(BiConsumer<Void, Exception> listener) {
8484
listener.accept(null, null);
8585
} else {
8686
final Throwable cause = f.cause();
87-
ExceptionsHelper.dieOnError(cause);
87+
ExceptionsHelper.maybeDieOnAnotherThread(cause);
8888
assert cause instanceof Exception;
8989
listener.accept(null, (Exception) cause);
9090
}

plugins/transport-nio/src/main/java/org/elasticsearch/http/nio/NettyListener.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ public static NettyListener fromBiConsumer(BiConsumer<Void, Exception> biConsume
223223
biConsumer.accept(null, null);
224224
} else {
225225
if (cause instanceof Error) {
226-
ExceptionsHelper.dieOnError(cause);
226+
ExceptionsHelper.maybeDieOnAnotherThread(cause);
227227
biConsumer.accept(null, new Exception(cause));
228228
} else {
229229
biConsumer.accept(null, (Exception) cause);

qa/die-with-dignity/src/test/java/org/elasticsearch/qa/die_with_dignity/DieWithDignityIT.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -90,22 +90,22 @@ public void testDieWithDignity() throws Exception {
9090

9191
final Iterator<String> it = lines.iterator();
9292

93-
boolean fatalErrorOnTheNetworkLayer = false;
93+
boolean fatalError = false;
9494
boolean fatalErrorInThreadExiting = false;
9595

96-
while (it.hasNext() && (fatalErrorOnTheNetworkLayer == false || fatalErrorInThreadExiting == false)) {
96+
while (it.hasNext() && (fatalError == false || fatalErrorInThreadExiting == false)) {
9797
final String line = it.next();
98-
if (line.contains("fatal error on the network layer")) {
99-
fatalErrorOnTheNetworkLayer = true;
100-
} else if (line.matches(".*\\[ERROR\\]\\[o.e.b.ElasticsearchUncaughtExceptionHandler\\] \\[node-0\\]"
98+
if (line.matches(".*\\[ERROR\\]\\[o\\.e\\.ExceptionsHelper\\s*\\] \\[node-0\\] fatal error")) {
99+
fatalError = true;
100+
} else if (line.matches(".*\\[ERROR\\]\\[o\\.e\\.b\\.ElasticsearchUncaughtExceptionHandler\\] \\[node-0\\]"
101101
+ " fatal error in thread \\[Thread-\\d+\\], exiting$")) {
102102
fatalErrorInThreadExiting = true;
103103
assertTrue(it.hasNext());
104104
assertThat(it.next(), equalTo("java.lang.OutOfMemoryError: die with dignity"));
105105
}
106106
}
107107

108-
assertTrue(fatalErrorOnTheNetworkLayer);
108+
assertTrue(fatalError);
109109
assertTrue(fatalErrorInThreadExiting);
110110
}
111111

0 commit comments

Comments
 (0)