Siyao Meng created HDDS-16888:
---------------------------------
Summary: Pipeline scrubber closes and deletes a pipeline that was
opened after the scrubber read it as a timed out ALLOCATED pipeline
Key: HDDS-16888
URL: https://issues.apache.org/jira/browse/HDDS-16888
Project: Apache Ozone
Issue Type: Bug
Reporter: Siyao Meng
Attachments:
MC-1-scrubber-skip-pipeline-opened-after-allocated-timeout.patch,
TestBugMC1ScrubberClosesOpenedPipeline.java
h3. Mechanism
{{PipelineManagerImpl.scrubPipelines}} reads the pipeline list once per pass
and decides on those {{Pipeline}} objects, which do not change when the
pipeline changes state. For an entry that has been ALLOCATED for longer than
{{ozone.scm.pipeline.allocated.timeout}}, it calls {{closePipeline}} and
{{deletePipeline}} without reading the current state again and without a lock
held across the check and the action. {{PipelineReportHandler}} opens a
pipeline ({{openPipeline}}, under the pipeline manager write lock) once all its
datanodes have reported it and a leader is known. If that happens after the
scrubber read the list and before it reaches the pipeline, the scrubber closes
and deletes a pipeline that is OPEN and may already have containers. The branch
is meant for "pipelines who stay ALLOCATED for too long", as its comment says.
h3. Trigger
# A Ratis THREE pipeline stays ALLOCATED longer than the ALLOCATED timeout
(default 5m), for example because one datanode was slow to create the Ratis
group.
# A scrub pass (default every 5m) reads the pipeline list. The pipeline is a
timed out ALLOCATED candidate.
# While the scrubber closes and deletes the candidates before it in the same
pass, the last datanode report arrives and {{PipelineReportHandler}} opens the
pipeline. A writer can now get a container on it;
{{WritableRatisContainerProvider}} even waits for such an ALLOCATED pipeline to
open when it cannot create a new one.
# The scrubber reaches the pipeline and closes and deletes it.
The window is the time the pass spends on the candidates before this pipeline,
and the pipeline must become healthy only after the timeout, so this is a
narrow race. It was not observed on a cluster.
h3. Impact
* A healthy, newly opened pipeline is destroyed: SCM sends
{{ClosePipelineCommand}} to its three datanodes (run).
* Containers already allocated on it are moved to CLOSING (run). Their writers
fail and, as for any pipeline closed while in use (dead node, admin close,
datanode close action), the client excludes the pipeline and continues on a new
block (argued from code, not run). No data loss was shown.
* The end state is the same as if the scrubber had removed the pipeline just
before it opened, but writers that already started on it have to fail over.
h3. Reproduction
PASS, deterministic, unmodified source.
[^TestBugMC1ScrubberClosesOpenedPipeline.java] ({{hadoop-hdds/server-scm}})
uses a real {{PipelineManagerImpl}}, {{ContainerManagerImpl}} and
{{PipelineReportHandler}} on RocksDB with the single node HA stub, a mock node
manager, a mock clock and a mock event publisher. Two pipelines are past the
ALLOCATED timeout when the scrub starts. A Mockito spy on {{closePipeline}}
provides the timing: when the scrubber starts closing the first pipeline, the
report handler receives reports from the three datanodes of the other one and
opens it, and a writer gets a container on it through
{{ContainerManagerImpl.getMatchingContainer}}. Output:
{noformat}
pipeline Pipeline-38f825a4-4d56-4bb4-bf21-eb14a3461121 after its reports: OPEN,
container #1 handed to a writer
after scrub: pipeline Pipeline-38f825a4-4d56-4bb4-bf21-eb14a3461121 present =
false, container #1 = CLOSING, ClosePipelineCommand sent to 3 datanodes
{noformat}
A passing test means the defect is present.
h3. Patch
[^MC-1-scrubber-skip-pipeline-opened-after-allocated-timeout.patch], against
b0aa6475b7856d2c7f467dd3db12b9a1dd116f63. It also applies to master at
6818707698c.
In the ALLOCATED branch the scrubber first moves the pipeline to CLOSED under
the pipeline manager write lock, only if it is still ALLOCATED (new private
{{closeIfAllocated}}). {{openPipeline}} takes the same lock and already refuses
a CLOSED pipeline, so either the report opens the pipeline first and the
scrubber leaves it alone (with an INFO log), or the scrubber closes it first
and the report no longer opens it (a report handler that read the pipeline as
ALLOCATED just before gets the existing "Closed pipeline can not be opened"
refusal, as today). The unchanged {{closePipeline}} (which closes any
containers) and {{deletePipeline}} then run. For a pipeline that is still
ALLOCATED the result is the same as before; only the CLOSED transition now
comes before the container close. {{closePipeline}} itself is not changed.
Covered by {{testScrubPipelinesKeepsPipelineOpenedDuringScrub}} in the existing
{{TestPipelineManagerImpl}}: without the change the opened pipeline is deleted
({{Expecting actual: [] to contain exactly ...}}); with it 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: the
pipeline stays OPEN, the container stays OPEN and no {{ClosePipelineCommand}}
is sent.
Related: HDDS-16887 (the scrub pass stops at a pipeline removed by another
thread). Both patches change the loop in {{scrubPipelines}} and are made
against the same commit, so they conflict textually; if HDDS-16887 lands first,
this patch needs a trivial rebase (the two changed lines of the ALLOCATED
branch move inside the new {{try}} block).
Found by TLA+ model checking and code review of the SCM pipeline lifecycle
paths 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]