[
https://issues.apache.org/jira/browse/HDDS-16815?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Siyao Meng updated HDDS-16815:
------------------------------
Attachment:
CR-5-report-invalid-stored-bytes-per-checksum-in-container-scanner.patch
> Container data scanner thread ends when a chunk's stored bytes per checksum
> is invalid, and a large stored value sizes the scan's read buffer
> ---------------------------------------------------------------------------------------------------------------------------------------------
>
> Key: HDDS-16815
> URL: https://issues.apache.org/jira/browse/HDDS-16815
> Project: Apache Ozone
> Issue Type: Bug
> Reporter: Siyao Meng
> Priority: Major
> Attachments:
> CR-5-report-invalid-stored-bytes-per-checksum-in-container-scanner.patch,
> TestBugCR5ScannerStoredBytesPerChecksum.java
>
>
> h3. Mechanism
> The container data scan reads the bytes per checksum stored in a chunk's
> checksum metadata and trusts it. {{KeyValueContainerCheck.scanBlock}} passes
> it to {{BUFFER_POOL.getBuffer(bytesPerChecksum)}} to size the read buffer,
> and {{verifyChecksum}} uses it to walk the chunk and, for the last interval,
> as the divisor in {{chunk.getLen() % bytesPerChecksum}}.
> For a chunk whose stored checksum type is not NONE and whose stored bytes per
> checksum is zero or negative, the scan throws an unchecked exception: zero
> gives an {{IndexOutOfBoundsException}} fetching the first computed checksum
> from an empty list (or an {{ArithmeticException}} divide by zero, depending
> on the chunk), and a negative value gives an {{IllegalArgumentException}}
> allocating the buffer. The data scan runs on CLOSED, QUASI_CLOSED and
> UNHEALTHY containers, so the exception can be thrown for any such container.
> The background data scanner does not survive this.
> {{AbstractBackgroundContainerScanner.scanContainers}} catches only
> {{InterruptedException}} and {{IOException}} per container, and {{run()}}
> catches the escaping exception, logs "exiting because of exception" and
> returns. There is one background data scanner thread per volume, and nothing
> restarts it, so one such chunk on a volume ends that volume's data scanning
> until the datanode restarts, and the scanner's metrics are unregistered. The
> on-demand scanner catches the exception in the scan future but leaves the
> container in {{containerRescheduleCheckSet}}, so later on-demand scans of
> that container are never queued.
> A large stored bytes per checksum (up to {{Integer.MAX_VALUE}}) is used to
> reserve a direct read buffer of that size, so one such chunk makes each scan
> of its container try to reserve up to about 2 GB of direct memory.
> h3. Trigger
> A closed (or quasi-closed or unhealthy) container holds a chunk whose stored
> checksum metadata has a checksum type other than NONE and a bytes per
> checksum that is zero, negative, or far larger than the chunk. A data scan of
> that container (background or on demand) reads the stored value.
> h3. Impact
> * A stored bytes per checksum of zero or less: the data scan throws an
> unchecked exception. The volume's background data scanner thread ends and
> does not restart until the datanode restarts, and the on-demand scanner stops
> scanning that container. Other volumes' scanner threads and the metadata
> scanner are not affected.
> * A very large stored bytes per checksum: each data scan of the container
> reserves a direct buffer of that size, which can exhaust the datanode's
> direct memory.
> h3. Reproduction
> PASS on the unmodified pin, deterministic.
> [^TestBugCR5ScannerStoredBytesPerChecksum.java]
> ({{hadoop-hdds/container-service}}) builds real closed {{KeyValueContainer}}s
> on disk and drives the real {{KeyValueContainerCheck}},
> {{BackgroundContainerDataScanner}} and {{OnDemandContainerScanner}}; only the
> {{ContainerController}} is a mock. The stored value is put in place by
> rewriting the block metadata in the container DB, the way the existing
> scanner tests set up their containers. Five cases: the data scan throws for a
> stored value of 0 and of -1; the background scanner thread ends although the
> volume has not failed and the scanner was not shut down; the on-demand
> scanner never schedules the container again after the scan fails; a stored
> value of {{Integer.MAX_VALUE}} makes the scan reserve over 1 GB of direct
> memory. A passing run means the defect is present. With the patch all five
> fail (4 failures, 1 error): the two scan cases and the on-demand case fail on
> their assertion because the scan returns a result instead of throwing and the
> container is scheduled again, the buffer case fails on its assertion because
> the read buffer stays small, and the background scanner case ends with a
> timeout waiting for the thread to end, because the thread stays alive.
> h3. Patch
> [^CR-5-report-invalid-stored-bytes-per-checksum-in-container-scanner.patch],
> against 1e528b97aed3eef1be6846f1f0767c1ef98a883a. It also applies to master
> at 8ca44914771.
> {{KeyValueContainerCheck.scanBlock}} now treats a stored bytes per checksum
> of zero or less as a chunk it cannot verify: it records a {{CORRUPT_CHUNK}}
> scan error and adds the chunk to the merkle tree as not matching, instead of
> reading it, and keeps scanning the rest of the container. A new
> {{readBufferSize}} helper caps the direct read buffer, so an interval larger
> than {{OZONE_SCM_CHUNK_MAX_SIZE}} (the largest chunk a datanode reads at
> once) does not allocate a buffer larger than the chunk file, and the
> last-interval {{buffer.limit(...)}} is clamped to the buffer capacity.
> {{AbstractBackgroundContainerScanner.scanContainers}} and
> {{OnDemandContainerScanner.performOnDemandScan}} now also catch
> {{RuntimeException}} per container, so an unexpected failure scanning one
> container is logged and skipped instead of ending the background scanner
> thread or leaving the container scheduled. {{InterruptedException}} handling
> is unchanged, so shutdown still works.
> A container that already holds such a chunk is, after the patch, scanned to
> completion and the chunk is reported as corrupt, so the container is marked
> UNHEALTHY. The chunk's data on disk is not touched. Containers whose chunks
> all have a valid stored bytes per checksum are unaffected.
> The patch covers the container data scan only: other readers of a chunk with
> such stored metadata (chunk and block reads, reconciliation, file checksum
> computation) still fail for that read or task (from reading the code, not run
> with the patch).
> Covered by new tests in existing suites:
> {{TestKeyValueContainerCheck.testInvalidBytesPerChecksumReportedAsCorruptChunk}}
> and {{testBytesPerChecksumLargerThanChunkFile}},
> {{TestBackgroundContainerDataScanner.testUnexpectedScanFailureDoesNotStopIteration}},
> and
> {{TestOnDemandContainerScanner.testContainerScannedAgainAfterUnexpectedScanFailure}}.
> Without the patch the first, third and fourth of these fail with the
> unchecked exception escaping the scan, and the second does not compile
> because it also checks the new {{readBufferSize}} helper; with it the unit
> suites of the {{keyvalue}}, {{ozoneimpl}} and {{checksum}} packages in
> {{hadoop-hdds/container-service}} pass (931 tests, 10 skipped by the suites),
> the scanner integration suites pass (21 tests), checkstyle is clean, and the
> reproduction's five cases fail as described above. Not run on a released
> version.
> Found by code review of the container reconciliation paths in the datanode
> and SCM, as part of the TLA+ verification effort under HDDS-15926, on commit
> 1e528b97aed3eef1be6846f1f0767c1ef98a883a. 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]