Siyao Meng created HDDS-16887:
---------------------------------

             Summary: Pipeline scrubber pass stops at a pipeline removed by 
another thread and skips the remaining pipelines until the next pass
                 Key: HDDS-16887
                 URL: https://issues.apache.org/jira/browse/HDDS-16887
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: CR-1-scrubber-skip-pipeline-removed-during-pass.patch, 
TestBugCR1ScrubPassAbortedByRemovedPipeline.java

h3. Mechanism
{{PipelineManagerImpl.scrubPipelines}} loops over the pipeline list read at the 
start of the pass with no exception handling per pipeline. If another thread 
removes a listed pipeline before the scrubber reaches it, {{closePipeline}} 
(through {{closeContainersForPipeline}}) or {{deletePipeline}} throws 
{{PipelineNotFoundException}}, the loop ends, and 
{{scrubAndClosePipelinesMissingDataStreamPort}} logs "Unexpected error during 
pipeline scrubbing" at ERROR with the stack trace. Pipelines are removed 
outside the scrubber by {{DeadNodeHandler.destroyPipelines}} and by 
{{closeStalePipelines}} (from {{NewNodeHandler}} and 
{{NodeAddressUpdateHandler}}). {{DeadNodeHandler}} already ignores 
{{PipelineNotFoundException}} for the same {{closePipeline}} and 
{{deletePipeline}} calls, and {{closePipelinesMissingDataStreamPort}} catches 
errors per pipeline.

h3. Trigger
# A scrub pass has several candidates (ALLOCATED past 
{{ozone.scm.pipeline.allocated.timeout}}, CLOSED past 
{{ozone.scm.pipeline.destroy.timeout}}, or OPEN with unregistered nodes).
# While the scrubber works on one of them, another thread removes a later one, 
for example on a dead node event.
# The scrubber reaches the removed pipeline and the pass ends.

The reproduction drives the removal from the scrubber's own thread at a fixed 
point; a removal racing a pass on a cluster was not exercised.

h3. Impact
* The candidates after the removed pipeline wait for the next pass, one 
{{ozone.scm.pipeline.scrub.interval}} later (default 5m). The next pass reads 
the list again and handles them (run).
* An ERROR log with stack trace for a benign race.
* No data impact.

h3. Reproduction
PASS, deterministic, unmodified source. 
[^TestBugCR1ScrubPassAbortedByRemovedPipeline.java] 
({{hadoop-hdds/server-scm}}) uses a real {{PipelineManagerImpl}} on RocksDB 
with the single node HA stub, a mock node manager and a mock clock. Four 
pipelines are past the ALLOCATED timeout. A Mockito spy on {{closePipeline}} 
closes and deletes the second pipeline through the public API when the scrubber 
starts closing the first one, as {{DeadNodeHandler}} would. The pass runs 
through {{scrubAndClosePipelinesMissingDataStreamPort}}, the method the 
background scrubber calls. After the first pass the error is logged and the 
third and fourth pipelines are still there; after the second pass none are 
left. A passing test means the defect is present.

h3. Patch
[^CR-1-scrubber-skip-pipeline-removed-during-pass.patch], against 
b0aa6475b7856d2c7f467dd3db12b9a1dd116f63. It also applies to master at 
6818707698c.

The loop body of {{scrubPipelines}} is wrapped in a catch of 
{{PipelineNotFoundException}} that logs at DEBUG and continues with the next 
pipeline, as {{DeadNodeHandler}} does. Other exceptions, for example losing SCM 
leadership during the pass, still end the pass and are logged once by the 
caller as before. Nothing changes when no pipeline is removed concurrently. The 
change reindents the loop body; {{git diff -w}} shows five added lines.

Covered by {{testScrubPipelinesSkipsPipelineRemovedDuringScrub}} in the 
existing {{TestPipelineManagerImpl}}: without the change {{scrubPipelines}} 
throws ({{Unexpected exception thrown: ... PipelineNotFoundException: Pipeline 
not found: ...}}); with it the pipeline after the removed one is scrubbed in 
the same pass. All test classes of the SCM pipeline package, 
{{TestBackgroundSCMService}}, {{TestSCMSafeModeManager}} and 
{{TestDeadNodeHandler}} pass, and checkstyle is clean. With the patch the 
reproduction fails at its first assertion: no error is logged and all pipelines 
are removed in the first pass.

Related: a separate issue for the same loop, where the scrubber closes and 
deletes a pipeline that was opened during the pass after it was read as a timed 
out ALLOCATED pipeline. Its proposed patch conflicts textually with this one 
and will be rebased if this one lands first.

Found by code review of the SCM pipeline lifecycle paths, as part of the TLA+ 
verification effort under HDDS-15926, on commit 
b0aa6475b7856d2c7f467dd3db12b9a1dd116f63. 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