Siyao Meng created HDDS-17021:
---------------------------------

             Summary: Failed mark for close of a RECOVERING container drops it 
from stale recovering tracking, so it is never marked UNHEALTHY
                 Key: HDDS-17021
                 URL: https://issues.apache.org/jira/browse/HDDS-17021
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: 
CR-17-keep-recovering-container-tracked-on-failed-close.patch, 
TestBugCR17FailedCloseUntracksRecoveringContainer.java

h3. Mechanism
{{KeyValueHandler.markContainerForClose}} removes a RECOVERING container from 
the stale recovering tracking map ({{ContainerSet.removeRecoveringContainer}}) 
before it calls {{KeyValueContainer.markContainerForClose}}. When writing the 
{{.container}} file then fails, {{KeyValueContainer.updateContainerData}} rolls 
the state back to RECOVERING, but the tracking entry is not restored. 
{{StaleRecoveringContainerScrubbingService}} only looks at that map, so it 
never marks the container UNHEALTHY, and {{ContainerSet.getContainerReport}} 
skips RECOVERING containers, so SCM never sees it either.

h3. Trigger
# EC reconstruction creates a RECOVERING target container and writes to it.
# The coordinator's {{CloseContainer}} fails on that target because the 
{{.container}} file cannot be written (EIO, ENOSPC, EACCES) while the volume 
stays healthy.
# The coordinator's cleanup ({{deleteContainerInState}}) does not remove the 
container.

In the usual case step 3 does not happen: 
{{ECReconstructionCoordinator.reconstructECContainerGroup}} deletes every 
target it created after any failure. From code reading (not run), it does not 
when the delete RPC fails (the error is only logged), when the coordinator, 
which is another target, dies before the delete, or when {{deleteInternal}} 
gives up on the container lock and returns success without deleting. No other 
close path reaches a RECOVERING container.

h3. Impact
The container stays RECOVERING in memory, untracked and unreported, until the 
datanode restarts ({{ContainerReader}} then marks it UNHEALTHY or deletes it). 
Until then a later reconstruction of the same container that picks this 
datanode as a target fails at container creation 
({{CONTAINER_ALREADY_EXISTS}}), because SCM does not know the replica is there. 
It also holds its data on disk, and SCM cannot remove it. Without the bug the 
scrubber marks it UNHEALTHY {{ozone.recovering.container.timeout}} (default 
20m) after its last write, after which it is reported and can be removed. No 
data loss. Affects all releases since 1.3.0 (HDDS-6987).

h3. Reproduction
PASS on unmodified source, deterministic. 
[^TestBugCR17FailedCloseUntracksRecoveringContainer.java] 
({{hadoop-hdds/container-service}}) creates two RECOVERING containers through 
{{KeyValueHandler.handleCreateContainer}} on a real {{HddsVolume}} with a 
recovering timeout of 0. The first is closed through {{handleCloseContainer}} 
while its metadata directory is read only (run as a non root user), the second 
is the control; then one scrubber run is executed. Output: {{failed close: 
result=CONTAINER_FILES_CREATE_ERROR state=RECOVERING inRecoveringMap=false}} 
and {{after scrub: control state=UNHEALTHY, failed state=RECOVERING, containers 
in report=[2]}}. A passing test means the defect is present. Not exercised: a 
real EIO or ENOSPC, the reconstruction coordinator, SCM.

h3. Patch
[^CR-17-keep-recovering-container-tracked-on-failed-close.patch], against 
cce1ca28944886218132f01c0e271c1fd0700078. It also applies to master at 
2536fe9cbc3.

The tracking entry is now removed only after the state change to CLOSING 
succeeded, under the same container write lock. On failure the entry stays with 
its original deadline, so the scrubber still marks the container UNHEALTHY in 
time. Restoring the entry on failure was not chosen, because the existing 
helper resets the deadline. The {{logRecovered}} container log line is 
unchanged. On a successful close the entry now stays in the map during the 
{{.container}} write, so a container whose deadline has already passed can be 
picked up by a scrubber run in that window and later marked UNHEALTHY although 
it closed. That is the existing HDDS-16455 race widened by one file write, and 
its proposed fix (check the state under the container lock in the scrub task) 
covers it. Covered by the new 
{{TestKeyValueHandler.testFailedCloseKeepsRecoveringContainerTracked}}, which 
fails without the change (empty tracking map after the failed close) and also 
checks that a successful close drops the entry. With the change the handler, 
scrubber, container, persistence, reader, container set and close command 
suites pass and checkstyle is clean. With the patch the reproduction fails, 
because the container is tracked and marked UNHEALTHY like the control.

Related, not duplicates: HDDS-16460 (stale entry left after delete), HDDS-16455 
and HDDS-16456 (races between the scrubber, the timeout update and a successful 
close).

Found by code review of the datanode volume and per-disk RocksDB lifecycle 
paths, as part of the TLA+ verification effort under HDDS-15926, on commit 
cce1ca28944886218132f01c0e271c1fd0700078. Checked against HDDS issues and 
apache/ozone pull requests for duplicates before filing. The attached patch is 
a proposal for review. Generated with Specula (Claude Opus 5.5).



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to