Gargi-jais11 opened a new pull request, #10109:
URL: https://github.com/apache/ozone/pull/10109
## What changes were proposed in this pull request?
On a datanode, some work runs under a container read lock (or otherwise
changes which replica directory / in-memory Container is authoritative) while
other threads look up a container once and later take a write lock. If the
`in-memory` container mapping or on-disk location changes in between, the
second thread can still use a stale Container or `KeyValueContainerData`
reference. That is a classic **TOCTOU** problem: the map and the caller
disagree about where the replica lives.
Worst cases include wrong ContainerSet updates, deleting or updating the
wrong paths, ghost / orphan replica data on disk that SCM no longer tracks, and
block deletion targeting the wrong RocksDB/chunks tree so pending deletes on
the live replica are never applied (space not reclaimed).
Applies to **CLOSED** and **QUASI_CLOSED** (and any path where
balancing/replication overlaps lifecycle commands), not a single state.
This can be easily explained with help of DiskBalancer as an example,
although it applies to other read write races as well:
While DiskBalancer moves a container between volumes on the same datanode,
it holds the container read lock, copies data, then calls
`ContainerSet.updateContainer(...) `to point the in-memory map at a new
Container instance (new volume / paths).
Whereas it is seen that other threads often look up the container once, then
block on writeLock() until the move finishes. After they unblock, they still
hold a reference to the old container (source volume). They then run logic
and/or **removeContainer(containerId**) using stale paths and stale object
identity.
**Impacts:**
1. **Replication Manager DeleteContainerCommand:** can remove the wrong map
entry (live replica on the destination volume), delete/move source files, and
leave an orphan replica on disk that SCM no longer sees — plus wrong volume
accounting and ICRs keyed off the old replica.
2. **BlockDeletingTask:** uses a task-scoped KeyValueContainerData snapshot
(source paths) while getContainer() may return the new replica; block cleanup
can target the wrong chunks path and never clear pending-deletion state on the
live replica → space not reclaimed.
**Suggested Fix:**
_For RM side:_ After acquiring writeLock(), re-fetch from the map and
compare by identity. If they differ, the container was moved — abort and let
the caller retry.
```
container.writeLock();
try {
Container<?> current containerSet.getContainer(
container.getContainerData().getContainerID());
if (current != container) {
// Container was relocated by DiskBalancer while we waited for the lock.Our
reference is stale; the operation will be retried on the next cycle.
return;
}
// ... proceed with the actual operation
}
```
**For BlockDeletingService:**
```
container.writeLock();
// Re-fetch AFTER lock: map already has disk-B in scenarios.
// current.getContainerData() is disk-B's ContainerData ≠ this.containerData
(disk-A snapshot).
Container<?> current = containerSet.getContainer(containerData
.getContainerID());
if (current == null || current.getContainerData() != containerData) {
// containerData is stale — DiskBalancer relocated this container.
BlockDeletingService will reschedule with disk-B's fresh containerData.
return;
}
```
**For DiskBalancer:**
Move the container state check after acquiring readLock() to prevent stale
references about container.
```
container.readLock();
try {
// Double check container state before acquiring lock to start move process.
// Container state may have changed after selection.
State containerState = container.getContainerData().getState();
if (!movableContainerStates.contains(containerState)) {
LOG.warn("Container {} is in {} state, skipping move process.",
containerId, containerState);
postCall(false, startTime);
return BackgroundTaskResult.EmptyTaskResult.newResult();
} // Step 1: Copy container to new Volume's tmp Dir
diskBalancerTmpDir = getDiskBalancerTmpDir(destVolume)
.resolve(String.valueOf(containerId));
ozoneContainer.getController().copyContainer(containerData,
diskBalancerTmpDir);
....
```
## What is the link to the Apache JIRA
https://issues.apache.org/jira/browse/HDDS-15066
## How was this patch tested?
Added unit tests on diskBalancer side as an example for the race condition
which can happen with any services read/write Lock race.
Test file: `TestDiskBalancerWithConcurrentBackgroundTasks`
--
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]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]