RockteMQ-AI commented on issue #11425:
URL: https://github.com/apache/rocketmq/issues/11425#issuecomment-6149876912

   **Automated Fix Proposal (v1)**
   
   Code base: `a7edeecfb662` (develop)
   
   # Fix Specification — Issue #11425: `compressedRegister=true` breaks broker 
registration (header/body compression flag mismatch)
   
   - **Issue**: [Bug] compressedRegister=true makes broker registration fail: 
the register body is compressed but the header says it is not
   - **Base**: develop @ `a7edeecfb662`
   - **Verification status**: CONFIRMED (root cause reproduced by code audit 
below)
   - **Severity**: High — with `compressedRegister=true`, a broker can never 
register with any name server; route discovery is dead for the whole cluster 
until config rollback.
   
   ---
   
   ## 1. Root Cause Analysis
   
   ### Primary defect (one line)
   
   `broker/src/main/java/org/apache/rocketmq/broker/out/BrokerOuterAPI.java:517`
   
   ```java
   requestHeader.setCompressed(false);          // line 517 — hardcoded, 
ignores the `compressed` parameter
   ...
   final byte[] body = requestBody.encode(compressed);   // line 525 — honors 
the `compressed` parameter
   ```
   
   When `compressed == true`, line 525 produces a zlib **deflate stream** 
(`RegisterBrokerBody.encode(true)`, 
`remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/RegisterBrokerBody.java:51-113`),
 while line 517 tells the name server the body is **plain JSON**.
   
   ### How the `compressed` parameter flows
   
   - `BrokerConfig.compressedRegister` (default `false`, 
`common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java:180`) is 
passed as the `compressed` argument via `BrokerController.doRegisterBrokerAll` 
→ `brokerOuterAPI.registerBrokerAll(...)` 
(`broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java:2158-2171`,
 specifically line 2169: `this.brokerConfig.isCompressedRegister()`).
   - All registration triggers flow through this single path: initial startup 
registration, the periodic re-registration task 
(`BrokerController.java:1983,1998`), `forceRegister`, dLeger role change 
(`DLedgerRoleChangeHandler.java:149,172`), controller-mode 
(`ReplicasManager.java:326`), container mode 
(`InnerBrokerController.java:81,96`), and admin-triggered registration 
(`AdminBrokerProcessor.java:1153`). So the bug hits every registration attempt, 
not just startup.
   
   ### Failure mode on the name server
   
   <details>
   <summary>Full Specification</summary>
   
   # Fix Specification — Issue #11425: `compressedRegister=true` breaks broker 
registration (header/body compression flag mismatch)
   
   - **Issue**: [Bug] compressedRegister=true makes broker registration fail: 
the register body is compressed but the header says it is not
   - **Base**: develop @ `a7edeecfb662`
   - **Verification status**: CONFIRMED (root cause reproduced by code audit 
below)
   - **Severity**: High — with `compressedRegister=true`, a broker can never 
register with any name server; route discovery is dead for the whole cluster 
until config rollback.
   
   ---
   
   ## 1. Root Cause Analysis
   
   ### Primary defect (one line)
   
   `broker/src/main/java/org/apache/rocketmq/broker/out/BrokerOuterAPI.java:517`
   
   ```java
   requestHeader.setCompressed(false);          // line 517 — hardcoded, 
ignores the `compressed` parameter
   ...
   final byte[] body = requestBody.encode(compressed);   // line 525 — honors 
the `compressed` parameter
   ```
   
   When `compressed == true`, line 525 produces a zlib **deflate stream** 
(`RegisterBrokerBody.encode(true)`, 
`remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/RegisterBrokerBody.java:51-113`),
 while line 517 tells the name server the body is **plain JSON**.
   
   ### How the `compressed` parameter flows
   
   - `BrokerConfig.compressedRegister` (default `false`, 
`common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java:180`) is 
passed as the `compressed` argument via `BrokerController.doRegisterBrokerAll` 
→ `brokerOuterAPI.registerBrokerAll(...)` 
(`broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java:2158-2171`,
 specifically line 2169: `this.brokerConfig.isCompressedRegister()`).
   - All registration triggers flow through this single path: initial startup 
registration, the periodic re-registration task 
(`BrokerController.java:1983,1998`), `forceRegister`, dLeger role change 
(`DLedgerRoleChangeHandler.java:149,172`), controller-mode 
(`ReplicasManager.java:326`), container mode 
(`InnerBrokerController.java:81,96`), and admin-triggered registration 
(`AdminBrokerProcessor.java:1153`). So the bug hits every registration attempt, 
not just startup.
   
   ### Failure mode on the name server
   
   
`namesrv/src/main/java/org/apache/rocketmq/namesrv/processor/DefaultRequestProcessor.java`:
   
   1. `registerBroker(...)` (line 218) first runs `checksum(...)` (line 225) — 
