Skip to content

[Bug] Namesrv: a stale expiry/channel-close unregister wipes a broker's fresh re-registration #11043

Description

@unbridled-41

Before Creating the Bug Report

  • I found a bug, not just asking a question, which should be created in GitHub Discussions.
  • I have searched the GitHub Issues and GitHub Discussions of this repository and believe that this is not a duplicate.
  • I have confirmed that this bug belongs to the current repository, not other repositories of RocketMQ.

Runtime platform environment

Linux, JDK 21, develop (ff8f6f7)

RocketMQ version

5.x develop

Describe the Bug

Since the introduction of BatchUnregistrationService, the decision that a broker should be unregistered (heartbeat expiry in scanNotActiveBroker, or a channel-close event) and its execution are separated by queue latency and blocking closeChannel I/O. setupUnRegisterRequest matches only by clusterName + brokerAddr in brokerAddrTable, and unRegisterBroker then removes unconditionally:

  • brokerLiveTable.remove(brokerAddrInfo) (no freshness re-check),
  • the addr mapping via removeIf(item -> item.getValue().equals(brokerAddr)) (matching by address only, ignoring the brokerId carried in the request — while registerBroker carefully distinguishes ids for the same address),
  • and all topic QueueDatas for the broker.

If the broker is actually alive and re-registers between the expiry decision and the queued execution (e.g. after a namesrv GC pause / scan backlog that expired live brokers), the brand-new registration — new BrokerLiveInfo, address mapping and topic QueueDatas — is deleted. The live broker then vanishes from all routes until its next periodic re-registration (registerNameServerPeriod, ~30s), producing cluster-wide TOPIC_NOT_EXIST / no-route windows. The same defect lets a queued unregister for a slave at addr A remove a master entry re-registered at the same addr A, because removal matches on address only.

Note the onChannelDestroy(Channel) overload already guards by channel identity (entry.getValue().getChannel() == channel) at decision time — but the BrokerAddrInfo overload (used by the expiry scan) has no such check, and even the channel guard cannot cover the decision-to-execution gap.

Steps to Reproduce

  1. Register broker (channel1).
  2. Fire the destroy event for it: onChannelDestroy(new BrokerAddrInfo(cluster, addr)) — the unregister request is queued asynchronously.
  3. Re-register the same broker with channel2 (fresh registration).
  4. Let the queued request execute: the fresh registration is removed; pickupTopicRouteData returns null for its topics.

Expected Behavior

A destroy-derived unregister must not remove a registration that is newer than the event it was derived from: if the current live entry for the address belongs to a different (newer) channel, the queued request is stale and must be skipped. Explicitly initiated unregisters (the broker's own UNREGISTER_BROKER request) remain unconditional.

Corresponding PR

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions