You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@livy.apache.org by gg...@apache.org on 2026/04/27 13:54:38 UTC

(incubator-livy) branch master updated: [LIVY-1043] Track ZK connection transitions in Livy

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

gyogal pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-livy.git


The following commit(s) were added to refs/heads/master by this push:
     new 717bf747 [LIVY-1043] Track ZK connection transitions in Livy
717bf747 is described below

commit 717bf7478233fd0f92f55bfe18d6816723697e78
Author: Arnav Balyan <60...@users.noreply.github.com>
AuthorDate: Mon Apr 27 19:24:32 2026 +0530

    [LIVY-1043] Track ZK connection transitions in Livy
    
    ## What changes were proposed in this pull request?
    
     - Currently ZKManager does not observe connection transitions. When underlying connection changes, Livy is not aware of it, only ZK side logs show up.
     - Additionally register to ConnectionStateListener, which adds/listens to Livy owned logs for the connection changes.
     - Same events can be used for recovery in the future.
    
    ## How was this patch tested?
    
     - Unit tests
---
 .../livy/server/recovery/ZooKeeperManager.scala      | 17 +++++++++++++++++
 .../server/recovery/ZooKeeperStateStoreSpec.scala    | 20 ++++++++++++++++++++
 2 files changed, 37 insertions(+)

diff --git a/server/src/main/scala/org/apache/livy/server/recovery/ZooKeeperManager.scala b/server/src/main/scala/org/apache/livy/server/recovery/ZooKeeperManager.scala
index 60d98cc0..eb10f631 100644
--- a/server/src/main/scala/org/apache/livy/server/recovery/ZooKeeperManager.scala
+++ b/server/src/main/scala/org/apache/livy/server/recovery/ZooKeeperManager.scala
@@ -23,6 +23,7 @@ import scala.reflect.ClassTag
 import org.apache.curator.framework.api.UnhandledErrorListener
 import org.apache.curator.framework.CuratorFramework
 import org.apache.curator.framework.CuratorFrameworkFactory
+import org.apache.curator.framework.state.{ConnectionState, ConnectionStateListener}
 import org.apache.curator.retry.RetryNTimes
 import org.apache.zookeeper.KeeperException.NoNodeException
 
@@ -85,6 +86,22 @@ class ZooKeeperManager(
     }
   })
 
+  curatorClient.getConnectionStateListenable.addListener(new ConnectionStateListener {
+    override def stateChanged(client: CuratorFramework, newState: ConnectionState): Unit = {
+      newState match {
+        case ConnectionState.SUSPENDED =>
+          warn("ZooKeeper connection suspended; recovery state operations will block " +
+            "until the connection is re-established.")
+        case ConnectionState.RECONNECTED =>
+          info("ZooKeeper connection re-established within the session timeout.")
+        case ConnectionState.LOST =>
+          error("ZooKeeper session lost; ephemeral nodes and watches held by this client " +
+            "have been discarded. Subsequent operations will run against a new session.")
+        case _ =>
+      }
+    }
+  })
+
   def start(): Unit = {
     curatorClient.start()
   }
diff --git a/server/src/test/scala/org/apache/livy/server/recovery/ZooKeeperStateStoreSpec.scala b/server/src/test/scala/org/apache/livy/server/recovery/ZooKeeperStateStoreSpec.scala
index 0cd35db2..f3ffd9b6 100644
--- a/server/src/test/scala/org/apache/livy/server/recovery/ZooKeeperStateStoreSpec.scala
+++ b/server/src/test/scala/org/apache/livy/server/recovery/ZooKeeperStateStoreSpec.scala
@@ -22,7 +22,9 @@ import scala.collection.JavaConverters._
 import org.apache.curator.framework.CuratorFramework
 import org.apache.curator.framework.api._
 import org.apache.curator.framework.listen.Listenable
+import org.apache.curator.framework.state.{ConnectionState, ConnectionStateListener}
 import org.apache.zookeeper.data.Stat
+import org.mockito.ArgumentCaptor
 import org.mockito.Mockito._
 import org.scalatest.FunSpec
 import org.scalatest.Matchers._
@@ -43,6 +45,8 @@ class ZooKeeperStateStoreSpec extends FunSpec with LivyBaseUnitTestSuite {
       val curatorClient = mock[CuratorFramework]
       when(curatorClient.getUnhandledErrorListenable())
         .thenReturn(mock[Listenable[UnhandledErrorListener]])
+      when(curatorClient.getConnectionStateListenable())
+        .thenReturn(mock[Listenable[ConnectionStateListener]])
       val zkManager = new ZooKeeperManager(conf, Some(curatorClient))
       zkManager.start()
       val stateStore = new ZooKeeperStateStore(conf, zkManager)
@@ -186,6 +190,22 @@ class ZooKeeperStateStoreSpec extends FunSpec with LivyBaseUnitTestSuite {
       System.getProperty("zookeeper.sasl.clientconfig") shouldBe "Client"
     }
 
+    it("should register a ConnectionStateListener that handles all connection states") {
+      val curatorClient = mock[CuratorFramework]
+      when(curatorClient.getUnhandledErrorListenable())
+        .thenReturn(mock[Listenable[UnhandledErrorListener]])
+      val listenable = mock[Listenable[ConnectionStateListener]]
+      when(curatorClient.getConnectionStateListenable()).thenReturn(listenable)
+
+      new ZooKeeperManager(conf, Some(curatorClient))
+
+      val captor = ArgumentCaptor.forClass(classOf[ConnectionStateListener])
+      verify(listenable).addListener(captor.capture())
+      ConnectionState.values.foreach { state =>
+        captor.getValue.stateChanged(curatorClient, state)
+      }
+    }
+
     it("should not set SASL system properties when ZK SASL is disabled") {
       System.clearProperty("zookeeper.sasl.client")
       System.clearProperty("zookeeper.sasl.clientconfig")