**this passes**, because CRC32 is computed over the same (compressed) bytes on 
both sides. The corruption is purely semantic.
   2. `extractRegisterBrokerBodyFromRequest(...)` (line 291) calls
      `RegisterBrokerBody.decode(request.getBody(), 
requestHeader.isCompressed(), brokerVersion)` (line 298).
   3. With `isCompressed() == false`, decode takes the JSON branch 
(`RegisterBrokerBody.java:116-118`: `RegisterBrokerBody.decode(data, 
RegisterBrokerBody.class)`) and attempts to JSON-parse the deflate stream → 
exception → rethrown as `RemotingCommandException("Failed to decode 
RegisterBrokerBody")` → name server returns `SYSTEM_ERROR`.
   4. Broker logs `Failed to register current broker to name server` and 
retries at every registration cycle — registration never succeeds.
   
   ### Why the header flag will actually reach the name server after the fix
   
   The `compressed` field is a plain (unannotated) `boolean` on 
`RegisterBrokerRequestHeader` 
(`remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/namesrv/RegisterBrokerRequestHeader.java:51`).
 It round-trips through extFields because:
   
   - `RemotingCommand.makeCustomHeaderToNet()` 
(`remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RemotingCommand.java:430-456`)
 serializes **all** non-static fields — including unannotated ones — into 
`extFields`.
   - `RemotingCommand.decodeCommandCustomHeader` (same file, ~line 290-345) 
reads all non-static fields back (`Boolean.parseBoolean` for booleans; 
unannotated fields are treated as nullable).
   
   So restoring `setCompressed(compressed)` makes the truthful flag reach the 
NS, which then takes the symmetric `InflaterInputStream` decode path 
(`RegisterBrokerBody.java:115-170`) — the mirror image of the broker's 
`DeflaterOutputStream` encode path, including the `V5_0_0` topic-queue-mapping 
section.
   
   ### Regression origin
   
   Commit `cad0cfcec` changed `setCompressed(compressed)` → 
