Siyao Meng created HDDS-16785:
---------------------------------

             Summary: Part commit rejected with NO_SUCH_MULTIPART_UPLOAD_ERROR 
leaves its open key behind, so its blocks are queued for deletion twice
                 Key: HDDS-16785
                 URL: https://issues.apache.org/jira/browse/HDDS-16785
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: MC-5-delete-part-open-key-on-rejected-part-commit.patch, 
TestBugMC5RejectedPartCommitOpenKey.java

h3. Mechanism
When {{S3MultipartUploadCommitPartRequest.validateAndUpdateCache}} finds no 
{{multipartInfoTable}} entry for the upload, it throws 
{{NO_SUCH_MULTIPART_UPLOAD_ERROR}} before any cache update. 
{{S3MultipartUploadCommitPartResponse.checkAndUpdateDB}} then moves the blocks 
named in the commit to {{deletedTable}} but does not delete the open key of the 
part. Part open keys are not flagged as multipart keys, so 
{{OpenKeyCleanupService}} selects the leftover entry after 
{{ozone.om.open.key.expire.threshold}} (default 7d) and 
{{OMOpenKeysDeleteRequest}} puts the same blocks into {{deletedTable}} a second 
time under another key.

The comment in that response branch ("So, delete this part information.") and 
{{TestS3MultipartUploadCommitPartResponse.testWithMultipartUploadError}}, which 
asserts that the open key is gone but never creates it, indicate that the entry 
was meant to be removed.

h3. Trigger
# InitiateMultipartUpload, then open a part and write data.
# The upload is aborted before the part is committed (run with a client abort, 
an abort by {{MultipartUploadCleanupService}} removes the same entry).
# The part commit is rejected with {{NO_SUCH_MULTIPART_UPLOAD_ERROR}}.

No fault is needed. A part commit that arrives after CompleteMultipartUpload 
takes the same branch (same code path, no separate reproduction).

h3. Impact
* Every rejected part commit leaves an open key entry until the open key 
expiry, and its blocks are then sent to SCM for deletion a second time. 
Datanodes skip duplicate block deletions since HDDS-11267, so no wrong datanode 
state is expected. I ran the OM side only, without datanodes.
* The leftover open key is still a valid write session. The stock client does 
not use it after the failed part commit, but a {{CommitKey}} on it is accepted 
and publishes a key whose block is already in {{deletedTable}}. The block is 
then sent for deletion while the key stays in {{keyTable}} (run against a real 
OM with a recording SCM client, actual data deletion not run).

h3. Reproduction
PASS, deterministic, unmodified source. 
[^TestBugMC5RejectedPartCommitOpenKey.java] ({{hadoop-ozone/ozone-manager}}) 
drives one real OM with its background services through 
{{OzoneManagerProtocol}}, with a recording SCM block client and no datanodes. 
The open key expiry is shortened to 200 ms and the two services are held with 
their own suspend and resume hooks so the tables can be read between the steps. 
The first test shows the same block ID in two {{deletedTable}} entries and sent 
to SCM twice. The second commits the leftover open key as a key and shows its 
block sent for deletion while the key remains. A passing test means the defect 
is present.

h3. Patch
[^MC-5-delete-part-open-key-on-rejected-part-commit.patch], against 
d067ba38e7aea917db7efd290b4af53987ec985a. It also applies to master at 
a6332fb5987.

On the rejected path the request adds a delete marker for the part open key to 
the table cache and the response deletes the key in the batch that writes 
{{deletedTable}}. Because the open key is then gone, the blocks that were 
allocated to the part but not named in the commit are added to the same 
{{deletedTable}} entry. Today only the open key cleanup releases those, so 
without that addition they would leak.

Covered by {{testValidateAndUpdateCacheMultipartNotFoundDeletesOpenKey}} in the 
existing {{TestS3MultipartUploadCommitPartRequest}} (inherited by the FSO 
subclass), and {{testWithMultipartUploadError}} now creates the open key so 
that its existing assertion is meaningful. Without the change both fail for 
both layouts because the open key is still present. With it the multipart 
request and response unit suites and {{TestOMOpenKeysDeleteRequest}} (142 
tests), the open key cleanup, multipart cleanup and key deleting service suites 
(37 tests) and the multipart tests of {{TestOzoneRpcClient}} on a mini cluster 
(29 tests, including {{testAbortUploadFailWithInProgressPartUpload}} and 
{{testCommitPartAfterCompleteUpload}}) pass, checkstyle is clean. With the 
patch both tests of the reproduction fail, because the open key no longer 
exists after the rejected commit. Entries left behind by earlier rejected 
commits are not touched, the open key cleanup removes them as before.

Found by TLA+ model checking and code review of the OM expired multipart upload 
cleanup paths under HDDS-15926, on commit 
d067ba38e7aea917db7efd290b4af53987ec985a. 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