fix(rocketmq): log warning when FLAG or DELAY header is not numeric#4330
Conversation
6f45a40 to
ac70625
Compare
|
Thanks for the fix. The direction looks good to me, but I think there are a few details worth adjusting before merging:
The code still parses
The added tests lock in the existing fallback/no-exception behavior, which is useful, but they would also pass before this change. Since the only new externally visible behavior is the
This path can run per produced message. If a producer is misconfigured, |
RocketMQMessageConverterSupport#getAndWrapMessage swallowed any
Integer.parseInt failure on the MQ_FLAG and DELAY_TIME_LEVEL message
headers with catch (Exception ignored) {}, silently falling back to
flag=0 and delayLevel=0. A producer that set DELAY to a misconfigured
non-numeric value (e.g. via a SpEL expression that produced a String
instead of an int) would have its delayed delivery silently turn into
an immediate send with no log line and no way to know the header was
malformed.
Parse each header independently in a small parseHeaderAsIntOrDefault
helper that warns, naming the offending header and echoing its raw
value, when parsing fails. Parsing both headers in a single shared try
block had two problems: a valid value on the header parsed first (e.g.
a legitimate DELAY) was reported in the warning as having fallen back
to 0 even though it was actually applied, and an exception on the
header parsed first left the other, well-formed header unparsed and
forced it to 0 as well. Independent parsing keeps every well-formed
value, and the warning now names only the header that actually failed.
Only the exception message is logged (not the full stack trace) to
avoid flooding the logs when many messages carry the same bad header,
and the catch is narrowed from Exception to NumberFormatException.
Adds regression tests covering both the fallback behaviour (non-numeric
DELAY and FLAG each stay at 0, conversion never propagates a parse
exception) and the behaviour introduced here: the warning is emitted
for a malformed header (asserted with OutputCaptureExtension), a valid
delay level survives a malformed flag on the same message, and a valid
flag survives a malformed delay level.
ac70625 to
a4e7f72
Compare
|
Thanks for the update. The latest revision addresses the three points from my previous review:
I found one remaining blocker in the current CI. Integration Testing fails during compilation with: The Please also update the PR description, which still describes the previous implementation. In particular, it says the code keeps a shared broad One small compatibility note: narrowing the catch from Once CI is green and the description is updated, my previous review concerns look resolved. |
The support package is @NullMarked, but parseHeaderAsIntOrDefault declared its rawValue parameter as non-null Object while receiving possibly-null values from MessageHeaders#get, failing NullAway checks: [NullAway] passing @nullable parameter 'flagObj' where @nonnull is required [NullAway] passing @nullable parameter 'delayLevelObj' where @nonnull is required Annotate the parameter with org.jspecify.annotations.Nullable to match the existing null guard and Javadoc.
|
Thanks for the update and fix. The same silent Could you please open a follow-up PR targeting |
…2025.0.x backport of #4330) (#4361) Backport #4330 to the 2025.0.x branch. RocketMQ message conversion silently ignored non-numeric MQ_FLAG and DELAY_TIME_LEVEL headers, which could cause a delayed message to be sent immediately without any diagnostic. Parse the headers independently, warn for the offending header without a stack trace, and fall back to 0 on NumberFormatException. Preserve a valid header when the other value is malformed, while allowing unexpected conversion failures to propagate. Add regression tests for fallback behavior, warning output, and mixed-validity cases. Omit the JSpecify @nullable annotation because this branch does not use @NullMarked, NullAway, or JSpecify.
…2025.0.x backport of alibaba#4330) (alibaba#4361) Backport alibaba#4330 to the 2025.0.x branch. RocketMQ message conversion silently ignored non-numeric MQ_FLAG and DELAY_TIME_LEVEL headers, which could cause a delayed message to be sent immediately without any diagnostic. Parse the headers independently, warn for the offending header without a stack trace, and fall back to 0 on NumberFormatException. Preserve a valid header when the other value is malformed, while allowing unexpected conversion failures to propagate. Add regression tests for fallback behavior, warning output, and mixed-validity cases. Omit the JSpecify @nullable annotation because this branch does not use @NullMarked, NullAway, or JSpecify.
RocketMQ message conversion silently ignored non-numeric MQ_FLAG and DELAY_TIME_LEVEL headers, which could cause a delayed message to be sent immediately without any diagnostic. Parse the two headers independently, log a warning for the offending header without a stack trace, and fall back to 0 on NumberFormatException. Preserve a valid header when the other value is malformed, while allowing unexpected conversion failures to propagate. Mark nullable header lookups with JSpecify @nullable and add tests for fallback behavior, warning output, and mixed-validity cases.
Describe what this PR does / why we need it
RocketMQMessageConverterSupport#getAndWrapMessageparses theMQ_FLAGandDELAY_TIME_LEVELSpring Messaging headers intointviaInteger.parseInt. The previous implementation wrapped both parses in a singletry { ... } catch (Exception ignored) {}, so any malformed header (e.g. aStringvalue injected via a SpEL expression, a typo, or a non-numeric class) was silently absorbed and the producer fell back toflag=0anddelayLevel=0with no exception, no log line, and no signal to the caller.The most user-visible consequence is on the delayed-delivery path: a misconfigured
DELAY_TIME_LEVEL(e.g."abc"from a SpEL that returned a String instead of an int) turns a delayed message into an immediate send and the application has no way of knowing.Does this pull request fix one issue?
NONE
Describe how you did it
RocketMQMessageConverterSupport.parseHeaderAsIntOrDefault(String headerName, @Nullable Object rawValue)helper and parse the two headers independently (flagfirst, thendelayLevel). A malformed value in one header no longer drags the other, well-formed header down to the fallback, and the warning names only the header that actually failed.NumberFormatException, logs awarnwith the header name, the raw value, and the exception message only (no stack trace, to avoid flooding logs on the per-message producer path), and falls back to0.rawValueparameter is annotated@org.jspecify.annotations.Nullable(thesupportpackage is@NullMarked; header lookups can returnnull).Describe how to verify it
mvn -pl spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq test— all 16 module tests pass locally, including 6 new ones inRocketMQMessageConverterSupportTest:nonNumericDelayTimeLevelHeaderFallsBackToZero—DELAY = "not-a-number"→getDelayTimeLevel() == 0.nonNumericFlagHeaderFallsBackToZero—MQ_FLAG = "not-a-number"→getFlag() == 0.invalidNumericHeaderDoesNotPropagateException— both headers non-numeric → no exception escapesconvertMessage2MQ.nonNumericFlagHeaderLogsWarning— usesOutputCaptureExtensionto assert the warning contains the header name and the raw value.validDelayLevelWithNonNumericFlagIsPreservedAndWarnsOnlyFlag— mixed validity: validDELAYis preserved, warning names onlyMQ_FLAG.validFlagWithNonNumericDelayLevelIsPreservedAndWarnsOnlyDelayLevel— the symmetric mixed-validity case.Special notes for reviews
Two intentional behaviour changes relative to the old silent-fallback code, both discussed in review:
0(the parses shared onetryblock). Now each header is parsed independently, so a well-formed header keeps its value even when the other one is malformed. The mixed-validity tests lock this in.ExceptiontoNumberFormatException(intentional).String.valueOf(rawValue)can in principle throw if a custom header value'stoString()throws; such an exception now propagates instead of being silently converted intoflag=0/delayLevel=0. This is deliberate: a throwingtoString()is a programming error in the producer application, and silently mapping it to "send immediately with flag 0" is exactly the failure mode this PR removes.NumberFormatException(the only expected failure: non-numeric text) is still handled with warn-and-fallback.For every previously valid header value the output is unchanged; for a message where all malformed headers are non-numeric text (the common case), the fallback output is also unchanged — the
log.warnis the only new externally visible behaviour.