mbutrovich commented on code in PR #6205:
URL: https://github.com/apache/datafusion-comet/pull/6205#discussion_r4097161359


##########
native/core/src/execution/memory_pools/fair_pool.rs:
##########
@@ -154,16 +155,26 @@ impl MemoryPool for CometFairMemoryPool {
                 .pool_size
                 .checked_div(num)
                 .expect("overflow in checked_div");
-            // We use state.used instead of reservation.size() because 
DataFusion 53+
-            // calls pool.try_grow() before incrementing the reservation's 
atomic size,
-            // so reservation.size() would not include prior grows.
-            let used = state.used;
-            if limit < used + additional {
+            // DataFusion calls pool.try_grow() before adding to the 
reservation's size, so this
+            // is what the reservation held before the request. shrink() is 
the other way round.
+            let size = reservation.size();
+            if limit < size.saturating_add(additional) {
                 return resources_err!(
-                    "Failed to acquire {additional} bytes where {used} bytes 
already reserved ({} bytes overcommitted) and the fair limit is {limit} bytes, 
{num} registered",
+                    "Failed to acquire {additional} bytes where this 
reservation already holds {size} bytes and the fair limit is {limit} bytes, 
{num} registered ({} bytes overcommitted)",
                     self.spark.overcommit()
                 );
             }

Review Comment:
   What happens to other consumers when one operator grows through several 
sibling reservations? Each sibling is checked against the full share on its 
own, so the share check becomes a per-allocation cap for those operators, and 
only the pool-total check bounds them. The streaming merge in sort creates a 
fresh `new_empty()` reservation for every cursor batch ([`stream.rs` 
L198-L199](https://github.com/apache/datafusion/blob/7d3835c71f30cbd3c3ae4041732267f1f453097a/datafusion/physical-plan/src/sorts/stream.rs#L198-L199)),
 and the hash aggregate's merge path does the same ([`hash_stream.rs` 
L402](https://github.com/apache/datafusion/blob/7d3835c71f30cbd3c3ae4041732267f1f453097a/datafusion/physical-plan/src/aggregates/hash_stream.rs#L402)).
   
   I checked this with a scratch test at the head commit: a 100-byte pool 
wrapped in `TrackConsumersPool`, consumer `a` with a sibling from 
`new_empty()`, and consumer `b`. `a` and its sibling each reserve 50, then 
`b.try_grow(1)` fails with "Failed to acquire 1 bytes where 100 bytes already 
reserved ... and the pool limit is 100 bytes". On main, `a`'s second 50 would 
have been refused, so `b` could not be starved this way. The PR description 
says this never refuses a request that main accepts. That holds for a given 
pool state, but this PR makes pool states reachable that main never reached, 
and in them `b` is refused where main would have accepted.
   
   Tracking usage per consumer instead of per reservation would close this and 
would also stop the check from depending on when DataFusion updates 
`reservation.size()`, which is the ordering that already changed once in 
DataFusion 53. `reservation.consumer().id()` is available in `try_grow`, `grow` 
and `shrink`, and #5847 has an implementation of that. Could this PR take the 
per-consumer accounting from there, or, if you want to keep it per reservation 
for the backport, add a test that pins down the sibling behavior and correct 
the tuning guide sentence ("prevents operators from using more than an even 
fraction") that it contradicts?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to