Repository navigation
Conversation
With topology.tuple.deserialization.strict.enable set, a decode failure is rethrown from DeserializingConnectionCallback.recv and reaches StormServerHandler.exceptionCaught, which swallows anything with an IOException in its cause chain. Failures from a corrupt or oversized compressed frame wrap an IOException, so they closed the connection and dropped the batch instead of terminating the worker. Strict-mode failures are now wrapped in FatalDeserializationException, and StormServerHandler does not apply the IOException exemption to it. Genuine connection errors keep the existing handling. Fixes apache#9157
6088ef9 to
6ae03e4
Compare
GGraziadei
left a comment
There was a problem hiding this comment.
Hi @rzo1, thanks!
Just some small changes required. Some of them identified with the use of Opus, others manually.
FatalDeserializationException is now checked at the top of StormServerHandler.exceptionCaught instead of going through an empty allowed-exception set and the catch (Error) block. The worker is terminated from a separate thread. Calling exit() on the netty event loop thread blocks forever, because the worker shutdown hook closes the netty server and waits for that same event loop to finish. The tests now call exceptionCaught itself, with the terminator injected.
GGraziadei
left a comment
There was a problem hiding this comment.
Hi @rzo1 thanks, some changes required.
|
|
||
| @Override | ||
| public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { | ||
| if (cause instanceof FatalDeserializationException) { |
There was a problem hiding this comment.
Hi @rzo1 what do you think about using the Utils API?
Utils.exceptionCauseIsInstanceOf(FatalDeserializationException.class, cause)
This API checks on the whole chain, internally has an unwrap method.
| public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { | ||
| if (cause instanceof FatalDeserializationException) { | ||
| // Strict deserialization mode: fatal even if the failure wraps an IOException. | ||
| LOG.error("Strict deserialization failure, terminating worker", cause); |
There was a problem hiding this comment.
What about extending the diagnostic in this way?
try {
LOG.error("Strict deserialization failure from {}, terminating worker",
ctx.channel().remoteAddress(), cause);
} catch (Throwable err) {
// nop
}
| // Exit from a separate thread: the worker shutdown hook closes the netty server, which waits for this | ||
| // event loop to finish, so calling exit() on the event loop thread itself would never return. | ||
| private static void terminateWorker() { | ||
| Thread exitThread = new Thread(() -> Runtime.getRuntime().exit(1), "netty-server-exit"); |
There was a problem hiding this comment.
What about adding an atomic barrier to prevent multiple termination threads from being spawned?
A CAS operation could be used coupled with an atomic isTerminating flag.
if (! isTerminating.compareAndSet(false, true)) {
return;
}
| } catch (Exception e) { | ||
| if (strictMode || !isToleratedDeserializationFailure(e)) { | ||
| if (strictMode) { | ||
| throw new FatalDeserializationException("Failed to deserialize a message of " + message.message().length |
There was a problem hiding this comment.
Please prevent NPE on message.message()
byte[] payload = message.message();
try {
// deserialize the payload ...
} catch (Exception e) {
if (strictMode) {
int size = payload == null ? -1 : payload.length;
throw new FatalDeserializationException("Failed to deserialize a message of " + size
+ " bytes destined for task " + message.task(), e);
}
...
}
|
Maybe just push your changes on the branch @GGraziadei ;-) |
|
Thanks @GGraziadei ! Changes lgtm |
Fixes #9157.
Follow-up to #9094.
Problem
In strict mode,
DeserializingConnectionCallback.recvrethrows the decode failure, and it reachesStormServerHandler.exceptionCaught. That method callsUtils.handleUncaughtExceptionwithIOExceptionas the only allowed exception, and the check walks the cause chain.ZstdUtils.decompressandKryoTupleDeserializer.deserializeTupleboth wrap anIOException, so a corrupt or oversized compressed frame closed the connection and discarded the batch, and the worker kept running.Change
DeserializingConnectionCallbackwraps the failure in a newFatalDeserializationException, keeping the original as the cause.StormServerHandlerdoes not apply theIOExceptionexemption when aFatalDeserializationExceptionis in the chain, so every strict-mode failure terminates the worker. Genuine connectionIOExceptions are handled as before.Configjavadoc,docs/Serialization.mdanddocs/Metrics.mdno longer describe the IOException case as an exception to strict mode.deserializationFailuresis still not incremented on the strict path, since the worker exits.Tests
DeserializingConnectionCallbackTest: the strict-mode tests expectFatalDeserializationExceptionwith the original cause, plus a new strict-mode test for a corrupt zstd frame.StormServerHandlerTest: a plain IOException-wrapping failure is swallowed, and one wrapped inFatalDeserializationExceptionis not.