You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kyuubi.apache.org by ch...@apache.org on 2026/10/28 01:49:24 UTC

(kyuubi) branch branch-1.12 updated: [KYUUBI #7759] Reduce the connection limit count when session creation fails

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

pan3793 pushed a commit to branch branch-1.12
in repository https://gitbox.apache.org/repos/asf/kyuubi.git


The following commit(s) were added to refs/heads/branch-1.12 by this push:
     new 903bd5de28 [KYUUBI #7759] Reduce the connection limit count when session creation fails
903bd5de28 is described below

commit 903bd5de2882c468040cf8fbb89eab29b9c30c62
Author: Jaeho Yoo <ja...@navercorp.com>
AuthorDate: Wed Oct 28 09:49:03 2026 +0800

    [KYUUBI #7759] Reduce the connection limit count when session creation fails
    
    ### Why are the changes needed?
    
    `KyuubiSessionManager.openSession` takes a count from the session limiter before the session is
    opened, and `closeSession` is the only place that gives one back.
    
    `SessionManager.openSession` calls `createSession` outside the `try` whose `catch` calls
    `closeSession`, and `AbstractSession.normalizedConf` is a `val`, so a session whose conf fails
    validation dies in the constructor and is never registered in `handleToSession`. `closeSession`
    never runs for it, and the count is kept for good.
    
    Once as many opens have failed as `kyuubi.server.limit.connections.per.user` allows, every further
    open from that user is rejected by the limiter although the user holds no session, until the
    server restarts. `kyuubi.server.limit.connections.per.ipaddress` leaks the same way.
    
    Ordinary client configuration reaches this on a server that follows the documented
    `kyuubi.session.conf.restrict.list` guidance: `spark.sql.extensions` is in the example restrict
    list in the [session configuration docs](https://kyuubi.readthedocs.io/en/master/security/session_configuration.html),
    and a client that keeps sending it locks its own account out. We hit this in production - three
    server nodes each rejected 20 opens for the restricted key, after which every open from that user
    failed with the connection-limit message while `kyuubi_connection_opened` stayed 0, and a rolling
    restart was the only way to recover.
    
    Affects master and 1.11.1. Batch sessions are not affected: `openBatchSession` takes its count
    after the session is built and inside the `try` that closes it.
    
    close #7759
    
    ### How was this patch tested?
    
    Added `KyuubiSessionManagerLimiterSuite`, which sets `kyuubi.server.limit.connections.per.user=2`
    and a restrict list, then opens `limit + 2` sessions carrying the restricted key and asserts every
    attempt is still rejected by the restrict list rather than by the limiter.
    
        build/mvn test -pl kyuubi-server -am -Dtest=none \
            -DwildcardSuites=org.apache.kyuubi.session.KyuubiSessionManagerLimiterSuite
    
    Passes with the change; with the change reverted it fails on the third attempt with
    `"Connection limit per user reached (user: user limit: 2)" did not contain
    "spark.sql.extensions is a restrict key"`, which is the production symptom.
    
    ### Was this patch assisted by generative AI tooling?
    
    Assisted-by: Claude Opus 5
    
    Closes #7760 from Chaho12/session-limiter-leak.
    
    Closes #7759
    
    ebbc2c933 [Jaeho Yoo] [KYUUBI #7759] Reduce the connection limit count when session creation fails
    
    Authored-by: Jaeho Yoo <ja...@navercorp.com>
    Signed-off-by: Cheng Pan <ch...@apache.org>
    (cherry picked from commit a4f61c4b15b784a6037c00ecfdb291d5563d0c70)
    Signed-off-by: Cheng Pan <ch...@apache.org>
---
 .../kyuubi/session/KyuubiSessionManager.scala      | 34 +++++++++----
 .../session/KyuubiSessionManagerLimiterSuite.scala | 58 ++++++++++++++++++++++
 2 files changed, 82 insertions(+), 10 deletions(-)

diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionManager.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionManager.scala
index 344da0e71e..8c8bd25c0f 100644
--- a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionManager.scala
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionManager.scala
@@ -93,16 +93,30 @@ class KyuubiSessionManager private (name: String) extends SessionManager(name) {
       ipAddress: String,
       conf: Map[String, String]): Session = {
     val userConf = this.getConf.getUserDefaults(user)
-    new KyuubiSessionImpl(
-      protocol,
-      user,
-      password,
-      ipAddress,
-      conf,
-      this,
-      userConf,
-      userConf.get(ENGINE_DO_AS_ENABLED),
-      parser)
+    try {
+      new KyuubiSessionImpl(
+        protocol,
+        user,
+        password,
+        ipAddress,
+        conf,
+        this,
+        userConf,
+        userConf.get(ENGINE_DO_AS_ENABLED),
+        parser)
+    } catch {
+      case e: Throwable =>
+        // The count openSession took is given back by closeSession alone. A session that fails
+        // here - on the session conf validation in AbstractSession.normalizedConf, for one - is
+        // never registered in handleToSession, so closeSession never runs for it and the count
+        // stays taken: once as many opens have failed as the limit allows, the user and the
+        // client address are turned away by the limiter until the server restarts. A failure
+        // after setSession is already given back by the closeSession call in
+        // SessionManager.openSession, so giving it back here alone keeps it from going back
+        // twice.
+        limiter.foreach(_.decrement(UserIpAddress(user, ipAddress)))
+        throw e
+    }
   }
 
   override def openSession(
diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/session/KyuubiSessionManagerLimiterSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/session/KyuubiSessionManagerLimiterSuite.scala
new file mode 100644
index 0000000000..a74fc29407
--- /dev/null
+++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/session/KyuubiSessionManagerLimiterSuite.scala
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.kyuubi.session
+
+import scala.collection.JavaConverters._
+
+import org.apache.kyuubi.{KyuubiFunSuite, Utils, WithKyuubiServer}
+import org.apache.kyuubi.config.KyuubiConf
+import org.apache.kyuubi.operation.TClientTestUtils
+import org.apache.kyuubi.shaded.hive.service.rpc.thrift.TOpenSessionReq
+
+class KyuubiSessionManagerLimiterSuite extends WithKyuubiServer with KyuubiFunSuite {
+
+  private val perUserLimit = 2
+  private val restrictKey = "spark.sql.extensions"
+
+  override protected val conf: KyuubiConf = KyuubiConf()
+    .set(KyuubiConf.SERVER_LIMIT_CONNECTIONS_PER_USER, perUserLimit)
+    .set(KyuubiConf.SESSION_CONF_RESTRICT_LIST, Set(restrictKey))
+
+  test("connection limit count is given back when the session fails before it is registered") {
+    // A session carrying a restricted key fails while it is created, before it is put into
+    // handleToSession, so closeSession never runs for it. Unless the count openSession took is
+    // given back there, every attempt keeps one, and the attempts past the limit are turned away
+    // by the limiter rather than by the restrict list - the user stays locked out until the
+    // server restarts.
+    (1 to perUserLimit + 2).foreach { attempt =>
+      TClientTestUtils.withThriftClient(server.frontendServices.head) { client =>
+        val req = new TOpenSessionReq()
+        req.setUsername(Utils.currentUser)
+        req.setPassword("anonymous")
+        req.setConfiguration(Map(restrictKey -> "org.apache.kyuubi.NoopExtensions").asJava)
+        val errorMessage = client.OpenSession(req).getStatus.getErrorMessage
+        assert(
+          errorMessage.contains(s"$restrictKey is a restrict key"),
+          s"attempt $attempt: $errorMessage")
+        assert(
+          !errorMessage.contains("Connection limit per user reached"),
+          s"attempt $attempt: the connection limit count was not given back")
+      }
+    }
+  }
+}