[FLINK-40674][runtime] Stop deserializing SerializedThrowable via ObjectInputStream on parse - #29193
Open
gaborgsomogyi wants to merge 1 commit into
Open
gaborgsomogyi wants to merge 1 commit into
gaborgsomogyi wants to merge 1 commit into
Conversation
gaborgsomogyi
force-pushed
the
FLINK-40674
branch
from
September 16, 2026 08:42
7f0c865 to
b68b71f
Compare
…ectInputStream on parse
gaborgsomogyi
force-pushed
the
FLINK-40674
branch
from
September 16, 2026 09:03
b68b71f to
d03c0ad
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What is the purpose of the change
SerializedThrowableDeserializercurrently reconstructs theserialized-throwableREST field by running it throughObjectInputStream.readObject()as an automatic side effect of parsing, even though none of the current callers of this field actually need the live, reconstructed exception object at that point; they only ever read its class name, message, and stack trace as text.This pull request changes
SerializedThrowableSerializer/SerializedThrowableDeserializerto carry that information as additive, plain-text JSON fields, so parsing a response recovers it directly, without touching Java's own object deserialization at all. The original exception object remains available through the existingSerializedThrowable#deserializeError()method, via an explicit, separate call, for the handful of callers that still need it.It changes the REST API in a backward compatible way.
Before:
After:
Brief change log
SerializedThrowableSerializernow also writesmessage,cause, andsuppressed(recursively) as plain JSON fields, alongside the existingclass,stack-trace, andserialized-throwablefields, which keep their current name, meaning, and byte content unchanged.SerializedThrowableDeserializerreconstructs aSerializedThrowable, and its cause/suppressed chain, entirely from those text fields, with a bounded recursion depth, and no longer callsObjectInputStream.readObject()while parsing.SerializedThrowablegains a constructor that builds an instance directly from the text fields above, and its structuredgetStackTrace()no longer reflects the deserializer's own call stack.JobResultgainstoSafeJobExecutionResult(ClassLoader), which builds the failure exception without deserializing its cause.ClusterClientJobClientAdapter, which backs anyRestClusterClient-basedJobClient(including remote/session-cluster job submission), now uses it. The existingtoJobExecutionResult(ClassLoader)keeps its original behavior unchanged for same-process callers (MiniCluster, Application Mode).ClientUtils#waitUntilJobInitializationFinishedreconstructsJobInitializationExceptionfrom the text fields above instead of callingdeserializeError().Verifying this change
This change added tests and can be verified as follows:
SerializedThrowableSerializerTest: round-trip tests confirmingclass/message/stack-trace/cause/suppressedsurvive parsing without deserialization; a test with a non-Java-serialized byte payload inserialized-throwableconfirming parsing never attempts deserialization regardless of what that field contains; two tests covering both rolling-upgrade directions (an old deserializer's logic against this fix's serializer output, and this fix's deserializer against an old-shaped response); a test confirming the reconstructed exception's structured stack trace does not leak the deserializer's own call stack; a test confirming acausechain nested past a fixed depth fails parsing with anIOExceptioninstead of exhausting the stack.RestClusterClientJobResultSafetyTestdrives the realRestClusterClient.requestJobResult()against a fake server returning a response whoseserialized-throwablefield is set so that a marker flips if that field is ever read viaObjectInputStream.readObject(). Confirms the marker never fires while parsing, and only fires oncedeserializeError()is called explicitly afterward.JobResultTestcoverstoSafeJobExecutionResult()'s behavior (cause stays aSerializedThrowable) alongside the unchangedtoJobExecutionResult()(cause is deserialized, as before).Does this pull request potentially affect one of the following parts:
@Public(Evolving): yesSerializedThrowableSerializer/SerializedThrowableDeserializergain additive wire fields; both rolling-upgrade directions are covered by tests)Documentation
Was generative AI tooling used to co-author this PR?
Claude code