This is an automated email from the ASF dual-hosted git repository.
andygrove pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-ballista.git
The following commit(s) were added to refs/heads/main by this push:
new 696ca29b3 fix(executor): keep heartbeating while all task slots are
busy (#2159)
696ca29b3 is described below
commit 696ca29b3c1fe3013238822b7f5d8cdb918fdaa3
Author: Andy Grove <[email protected]>
AuthorDate: Thu Jul 23 08:38:18 2026 -0600
fix(executor): keep heartbeating while all task slots are busy (#2159)
Under pull-based scheduling the executor's only heartbeat is `poll_work`,
but
the poll loop blocked on `free_vcores.acquire().await` before polling. When
every vcore is held by a task running longer than the scheduler's
`executor_timeout` (e.g. a long stage-1 scan on a wide TPC-H query), the
loop
blocked on acquire and never polled, so the scheduler stopped receiving
heartbeats and wrongly marked the healthy-but-busy executor dead, resetting
its
in-flight (and completed) tasks. When a stage's tasks consistently exceed
the
timeout this livelocks: reset -> re-dispatch -> reset, never converging.
Cap the vcore wait at `HEARTBEAT_POLL_INTERVAL` (5s); on timeout, poll
anyway
reporting `num_free_vcores: 0` (heartbeat only), so executor liveness no
longer
depends on vcore availability. A freed vcore still wakes the wait
immediately,
so scheduling responsiveness is unchanged.
---
ballista/executor/src/execution_loop.rs | 36 ++++++++++++++++++++++++++-------
1 file changed, 29 insertions(+), 7 deletions(-)
diff --git a/ballista/executor/src/execution_loop.rs
b/ballista/executor/src/execution_loop.rs
index c4fac99d2..e0eea30e4 100644
--- a/ballista/executor/src/execution_loop.rs
+++ b/ballista/executor/src/execution_loop.rs
@@ -49,6 +49,13 @@ use std::{sync::Arc, time::Duration};
use tokio::sync::{OwnedSemaphorePermit, Semaphore};
use tonic::codegen::{Body, Bytes, StdError};
+/// Maximum time the poll loop waits for a free vcore before polling the
+/// scheduler anyway. `poll_work` doubles as the executor's heartbeat under
+/// pull-based scheduling, so a fully-busy executor must keep polling
(reporting
+/// zero free vcores) or the scheduler times it out and resets its tasks. Kept
+/// well below the scheduler's executor timeout.
+const HEARTBEAT_POLL_INTERVAL: Duration = Duration::from_secs(5);
+
/// Main execution loop that polls the scheduler for available tasks.
///
/// Runs indefinitely, periodically asking the scheduler for work. When tasks
@@ -114,13 +121,28 @@ where
DedicatedExecutor::new("task_runner", executor_specification.vcores as
usize);
loop {
- // Wait for a vcore permit before asking for new work.
- let permit = free_vcores
- .acquire()
- .await
- .map_err(|_| BallistaError::Internal("vcore semaphore
closed".to_string()))?;
- // Make the vcore available again for the actual bind below.
- drop(permit);
+ // Wait for a vcore permit before asking for new work, but cap the wait
+ // so a fully-busy executor still polls the scheduler periodically.
+ // `poll_work` is the executor's ONLY heartbeat under pull-based
+ // scheduling (the scheduler records a heartbeat on every poll). If
every
+ // vcore is held by a task running longer than the scheduler's executor
+ // timeout, blocking here indefinitely stops heartbeats, so the
scheduler
+ // wrongly marks this healthy-but-busy executor dead and resets its
+ // in-flight tasks. On timeout we poll anyway below, reporting
+ // `num_free_vcores: 0`, so liveness no longer depends on vcore
+ // availability.
+ match tokio::time::timeout(HEARTBEAT_POLL_INTERVAL,
free_vcores.acquire()).await {
+ // A vcore is free; release it so the bind below can claim it.
+ Ok(Ok(permit)) => drop(permit),
+ // Semaphore closed (executor shutting down).
+ Ok(Err(_)) => {
+ return Err(BallistaError::Internal(
+ "vcore semaphore closed".to_string(),
+ ));
+ }
+ // No free vcore within the interval; poll anyway to stay alive.
+ Err(_) => {}
+ }
// Keeps track of whether we received task in last iteration
// to avoid going in sleep mode between polling
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]