andygrove commented on code in PR #6092:
URL: https://github.com/apache/datafusion-comet/pull/6092#discussion_r4067214575


##########
native/core/src/execution/jni_api.rs:
##########
@@ -1113,30 +1133,26 @@ pub unsafe extern "system" fn 
Java_org_apache_comet_Native_executePlan(
                 }
             }
 
-            // ScanExec path: busy-poll to interleave JVM batch pulls with 
stream polling
+            // ScanExec path: JVM-fed scans return Pending without a waker and 
are refilled here.
+            // Nothing pulled means the stream waits on native I/O, so park 
instead of spinning.
             get_runtime().block_on(async {
                 loop {
                     let next_item = 
exec_context.stream.as_mut().unwrap().next();
                     let poll_output = poll!(next_item);
 
-                    // Only check time/tracing every 100 polls to reduce 
overhead
-                    exec_context.poll_count_since_metrics_check += 1;
-                    if exec_context.poll_count_since_metrics_check >= 100 {
-                        exec_context.poll_count_since_metrics_check = 0;
-                        if let Some(interval) = 
exec_context.metrics_update_interval {
-                            let now = Instant::now();
-                            if now - exec_context.metrics_last_update_time >= 
interval {
-                                update_metrics(env, exec_context)?;
-                                exec_context.metrics_last_update_time = now;
-                            }
-                        }
-                        if exec_context.tracing_enabled {
-                            log_memory_usage(
-                                &exec_context.tracing_memory_metric_name,
-                                
total_reserved_for_thread(exec_context.rust_thread_id) as u64,
-                            );
+                    if let Some(interval) = 
exec_context.metrics_update_interval {
+                        let now = Instant::now();
+                        if now - exec_context.metrics_last_update_time >= 
interval {
+                            update_metrics(env, exec_context)?;
+                            exec_context.metrics_last_update_time = now;
                         }
                     }
+                    if exec_context.tracing_enabled {

Review Comment:
   The tracing block came out of the 100-poll gate along with the metrics 
check, and unlike the metrics check there is no interval behind it now. On the 
path where the loop still turns quickly, a shuffle read or a broadcast build 
side where every iteration does a real JVM pull, that is one `log_memory_usage` 
per pulled batch instead of one per hundred. Each one is a `format!` plus the 
process-wide `RECORDER` writer mutex in `native/common/src/tracing.rs`, and 
`total_reserved_for_thread` takes the global pool registry lock on top of that. 
Every task on the executor shares both of those locks.
   
   During an I/O wait the new code is far quieter than the old spin, so this is 
only about the pulling path. That is also the path someone opens a trace to 
look at, though, so the denser sampling lands right where it perturbs the thing 
being measured. Could the tracing emission sit behind the same interval check 
as `update_metrics`? That would make trace density independent of how many 
times the loop went round, which seems closer to what a counter track wants 
anyway.



##########
native/core/src/execution/operators/scan.rs:
##########
@@ -112,23 +112,26 @@ impl ScanExec {
         *self.batch.try_lock().unwrap() = Some(input);
     }
 
-    /// Pull next input batch from the upstream `ArrowArrayStreamReader`.
-    pub fn get_next_batch(&mut self) -> Result<(), CometError> {
+    /// Pulls the next input batch from the upstream `ArrowArrayStreamReader` 
unless one is
+    /// already buffered; returns whether it did.
+    pub fn get_next_batch(&mut self) -> Result<bool, CometError> {
         if self.input_source.is_none() {
             // This is a unit test. Input batches are seeded via 
`set_input_batch`.
-            return Ok(());
+            return Ok(false);
         }
 
         let mut current_batch = self.batch.try_lock().unwrap();
-        if current_batch.is_none() {
-            let mut timer = self.baseline_metrics.elapsed_compute().timer();
-            let next_batch =
-                ScanExec::pull_next(self.exec_context_id, 
self.input_source.as_ref().unwrap())?;
-            *current_batch = Some(next_batch);
-            timer.stop();
+        if current_batch.is_some() {
+            return Ok(false);
         }
 
-        Ok(())
+        let mut timer = self.baseline_metrics.elapsed_compute().timer();
+        let next_batch =
+            ScanExec::pull_next(self.exec_context_id, 
self.input_source.as_ref().unwrap())?;
+        *current_batch = Some(next_batch);
+        timer.stop();
+
+        Ok(true)

Review Comment:
   This is the return value I want to poke at. Down in `ScanStream::poll_next`, 
the `*scan_batch = None` at line 350 runs for the `InputBatch::EOF` arm too, so 
EOF is not sticky in the stream. Once a JVM-fed scan finishes, the next 
`get_next_batch` finds an empty buffer, makes another JNI round trip into an 
exhausted reader, gets `InputBatch::EOF` back, and returns `true` here. #6091 
describes the resting state as every JVM-fed scan holding a batch or having 
reached EOF making the pull a no-op, and the EOF half of that isn't quite what 
the code does today.
   
   In the broadcast shape it costs one extra non-parking iteration and then 
settles, because the buffer holds `Some(EOF)` afterwards and nothing polls the 
exhausted stream again. What I am less sure about is an operator that does 
re-poll it. An exhausted `ScanStream` returns `Pending` rather than 
`Ready(None)` on a re-poll, the loop refills it with EOF, gets `true` back, 
skips the park, and the cycle turns at full speed. Would leaving the EOF in 
place, so `poll_next` returns `Ready(None)` without clearing, close that off? 
`get_next_batch` becomes a real no-op after EOF, the wasted JNI call goes away, 
and the invariant the issue leans on becomes true. Same thing in 
`shuffle_scan.rs` at line 397.



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