Skip to content

HDDS-14834. Fix race condition between DeadNodeHandler and HealthyReadOnlyNodeHandler on NetworkTopology#9926

Open
ivandika3 wants to merge 6 commits intoapache:masterfrom
ivandika3:HDDS-14834
Open

HDDS-14834. Fix race condition between DeadNodeHandler and HealthyReadOnlyNodeHandler on NetworkTopology#9926
ivandika3 wants to merge 6 commits intoapache:masterfrom
ivandika3:HDDS-14834

Conversation

@ivandika3
Copy link
Contributor

@ivandika3 ivandika3 commented Mar 15, 2026

What changes were proposed in this pull request?

DeadNodeHandler and HealthyReadOnlyNodeHandler run on separate SingleThreadExecutors, which can lead to a race condition where a resurrected datanode is removed from the NetworkTopology after being re-added. This leaves the node reachable but invisible to the placement policy.

Fix: DeadNodeHandler now checks the current node state before removing it from the topology, skipping removal if the node is no longer DEAD. HealthyReadOnlyNodeHandler uses unconditional add (idempotent) instead of a contains-then-add check, closing the TOCTOU gap.

Made-with: Cursor

There is still a very small chance that race condition might still happen since there is no synchronization method (i.e. lock), but the chance is reduced compared to the previous implementation.

Alternative considered approaches

  • Use a shared SingleThreadExecutor for both DeadNodeHandler: This requires a large change in the SCM event
    framework and might delay event processing

What is the link to the Apache JIRA

https://issues.apache.org/jira/browse/HDDS-14834

How was this patch tested?

UT (Clean CI: https://github.com/ivandika3/ozone/actions/runs/23047544006)

…dOnlyNodeHandler on NetworkTopology

DeadNodeHandler and HealthyReadOnlyNodeHandler run on separate
SingleThreadExecutors, which can lead to a race condition where a
resurrected datanode is removed from the NetworkTopology after being
re-added. This leaves the node reachable but invisible to the placement
policy.

Fix: DeadNodeHandler now checks the current node state before removing
it from the topology, skipping removal if the node is no longer DEAD.
HealthyReadOnlyNodeHandler uses unconditional add (idempotent) instead
of a contains-then-add check, closing the TOCTOU gap.

Made-with: Cursor
@ivandika3 ivandika3 self-assigned this Mar 15, 2026
Comment on lines +122 to +129
// Only remove from topology if the node is still DEAD. Between the time
// the DEAD_NODE event was fired and now, the node may have been
// resurrected (DEAD -> HEALTHY_READONLY) via a heartbeat. Removing a
// resurrected node from the topology would leave it reachable but
// invisible to the placement policy.
NodeStatus currentStatus =
nodeManager.getNodeStatus(datanodeDetails);
if (currentStatus.getHealth() == HddsProtos.NodeState.DEAD) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ivandika3 I was thinking would it make sense to add an early check at the start of onMessage and return if the node is no longer DEAD? In the race where the node is resurrected before this handler runs, we’d still run removeContainerReplicas, REPLICATION_MANAGER_NOTIFY, deletedBlockLog.onDatanodeDead, etc, which may not be appropriate for a resurrected node.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I added another check at the start. Technically, we need to do each check before doing any of these actions, but seems to be overkill.

Copy link
Contributor

@priyeshkaratha priyeshkaratha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes overall LGTM. Please check few minor comments.

NetworkTopology nt = nodeManager.getClusterNetworkTopologyMap();
if (nt.contains(datanodeDetails)) {
nt.remove(datanodeDetails);
Preconditions.checkState(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The call to nodeManager.getNode(datanodeDetails.getID()) could return null if the node is concurrently removed from the NodeManager while this handler is executing. This would lead to a NullPointerException when .getParent() is called, which could terminate the event handler thread.
better to handle like below

DatanodeDetails node = nodeManager.getNode(datanodeDetails.getID());
if (node != null) {
  Preconditions.checkState(node.getParent() == null);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, should not be introduced in this patch, but updated to prevent null.

Anyway, since SingleThreadExecutor#onEvent catches all exception, it should not cause event handler thread termination.

Copy link
Contributor

@Gargi-jais11 Gargi-jais11 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ivandika3 for updating patch, it looks almost good. Just one minor change to do.

Copy link
Contributor

@Gargi-jais11 Gargi-jais11 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Copy link
Contributor

@priyeshkaratha priyeshkaratha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ivandika3 for updating patch. Changes LGTM

@adoroszlai adoroszlai requested a review from ChenSammi March 24, 2026 06:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants