[ 
https://issues.apache.org/jira/browse/HDDS-16052?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18100687#comment-18100687
 ] 

Ivan Andika commented on HDDS-16052:
------------------------------------

Two concrete race windows:
 # *Stale {{unused}} → over-release of committed space.* Thread A reads 
{{unused = 100}} for a 100-byte write; before A reaches 
{{{}incCommittedBytes{}}}, thread B writes 100 bytes (its {{updateWriteStats}} 
bumps {{bytesUsed}} by 100). A still decrements {{committedBytes}} by 100. Now 
{{bytesUsed}} has grown by 200 but {{committedBytes}} was only released by 100, 
when the whole 100 should have been released exactly once (or B's write 
shouldn't have been counted against it at all). Result: volume 
{{committedBytes}} drifts negative / under-reported, which makes 
{{VolumeUsage}} over-estimate available space for new container creation.

 # *Non-atomic, non-volatile {{{}committedSpace{}}}.* It's a plain boolean with 
no synchronization ([ContainerData.java (line 
96)](hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/ContainerData.java:96)),
 read in {{incrWriteBytes}} while {{commitSpace}} / {{releaseCommitSpace}} 
([ContainerData.java (line 
425)](hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/ContainerData.java:425))
 mutate it from container lifecycle paths. There's no happens-before edge, so 
visibility isn't guaranteed, and the check-then-act across {{commitSpace}} ↔ 
{{incrWriteBytes}} can double-release or double-acquire.

There's also an internal ordering wrinkle: {{incrBytesUsed}} runs in 
{{updateWriteStats}} ([ContainerData.java (line 
769)](hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/ContainerData.java:769))
 _before_ {{{}incrWriteBytes{}}}, and {{incrementUsedSpace}} runs _inside_ 
{{incrWriteBytes}} — so concurrent updates interleave the {{bytesUsed}} 
snapshot against volume-level updates arbitrarily.

*Severity caveat:* this is bounded accounting drift in space bookkeeping 
(committed/used bytes), not data corruption — the counters that matter for 
correctness ({{{}bytesUsed{}}}, {{{}writeBytes{}}}) can't lose updates. Fixing 
it properly would mean making the commit-space release atomic with the 
{{bytesUsed}} increment, e.g. a synchronized block or moving the decrement 
computation to use the return value of {{incrBytesUsed}} instead of the 
pre-read snapshot.

> ContainerData updateWriteStats is not atomic
> --------------------------------------------
>
>                 Key: HDDS-16052
>                 URL: https://issues.apache.org/jira/browse/HDDS-16052
>             Project: Apache Ozone
>          Issue Type: Sub-task
>            Reporter: Ivan Andika
>            Assignee: Ivan Andika
>            Priority: Major
>
> ContainerData#updateWriteStats is not atomic. It is updated for each 
> WriteChunk, but WriteChunk is only serialized for a single block. If there 
> are two concurrent WriteChunk for the same container, but with different 
> blocks, this can trigger race conditions which can cause updateWriteStats to 
> be interleaved which can end up causing inconsistency in ContainerData. We 
> also need to specify the thread safety guarantee of ContainerData. 
> Additionally, ContainerData is also has memory visibility (no happens-before 
> guarantee) issue since the shared variables like committedSpace is not 
> volatile (or protected by synchronization), although ContainerData can be 
> accessed by multiple threads.
> Example: container has 100 bytes unused, T1 and T2 each write 100 bytes.
>  * T1 reads {{{}unused = 100{}}}, gets preempted.
>  * T2 writes: {{bytesUsed}} goes to +100, releases 100 from 
> {{{}committedBytes{}}}. Container is now full, committed space fully 
> released. Correct so far.
>  * T1 resumes with its stale {{{}unused = 100{}}}, writes (now {{bytesUsed}} 
> = +200), and releases _another_ 100 from {{{}committedBytes{}}}.
> End state: {{committedBytes}} was decremented by 200 when only 100 was ever 
> reserved for that container. The volume-level {{committedBytes}} 
> under-reports by 100, so {{VolumeUsage}} thinks there's more free space for 
> new containers than there really is.
> A few things worth keeping in mind:
>  * The error compounds over many concurrent writes rather than being a 
> one-off, but it's bounded by how much committed space was actually reserved — 
> it can't go arbitrarily wrong from a single container.
>  * It self-corrects partially: DU/{{{}DedicatedDiskSpaceUsage{}}} 
> periodically recomputes real used space, and {{committedBytes}} is released 
> wholesale on container close via {{releaseCommitSpace}} (which has its own 
> races with the same stale-read pattern).
>  * The cleanest fix is to compute the decrement from the return value of 
> {{incrBytesUsed}} (the post-increment total) instead of the pre-read 
> {{{}unused{}}}, or guard the whole release block with the container's lock — 
> though that also needs {{{}commitSpace{}}}/{{{}releaseCommitSpace{}}} to 
> participate in the same locking to close the check-then-act window on the 
> {{committedSpace}} flag.



--
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