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

jiangtian pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new a835194b466 Fix query leak during login & fix inconsistent expiration 
hints & skipped expiration check when password history is missing (#16166)
a835194b466 is described below

commit a835194b4662f4328f468e2b357f937f82ab6093
Author: Jiang Tian <[email protected]>
AuthorDate: Sat Aug 16 12:46:43 2025 +0800

    Fix query leak during login & fix inconsistent expiration hints & skipped 
expiration check when password history is missing (#16166)
    
    * Fix query leak during login & fix inconsistent expiration hints
    
    * fix test
    
    * Fix ignoring expiration check when the password history is missing
    
    * change ci jdk distribution
    
    * revert somd jdks of ci work flow
    
    * avoid using ClusterResultSetInTest
    
    * fix session not open
    
    * fix test
    
    * spotless
---
 .../org/apache/iotdb/db/it/IoTDBLoginAndOutIT.java | 87 ++++++++++++++++++++++
 .../iotdb/db/protocol/session/SessionManager.java  | 19 +++--
 .../sql/ast/RelationalAuthorStatement.java         |  9 ++-
 .../plan/statement/sys/AuthorStatement.java        |  9 ++-
 .../apache/iotdb/db/utils/DataNodeAuthUtils.java   | 17 ++++-
 5 files changed, 129 insertions(+), 12 deletions(-)

diff --git 
a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBLoginAndOutIT.java 
b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBLoginAndOutIT.java
new file mode 100644
index 00000000000..3d6f07681de
--- /dev/null
+++ 
b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBLoginAndOutIT.java
@@ -0,0 +1,87 @@
+/*
+ * 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.iotdb.db.it;
+
+import org.apache.iotdb.isession.SessionDataSet;
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.it.env.cluster.node.DataNodeWrapper;
+import org.apache.iotdb.it.framework.IoTDBTestRunner;
+import org.apache.iotdb.itbase.category.ClusterIT;
+import org.apache.iotdb.itbase.category.LocalStandaloneIT;
+import org.apache.iotdb.session.Session;
+
+import org.apache.tsfile.read.common.RowRecord;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import java.sql.Connection;
+
+import static org.junit.Assert.assertTrue;
+
+@RunWith(IoTDBTestRunner.class)
+@Category({LocalStandaloneIT.class, ClusterIT.class})
+public class IoTDBLoginAndOutIT {
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    // use small page
+    EnvFactory.getEnv().initClusterEnvironment();
+  }
+
+  @AfterClass
+  public static void tearDown() throws Exception {
+    EnvFactory.getEnv().cleanClusterEnvironment();
+  }
+
+  @Test
+  public void testRepeatedlyLoginAndOut() throws Exception {
+    int attempts = 100;
+    for (int i = 0; i < attempts; i++) {
+      try (Connection ignored = EnvFactory.getEnv().getConnection()) {
+        // do nothing
+      }
+    }
+    DataNodeWrapper dataNodeWrapper = 
EnvFactory.getEnv().getDataNodeWrapper(0);
+    try (Session session = new Session(dataNodeWrapper.getIp(), 
dataNodeWrapper.getPort())) {
+      session.open();
+      int rowCount = 0;
+      SessionDataSet dataSet = session.executeQueryStatement("SHOW QUERIES");
+      int columnCount = dataSet.getColumnNames().size();
+      for (int i = 0; i < columnCount; i++) {
+        System.out.printf("%s, ", dataSet.getColumnNames().get(i));
+      }
+      System.out.println();
+      while (dataSet.hasNext()) {
+        RowRecord rec = dataSet.next();
+        System.out.printf("%s, ", rec.getTimestamp());
+        for (int i = 0; i < columnCount - 1; i++) {
+          System.out.printf("%s, ", rec.getFields().get(i).toString());
+        }
+        System.out.println();
+        rowCount++;
+      }
+      // show queries and show data nodes
+      assertTrue(rowCount <= 10);
+    }
+  }
+}
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/SessionManager.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/SessionManager.java
index 69008d34b75..9fa85a0be76 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/SessionManager.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/SessionManager.java
@@ -152,12 +152,14 @@ public class SessionManager implements 
SessionManagerMBean {
     lastDataQueryReq.setPaths(
         Collections.singletonList(
             SystemConstant.PREFIX_PASSWORD_HISTORY + ".`_" + username + 
"`.password"));
+
+    long queryId = -1;
     try {
       Statement statement = 
StatementGenerator.createStatement(lastDataQueryReq);
       SessionInfo sessionInfo =
           new SessionInfo(0, AuthorityChecker.SUPER_USER, 
ZoneId.systemDefault());
 
-      long queryId = requestQueryId();
+      queryId = requestQueryId();
       ExecutionResult result =
           Coordinator.getInstance()
               .executeForTreeModel(
@@ -196,8 +198,9 @@ public class SessionManager implements SessionManagerMBean {
             return lastPasswordTime + passwordExpirationDays * 1000 * 86400;
           }
         } else {
-          // the password is incorrect, later logIn will fail
-          return Long.MAX_VALUE;
+          // 1. the password is incorrect, later logIn will fail
+          // 2. the password history does not record correctly, use the 
current time to create one
+          return null;
         }
       } else {
         return null;
@@ -211,6 +214,10 @@ public class SessionManager implements SessionManagerMBean 
{
             "Internal server error " + ", please log in later or disable 
password expiration.",
             TSStatusCode.INTERNAL_SERVER_ERROR.getStatusCode());
       }
+    } finally {
+      if (queryId != -1) {
+        Coordinator.getInstance().cleanupQueryExecution(queryId);
+      }
     }
   }
 
@@ -256,7 +263,9 @@ public class SessionManager implements SessionManagerMBean {
           LOGGER.info(
               "No password history for user {}, using the current time to 
create a new one",
               username);
-          TSStatus tsStatus = DataNodeAuthUtils.recordPassword(username, 
password, null);
+          long currentTime = CommonDateTimeUtils.currentTime();
+          TSStatus tsStatus =
+              DataNodeAuthUtils.recordPassword(username, password, null, 
currentTime);
           if (tsStatus.getCode() != 
TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
             openSessionResp
                 .sessionId(-1)
@@ -265,7 +274,7 @@ public class SessionManager implements SessionManagerMBean {
             return openSessionResp;
           }
           timeToExpire =
-              System.currentTimeMillis()
+              CommonDateTimeUtils.convertIoTDBTimeToMillis(currentTime)
                   + 
CommonDescriptor.getInstance().getConfig().getPasswordExpirationDays()
                       * 1000
                       * 86400;
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/RelationalAuthorStatement.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/RelationalAuthorStatement.java
index 74454e0eaf3..4e2b4597672 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/RelationalAuthorStatement.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/RelationalAuthorStatement.java
@@ -20,6 +20,7 @@ package 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast;
 
 import org.apache.iotdb.common.rpc.thrift.TSStatus;
 import org.apache.iotdb.commons.auth.entity.PrivilegeType;
+import org.apache.iotdb.commons.utils.CommonDateTimeUtils;
 import org.apache.iotdb.db.queryengine.plan.analyze.QueryType;
 import org.apache.iotdb.db.queryengine.plan.relational.type.AuthorRType;
 import org.apache.iotdb.db.utils.DataNodeAuthUtils;
@@ -265,7 +266,9 @@ public class RelationalAuthorStatement extends Statement {
   }
 
   private TSStatus onCreateUserSuccess() {
-    TSStatus tsStatus = DataNodeAuthUtils.recordPassword(userName, password, 
null);
+    TSStatus tsStatus =
+        DataNodeAuthUtils.recordPassword(
+            userName, password, null, CommonDateTimeUtils.currentTime());
     try {
       RpcUtils.verifySuccess(tsStatus);
     } catch (StatementExecutionException e) {
@@ -275,7 +278,9 @@ public class RelationalAuthorStatement extends Statement {
   }
 
   private TSStatus onUpdateUserSuccess() {
-    TSStatus tsStatus = DataNodeAuthUtils.recordPassword(userName, password, 
oldPassword);
+    TSStatus tsStatus =
+        DataNodeAuthUtils.recordPassword(
+            userName, password, oldPassword, 
CommonDateTimeUtils.currentTime());
     try {
       RpcUtils.verifySuccess(tsStatus);
     } catch (StatementExecutionException e) {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/sys/AuthorStatement.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/sys/AuthorStatement.java
index fc0481fa656..1cc2b3d499c 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/sys/AuthorStatement.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/sys/AuthorStatement.java
@@ -22,6 +22,7 @@ package org.apache.iotdb.db.queryengine.plan.statement.sys;
 import org.apache.iotdb.common.rpc.thrift.TSStatus;
 import org.apache.iotdb.commons.auth.entity.PrivilegeType;
 import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.commons.utils.CommonDateTimeUtils;
 import org.apache.iotdb.db.auth.AuthorityChecker;
 import org.apache.iotdb.db.queryengine.plan.analyze.QueryType;
 import org.apache.iotdb.db.queryengine.plan.statement.AuthorType;
@@ -364,7 +365,9 @@ public class AuthorStatement extends Statement implements 
IConfigStatement {
   }
 
   private TSStatus onCreateUserSuccess() {
-    TSStatus tsStatus = DataNodeAuthUtils.recordPassword(userName, password, 
null);
+    TSStatus tsStatus =
+        DataNodeAuthUtils.recordPassword(
+            userName, password, null, CommonDateTimeUtils.currentTime());
     try {
       RpcUtils.verifySuccess(tsStatus);
     } catch (StatementExecutionException e) {
@@ -374,7 +377,9 @@ public class AuthorStatement extends Statement implements 
IConfigStatement {
   }
 
   private TSStatus onUpdateUserSuccess() {
-    TSStatus tsStatus = DataNodeAuthUtils.recordPassword(userName, 
newPassword, password);
+    TSStatus tsStatus =
+        DataNodeAuthUtils.recordPassword(
+            userName, newPassword, password, 
CommonDateTimeUtils.currentTime());
     try {
       RpcUtils.verifySuccess(tsStatus);
     } catch (StatementExecutionException e) {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/DataNodeAuthUtils.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/DataNodeAuthUtils.java
index 3526c2619ff..180fb3e3e68 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/DataNodeAuthUtils.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/DataNodeAuthUtils.java
@@ -65,6 +65,7 @@ public class DataNodeAuthUtils {
    */
   public static long getPasswordChangeTimeMillis(String username, String 
password) {
 
+    long queryId = -1;
     try {
       Statement statement =
           StatementGenerator.createStatement(
@@ -79,7 +80,7 @@ public class DataNodeAuthUtils {
       SessionInfo sessionInfo =
           new SessionInfo(0, AuthorityChecker.SUPER_USER, 
ZoneId.systemDefault());
 
-      long queryId = SessionManager.getInstance().requestQueryId();
+      queryId = SessionManager.getInstance().requestQueryId();
       ExecutionResult result =
           Coordinator.getInstance()
               .executeForTreeModel(
@@ -108,6 +109,10 @@ public class DataNodeAuthUtils {
       }
     } catch (IoTDBException e) {
       LOGGER.warn("Cannot generate query for checking password reuse 
interval", e);
+    } finally {
+      if (queryId != -1) {
+        Coordinator.getInstance().cleanupQueryExecution(queryId);
+      }
     }
     return -1;
   }
@@ -139,7 +144,8 @@ public class DataNodeAuthUtils {
         currentTimeMillis);
   }
 
-  public static TSStatus recordPassword(String username, String password, 
String oldPassword) {
+  public static TSStatus recordPassword(
+      String username, String password, String oldPassword, long timeToRecord) 
{
     InsertRowStatement insertRowStatement = new InsertRowStatement();
     try {
       insertRowStatement.setDevicePath(
@@ -160,11 +166,12 @@ public class DataNodeAuthUtils {
                   + " because the path will be illegal");
     }
 
+    long queryId = -1;
     try {
       SessionInfo sessionInfo =
           new SessionInfo(0, AuthorityChecker.SUPER_USER, 
ZoneId.systemDefault());
 
-      long queryId = SessionManager.getInstance().requestQueryId();
+      queryId = SessionManager.getInstance().requestQueryId();
       ExecutionResult result =
           Coordinator.getInstance()
               .executeForTreeModel(
@@ -182,6 +189,10 @@ public class DataNodeAuthUtils {
       LOGGER.error("Cannot create password history for {} because {}", 
username, e.getMessage());
       return new TSStatus(TSStatusCode.INTERNAL_SERVER_ERROR.getStatusCode())
           .setMessage("The server is not ready for login, please check the 
server log for details");
+    } finally {
+      if (queryId != -1) {
+        Coordinator.getInstance().cleanupQueryExecution(queryId);
+      }
     }
   }
 }

Reply via email to