Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
83ccfa2
refetch containerData after acquiring writeLock
Gargi-jais11 Apr 22, 2026
bb7e165
added unit test for race condition
Gargi-jais11 Apr 22, 2026
2095f4f
remove postcall from container state change move skip
Gargi-jais11 Apr 23, 2026
fe91aac
use asserThat and remove supressWarning
Gargi-jais11 Apr 23, 2026
77bf985
use assertThat at rest of the places as well
Gargi-jais11 Apr 23, 2026
05f6978
fix checkstyle issue
Gargi-jais11 Apr 23, 2026
d40da3a
remove annotation
Gargi-jais11 Apr 24, 2026
50f28de
Merge branch 'master' of github.com:apache/ozone into HDDS-15066
Gargi-jais11 Apr 27, 2026
3e4322d
retry for deletion instead of abrupt failure
Gargi-jais11 Apr 28, 2026
d2a7e96
refetch container ref for unhealthyClose and markUnhealthy handler
Gargi-jais11 May 4, 2026
94316cc
fix test failure
Gargi-jais11 May 5, 2026
2f1fa12
fix checkstyle
Gargi-jais11 May 5, 2026
73bd05b
add refetch and retry logic in DeleteBlockCommandHandler
Gargi-jais11 May 12, 2026
5c0479c
fix findbugs
Gargi-jais11 May 12, 2026
ba0a8a4
remove retry and lock current first then unlock candidate
Gargi-jais11 May 13, 2026
c633bbb
Revert "remove retry and lock current first then unlock candidate"
Gargi-jais11 May 13, 2026
bd16367
reduce max retry to 5
Gargi-jais11 May 13, 2026
fc97786
improved container not found and exhausted retries
Gargi-jais11 May 14, 2026
82e77f1
fix checkstyle
Gargi-jais11 May 14, 2026
bfe99c7
address review comment
Gargi-jais11 May 14, 2026
e461576
fix log message
Gargi-jais11 May 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ public class ContainerSet implements Iterable<Container<?>> {

private static final Logger LOG = LoggerFactory.getLogger(ContainerSet.class);

/**
* Max attempts to acquire {@link Container#writeLock()} while verifying this set's id → container mapping
* is same (e.g. another thread may {@link #updateContainer} / DiskBalancer swap the instance).
*/
private static final int MAX_CONTAINER_MAP_SWAP_RETRIES = 5;

private final ConcurrentSkipListMap<Long, Container<?>> containerMap = new
ConcurrentSkipListMap<>();
private final ConcurrentSkipListSet<Long> missingContainerSet =
Expand Down Expand Up @@ -279,6 +285,54 @@ public Container<?> getContainer(long containerId) {
return containerMap.get(containerId);
}

/**
* Returns the max retry for a container map swap while acquiring container lock.
* @return max retry count
*/
public static int maxContainerMapSwapRetries() {
return MAX_CONTAINER_MAP_SWAP_RETRIES;
}

/**
* Locks the container mapped to {@code containerId} for write, and verifies that the instance locked is still
* the one stored in this set. If the mapping is swapped, unlocks and retries up to
* {@link #maxContainerMapSwapRetries()} times, then returns {@code null}.
*
* @return the locked container, or {@code null} if the mapping could not be stabilized after all retries
* @throws StorageContainerException with {@code CONTAINER_NOT_FOUND}
*/
@Nullable
public Container<?> getContainerWithWriteLock(long containerId) throws StorageContainerException {
for (int retry = 0; retry < MAX_CONTAINER_MAP_SWAP_RETRIES; retry++) {
Container<?> candidate = getContainer(containerId);
if (candidate == null) {
throw new StorageContainerException(
"Container " + containerId + " not found in ContainerSet.",
ContainerProtos.Result.CONTAINER_NOT_FOUND);
}
candidate.writeLock();
Container<?> current = getContainer(containerId);
if (current == null) {
candidate.writeUnlock();
throw new StorageContainerException(
"Container " + containerId + " not found in ContainerSet.",
ContainerProtos.Result.CONTAINER_NOT_FOUND);
}
if (current != candidate) {
candidate.writeUnlock();
if (LOG.isDebugEnabled()) {
LOG.debug("Container {} mapping changed during lock acquisition (attempt {}); retrying.",
containerId, retry);
}
continue;
}
return candidate;
}
LOG.warn("Container {} mapping kept changing after {} attempts; giving up.",
containerId, MAX_CONTAINER_MAP_SWAP_RETRIES);
return null;
}

/**
* Removes container from both memory and database. This should be used when the containerData on disk has been
* removed completely from the node.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,19 +303,31 @@ public DeleteBlockTransactionExecutionResult call() {
if (keyValueContainer.
writeLockTryLock(tryLockTimeoutMs, TimeUnit.MILLISECONDS)) {
try {
String schemaVersion = containerData
.getSupportedSchemaVersionOrDefault();
if (getSchemaHandlers().containsKey(schemaVersion)) {
schemaHandlers.get(schemaVersion).handle(containerData, tx);
// Re-fetch the container after acquiring the lock. DiskBalancer may have relocated
// this container to a different disk while we waited — in that case, the container
// object in ContainerSet has changed and containerData points to the old replica.
Container<?> current = containerSet.getContainer(containerId);
if (current == null || current.getContainerData() != containerData) {
LOG.debug("DeleteBlocks: containerData for container {} is stale "
+ ", Will retry on the new replica.",
containerId);
lockAcquisitionFailed = true;
Comment thread
Gargi-jais11 marked this conversation as resolved.
txResultBuilder.setContainerID(containerId).setSuccess(false);
} else {
throw new UnsupportedOperationException(
"Only schema version 1,2,3 are supported.");
String schemaVersion = containerData
.getSupportedSchemaVersionOrDefault();
if (getSchemaHandlers().containsKey(schemaVersion)) {
schemaHandlers.get(schemaVersion).handle(containerData, tx);
} else {
throw new UnsupportedOperationException(
"Only schema version 1,2,3 are supported.");
}
txResultBuilder.setContainerID(containerId)
.setSuccess(true);
}
} finally {
keyValueContainer.writeUnlock();
}
txResultBuilder.setContainerID(containerId)
.setSuccess(true);
} else {
lockAcquisitionFailed = true;
txResultBuilder.setContainerID(containerId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -512,19 +512,19 @@ public BackgroundTaskResult call() {
return BackgroundTaskResult.EmptyTaskResult.newResult();
}

// 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();
}

// hold read lock on the container first, to avoid other threads to update the container state,
// such as block deletion.
container.readLock();
try {
// Double check container state after 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);
moveSucceeded = false;
return BackgroundTaskResult.EmptyTaskResult.newResult();
}

// Step 1: Copy container to new Volume's tmp Dir
diskBalancerTmpDir = getDiskBalancerTmpDir(destVolume)
.resolve(String.valueOf(containerId));
Expand Down Expand Up @@ -580,6 +580,10 @@ public BackgroundTaskResult call() {
// old caller can still hold the old Container object.
ozoneContainer.getContainerSet().updateContainer(newContainer);
destVolume.incrementUsedSpace(containerSize);

// Test injector: ContainerSet now references newContainer while this thread still holds
// readLock on the old replica.
pauseInjector();
// Mark old container as DELETED and persist state.
// markContainerForDelete require writeLock, so release readLock first
container.readUnlock();
Expand All @@ -597,13 +601,7 @@ public BackgroundTaskResult call() {
metrics.incrSuccessBytes(containerSize);
totalBalancedBytes.addAndGet(containerSize);
} catch (IOException e) {
if (injector != null) {
try {
injector.pause();
} catch (IOException ex) {
// do nothing
}
}
pauseInjector();
moveSucceeded = false;
LOG.warn("Failed to move container {}", containerId, e);
if (diskBalancerTmpDir != null) {
Expand Down Expand Up @@ -861,6 +859,17 @@ public static void setInjector(FaultInjector instance) {
injector = instance;
}

// call FaultInjector#pause when an injector is registered; ignore IOException.
private static void pauseInjector() {
if (injector != null) {
try {
injector.pause();
} catch (IOException ex) {
// do nothing
}
}
}

@VisibleForTesting
public void setReplicaDeletionDelay(long durationMills) {
this.replicaDeletionDelay = durationMills;
Expand Down
Loading