`setCompressed(false)` (motivated by the javadoc note at 
`BrokerOuterAPI.java:475-478`: "Considering compression brings much CPU 
overhead to name server, stream API will not support compression and 
compression feature is deprecated") but **kept `encode(compressed)`**. The 
result is a header/body disagreement that only manifests when a user opts in 
via `compressedRegister=true` — the config remained public and 
functional-looking (`BrokerConfig.java:1328-1334`), so nothing alerted the 
user. The local repo is a shallow clone (single commit), so the regression 
commit itself could not be re-inspected; the evidence is the current 
inconsistent state, which fully matches the issue description.
   
   ### Why existing tests missed it
   
   
`broker/src/test/java/org/apache/rocketmq/broker/BrokerOuterAPITest.java:197-210`
 (`test_register_normal`) already passes `compressed=true`, but the mocked 
`NettyRemotingClient` never decodes the body nor inspects the header — it just 
returns a canned SUCCESS response. The test validates the broker-side happy 
path only; the mismatch is invisible to it.
   
   ---
   
   ## 2. Fix Strategy
   
   **Chosen fix: make the header tell the truth (restore pre-regression 
behavior).**
   
   ### Step 1 — The one-line fix (required)
   
   In 
`broker/src/main/java/org/apache/rocketmq/broker/out/BrokerOuterAPI.java:517`, 
change:
   
   ```java
   requestHeader.setCompressed(false);
   ```
   
   to:
   
   ```java
   requestHeader.setCompressed(compressed);
   ```
   
   This restores header/body consistency for both values of the flag:
   - `compressed=false` (default): header says `false`, body is JSON — 
unchanged wire behavior for the overwhelming majority of deployments.
   - `compressed=true`: header says `true`, body is deflate — name server takes 
the correct inflate/decode path, exactly as it did before commit `cad0cfcec`.
   
   ### Step 2 — Correct the misleading javadoc (recommended, prevents 
recurrence)
   
   The javadoc at `BrokerOuterAPI.java:475-490` ("compression feature is 
deprecated") is factually wrong post-fix and is precisely the comment that 
accompanied the regression. Replace:
   
   ```java
   /**
    * Considering compression brings much CPU overhead to name server, stream 
API will not support compression and
    * compression feature is deprecated.
    * ...
    * @param compressed         default false
    */
   ```
   
   with:
   
   ```java
   /**
    * The register body is compressed (zlib deflate) if and only if {@code 
compressed} is true, and the
    * request header's compressed flag is set accordingly so the name server 
decodes it symmetrically.
    * The stream API does not support compression; this remoting path honors 
the broker's
    * compressedRegister configuration, default false.
    * ...
    * @param compressed         whether to compress the register body, default 
false
    */
   ```
   
   ### Alternative considered and rejected
   
   Changing line 525 to `encode(false)` instead (fully kill compression) would 
also stop the crash, but it silently turns the public, documented 
`compressedRegister` config into a no-op — a config that lies to users. If the 
project ever wants to truly deprecate the feature, that must be done explicitly 
(deprecate the config, log a warning), not via a hidden flag mismatch. The 
issue's verification result also prescribes `setCompressed(compressed)`.
   
   ### What is explicitly out of scope
   
   - Any change to `RegisterBrokerBody` encode/decode (symmetric and 
long-standing).
   - Any change to `DefaultRequestProcessor` (its logic is correct once the 
flag is truthful).
   - The pre-existing edge case where `encode(true)` returns `null` on 
`IOException` (`RegisterBrokerBody.java:108-112`), which would NPE at 
`UtilAll.crc32(body)`. That behavior predates the regression and is unchanged 
by this fix; file a separate issue if desired.
   
   ---
   
   ## 3. Files to Modify
   
   | # | File | Change |
   |---|------|--------|
   | 1 | 
`broker/src/main/java/org/apache/rocketmq/broker/out/BrokerOuterAPI.java` | 
**Line 517**: `setCompressed(false)` → `setCompressed(compressed)`. |
   | 2 | 
`broker/src/main/java/org/apache/rocketmq/broker/out/BrokerOuterAPI.java` | 
**Lines 475-490** (optional, recommended): javadoc correction per Step 2. |
   
   Test files (see §4):
   
   | # | File | Change |
   |---|------|--------|
   | 3 | 
`broker/src/test/java/org/apache/rocketmq/broker/BrokerOuterAPITest.java` | Add 
regression test(s) asserting header/body agreement. |
   | 4 | 
`namesrv/src/test/java/org/apache/rocketmq/namesrv/processor/RequestProcessorTest.java`
 | Add compressed-register success + mismatch-failure cases (optional but 
cheap). |
   
   No changes to `remoting` module production code. No protocol/schema changes 
(the existing `RegisterBrokerRequestHeader` schema already covers 
`setCompressed`/`isCompressed`).
   
   ---
   
   ## 4. Test Plan
   
   ### 4.1 Broker side — header/body agreement test (required)
   
   Add to 
`broker/src/test/java/org/apache/rocketmq/broker/BrokerOuterAPITest.java` 
(reuse existing `init()` / mocked `NettyRemotingClient` harness from 
`test_register_normal`):
   
   **`test_register_compressed_header_matches_body`**:
   1. Call `registerBrokerAll(..., compressed = true, ...)` with a non-empty 
`TopicConfigSerializeWrapper`.
   2. Capture the `RemotingCommand` passed to `invokeSync` with 
`ArgumentCaptor`.
   3. Important harness detail: with a mocked client the command is never 
serialized, so extFields are not populated yet — the test must call 
`captured.makeCustomHeaderToNet()` and then 
`captured.decodeCommandCustomHeader(RegisterBrokerRequestHeader.class)`; assert 
`header.isCompressed() == true` (or assert 
`captured.getExtFields().get("compressed").equals("true")`).
   4. Prove body/flag agreement end-to-end: 
`RegisterBrokerBody.decode(captured.getBody(), true, MQVersion.Version.V5_0_0)` 
must succeed and reproduce the registered topic count.
   
   **`test_register_uncompressed_header_matches_body`** (mirror, guards the 
default path):
   1. Call with `compressed = false`.
   2. Assert header `isCompressed() == false` and that 
`RegisterBrokerBody.decode(captured.getBody(), false, version)` (JSON path) 
succeeds.
   
   These two tests fail on the unfixed code (test 1 header says `false` while 
body is deflate; decoding with `true` fails) and pass after the fix.
   
   ### 4.2 Name server side — decode both flags correctly (recommended)
   
   Extend 
`namesrv/src/test/java/org/apache/rocketmq/namesrv/processor/RequestProcessorTest.java`
 (it already builds REGISTER_BROKER commands manually, see 
`requestBody.encode(false)` around line 623):
   
   - **Compressed success case**: build body via `requestBody.encode(true)`, 
add ext field `compressed=true` (the test constructs extFields manually, so 
`request.addExtField("compressed", "true")`), assert response code `SUCCESS` 
and that the broker/topic route is registered.
   - **Mismatch failure case (documents the bug)**: body `encode(true)` + ext 
field `compressed=false`, assert non-SUCCESS response — this locks in the 
failure mode so any future header/body divergence is caught by a red test.
   
   ### 4.3 Existing tests to keep green (regression suite)
   
   - 
`remoting/src/test/java/org/apache/rocketmq/remoting/protocol/RegisterBrokerBodyTest.java`
 — already covers the encode(true)/decode(true) round-trip; no change needed.
   - `BrokerOuterAPITest#test_register_normal`, `test_register_timeout` — must 
stay green (they pass `compressed=true` / use the default path).
   - Existing `RequestProcessorTest` register/unregister cases — must stay 
green.
   
   ### 4.4 Commands
   
   ```bash
   mvn -pl broker -am test -Dtest=BrokerOuterAPITest
   mvn -pl namesrv -am test -Dtest=RequestProcessorTest
   mvn -pl remoting -am test -Dtest=RegisterBrokerBodyTest
   # broader safety net:
   mvn -pl broker,namesrv,remoting test
   ```
   
   ### 4.5 Manual verification (optional, end-to-end)
   
   Start a namesrv + broker with `-DcompressedRegister=true` (or 
`brokerConfig.compressedRegister=true` in conf), confirm `broker` logs 
`Registering current broker to name server completed` and `sh mqadmin topicList 
-n <namesrv>` shows system/registered topics; the namesrv log should not 
contain `Failed to decode RegisterBrokerBody`. Repeat with default `false` to 
confirm the unchanged default path.
   
   ---
   
   ## 5. Backward Compatibility
   
   - **Default deployments (compressedRegister=false, the vast majority): zero 
behavior change.** The header flag stays `false` and the body stays JSON — 
bit-for-bit the same request as before this fix and before the regression.
   - **Opt-in users (compressedRegister=true): the fix restores the 
pre-`cad0cfcec` behavior.** These users are currently hard-broken (registration 
always fails); after the fix they work again. The compressed decode path in the 
name server (`RegisterBrokerBody.decode(body, compressed, version)`) and the 
`compressed` extField have been part of the protocol for many releases, so a 
fixed broker registers fine against older name servers, and older brokers are 
unaffected against any NS.
   - **No wire-protocol change**: no new fields, no type changes — only the 
value of the existing `compressed` extField becomes truthful when compression 
is enabled.
   - **No API/config surface change**: `BrokerConfig.compressedRegister` keeps 
its name, default, and semantics.
   - **Mixed-version clusters**: new broker + old NS with compression on → 
works (identical to RocketMQ pre-regression). Old broker + new NS → unaffected 
(its header/body were already consistent per its own version).
   - **Not a breaking change in any sense**; it is a strict regression repair.
   
   ## 6. Risk Assessment
   
   | # | Risk | Likelihood | Impact | Mitigation |
   |---|------|-----------|--------|------------|
   | 1 | **Design-intent conflict**: if `cad0cfcec` truly intended to disable 
compression globally (ignoring the config), this fix "reverts" that intent and 
re-introduces NS-side inflate CPU overhead for opt-in users. | Low | Low | The 
config is public, documented, and default-off; honoring it is the correct 
contract. CPU overhead only applies to clusters that explicitly asked for it. 
If deprecation is genuinely desired, do it explicitly (deprecate config + warn 
log) in a separate change. |
   | 2 | **PowerMock test fragility**: the new broker-side test depends on 
calling `makeCustomHeaderToNet()` manually because the mocked client never 
serializes; forgetting this makes assertions fail confusingly. | Medium | Low 
(test-only) | Documented in §4.1 step 3; assert on extFields directly as a 
fallback. |
   | 3 | **Very old name servers** (pre-3.0.11 decode path, 
`DefaultRequestProcessor.java:235-242`) combined with compressed bodies from a 
modern broker. | Very low | Medium | That path is gated on broker *version*, 
not the compressed flag, and such version combinations are already unsupported 
for other reasons. Not introduced by this fix. |
   | 4 | **Pre-existing `encode(true)` → `null` on IOException** 
(`RegisterBrokerBody.java:108-112`) which would NPE at `UtilAll.crc32` 
(BrokerOuterAPI.java:526). | Very low | Medium | Pre-existing since before the 
regression; unchanged by this fix. Out of scope; recommend a follow-up issue to 
fail-fast instead of returning null. |
   | 5 | **Compression content asymmetry**: broker compresses DataVersion + 
topic configs + filter list + topic-queue-mappings, but NS only parses 
topic-queue-mappings for broker ≥ V5_0_0 (`RegisterBrokerBody.java:152`). A 
<5.0 broker's mapping section would be silently skipped. | Very low | Low | 
Long-standing protocol logic, untouched by this fix. |
   | 6 | **Rollback risk**: essentially none — the fix is one line and 
trivially revertible. | — | — | — |
   
   **Overall risk: low.** The change is a single-line truth restoration on a 
default-off path, with the golden path (uncompressed) provably untouched.
   
   </details>
   
   **Next Steps:**
   - Reply `/approve` to proceed with PR generation
   - Reply `/revise <feedback>` to request changes
   - Reply `/reject` to close this proposal
   
   *This proposal expires in 72 hours.*
   
   ---
   *Automated by github-manager-bot*


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to