This is an automated email from the ASF dual-hosted git repository.

Dandandan 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 25b330778 fix(scheduler): drop an executor's cached gRPC client when 
it is removed (#2310)
25b330778 is described below

commit 25b33077847701c056d36f63e8d714cf2a5ce207
Author: DaniĆ«l Heres <[email protected]>
AuthorDate: Sun Aug 16 11:25:49 2026 +0200

    fix(scheduler): drop an executor's cached gRPC client when it is removed 
(#2310)
    
    * fix(scheduler): drop an executor's cached gRPC client when it is removed
    
    `ExecutorManager` caches an `ExecutorGrpcClient` per executor in
    `self.clients`, but nothing ever removes an entry: the map is only
    inserted into by `get_client` and read back. `remove_executor` drops the
    executor from the cluster state and leaves the client behind.
    
    Because an executor id is a fresh uuid per executor process, a client left
    in the map is never looked up again. It is not dropped either, so the
    scheduler retains the client and its `Channel` for as long as it runs. A
    cluster with executor churn (autoscaling, preemption, restarts) therefore
    accumulates one dead client per executor that has ever registered.
    
    Remove the cached client in `remove_executor`, before delegating to the
    cluster state so that it goes even if that call fails. Every removal path
    already funnels through here, as `SchedulerState::remove_executor`
    documents.
    
    Co-Authored-By: Claude <[email protected]>
    
    * Clean up comments in executor manager
    
    Removed unnecessary comments about cached client removal.
    
    ---------
    
    Co-authored-by: Claude <[email protected]>
---
 ballista/scheduler/src/state/executor_manager.rs | 27 ++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/ballista/scheduler/src/state/executor_manager.rs 
b/ballista/scheduler/src/state/executor_manager.rs
index 1da756566..da8d7470b 100644
--- a/ballista/scheduler/src/state/executor_manager.rs
+++ b/ballista/scheduler/src/state/executor_manager.rs
@@ -381,6 +381,8 @@ impl ExecutorManager {
         reason: Option<String>,
     ) -> Result<()> {
         info!("Removing executor {executor_id}: {reason:?}");
+        // Drop the cached client
+        self.clients.remove(executor_id);
         self.cluster_state.remove_executor(executor_id).await
     }
 
@@ -608,6 +610,7 @@ mod tests {
     use crate::test_utils::test_cluster_context;
     use ballista_core::extension::SessionConfigExt;
     use datafusion::prelude::SessionConfig;
+    use tonic::transport::Endpoint;
 
     #[test]
     fn grpc_client_max_message_size_flag_reaches_client_config() {
@@ -646,4 +649,28 @@ mod tests {
             32 * 1024 * 1024
         );
     }
+
+    #[tokio::test]
+    async fn removing_an_executor_drops_its_cached_client() {
+        let manager = ExecutorManager::new(
+            test_cluster_context().cluster_state(),
+            Arc::new(SchedulerConfig::default()),
+        );
+
+        // `get_client` needs an executor to connect to, so cache a client
+        // directly. `connect_lazy` gives a `Channel` without a server behind 
it.
+        let channel = 
Endpoint::from_static("http://localhost:1";).connect_lazy();
+        manager
+            .clients
+            .insert("executor-1".to_owned(), ExecutorGrpcClient::new(channel));
+
+        manager
+            .remove_executor("executor-1", None)
+            .await
+            .expect("executor removed");
+
+        // An executor id is a fresh uuid per executor process, so a client 
left
+        // behind here would never be reused, and never dropped either.
+        assert!(manager.clients.is_empty());
+    }
 }


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

Reply via email to