You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hugegraph.apache.org by ji...@apache.org on 2026/10/08 15:34:57 UTC

(hugegraph) 07/15: fix(server): close request and graph resources

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

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

commit 1af9589d97bad41e31fd4d68aca791b35616d101
Author: imbajin <ji...@apache.org>
AuthorDate: Thu Oct 8 20:31:40 2026 +0800

    fix(server): close request and graph resources
    
    - release backend leases at request and task completion
    - preserve committed work and roll back unfinished writes
    - cover authentication and graph transaction cleanup
---
 .../apache/hugegraph/auth/HugeGraphAuthProxy.java  |   9 +-
 .../apache/hugegraph/server/ApplicationConfig.java |  16 ++-
 .../hugegraph/backend/tx/GraphTransaction.java     |  15 ++-
 .../hugegraph/auth/BackendLeaseCleanupTest.java    | 147 +++++++++++++++++++++
 .../hugegraph/backend/tx/GraphTransactionTest.java |  18 +++
 .../java/org/apache/hugegraph/core/AuthTest.java   |  91 ++++++++++++-
 .../org/apache/hugegraph/core/CoreTestSuite.java   |   2 +
 .../org/apache/hugegraph/unit/UnitTestSuite.java   |   2 +
 8 files changed, 290 insertions(+), 10 deletions(-)

diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/auth/HugeGraphAuthProxy.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/auth/HugeGraphAuthProxy.java
index 702abfe3b..9d23e8255 100644
--- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/auth/HugeGraphAuthProxy.java
+++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/auth/HugeGraphAuthProxy.java
@@ -40,6 +40,7 @@ import java.util.function.Supplier;
 import javax.security.sasl.AuthenticationException;
 
 import org.apache.commons.configuration2.Configuration;
+import org.apache.hugegraph.HugeFactory;
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.HugeGraphSupplier;
 import org.apache.hugegraph.auth.HugeAuthenticator.RolePerm;
@@ -1303,7 +1304,13 @@ public final class HugeGraphAuthProxy implements HugeGraph {
             try {
                 this.runner.run();
             } finally {
-                resetContext();
+                try {
+                    HugeFactory.closeCurrentThreadTransactions();
+                } catch (Throwable e) {
+                    LOG.error("Failed to close Gremlin worker transactions", e);
+                } finally {
+                    resetContext();
+                }
             }
         }
     }
diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/server/ApplicationConfig.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/server/ApplicationConfig.java
index 9bbed7f48..5cc3e0eb3 100644
--- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/server/ApplicationConfig.java
+++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/server/ApplicationConfig.java
@@ -17,12 +17,14 @@
 
 package org.apache.hugegraph.server;
 
+import org.apache.hugegraph.HugeFactory;
 import org.apache.hugegraph.config.HugeConfig;
 import org.apache.hugegraph.core.GraphManager;
 import org.apache.hugegraph.define.WorkLoad;
 import org.apache.hugegraph.event.EventHub;
 import org.apache.hugegraph.exception.HugeException;
 import org.apache.hugegraph.util.E;
+import org.apache.hugegraph.util.Log;
 import org.apache.hugegraph.version.CoreVersion;
 import org.apache.tinkerpop.gremlin.server.util.MetricManager;
 import org.glassfish.hk2.api.Factory;
@@ -35,6 +37,7 @@ import org.glassfish.jersey.server.monitoring.ApplicationEvent;
 import org.glassfish.jersey.server.monitoring.ApplicationEventListener;
 import org.glassfish.jersey.server.monitoring.RequestEvent;
 import org.glassfish.jersey.server.monitoring.RequestEventListener;
+import org.slf4j.Logger;
 
 import com.codahale.metrics.MetricRegistry;
 import com.codahale.metrics.jersey3.InstrumentedResourceMethodApplicationListener;
@@ -64,6 +67,9 @@ import jakarta.ws.rs.core.Context;
 )
 @ApplicationPath("/")
 public class ApplicationConfig extends ResourceConfig {
+
+    private static final Logger LOG = Log.logger(ApplicationConfig.class);
+
     @Context
     private ServletConfig servletConfig;
 
@@ -195,7 +201,15 @@ public class ApplicationConfig extends ResourceConfig {
 
                 @Override
                 public RequestEventListener onRequest(RequestEvent event) {
-                    return null;
+                    return request -> {
+                        if (request.getType() == RequestEvent.Type.FINISHED) {
+                            try {
+                                HugeFactory.closeCurrentThreadTransactions();
+                            } catch (RuntimeException e) {
+                                LOG.error("Failed to release REST request transactions", e);
+                            }
+                        }
+                    };
                 }
             });
         }
diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/tx/GraphTransaction.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/tx/GraphTransaction.java
index e807b74fb..70487f159 100644
--- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/tx/GraphTransaction.java
+++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/tx/GraphTransaction.java
@@ -370,7 +370,12 @@ public class GraphTransaction extends IndexableTransaction {
                                     Map<Id, HugeEdge> removedEdges) {
         // Remove related edges of each vertex
         for (HugeVertex v : removedVertices.values()) {
-            if (!v.schemaLabel().existsLinkLabel()) {
+            // System edge labels may be absent from schema enumeration after
+            // request cleanup, so always scan auth/system vertex relations.
+            // OLAP shares its ID with a base vertex and must keep its original
+            // path rather than scan that base vertex's ordinary edges.
+            VertexLabel label = v.schemaLabel();
+            if ((!label.system() || label.olap()) && !label.existsLinkLabel()) {
                 continue;
             }
             // Query all edges of the vertex and remove them
@@ -638,7 +643,7 @@ public class GraphTransaction extends IndexableTransaction {
             this.locksTable.lockReads(LockUtil.VERTEX_LABEL_DELETE,
                                       vertex.schemaLabel().id());
             this.locksTable.lockReads(LockUtil.INDEX_LABEL_DELETE,
-                                      vertex.schemaLabel().indexLabels());
+                                      this.indexTx.indexLabelIds(vertex.schemaLabel()));
             // Ensure vertex label still exists from vertex-construct to lock
             this.graph().vertexLabel(vertex.schemaLabel().id());
             /*
@@ -711,7 +716,7 @@ public class GraphTransaction extends IndexableTransaction {
         // Override vertices in local `addedVertices`
         this.addedVertices.remove(vertex.id());
         // Force load vertex to ensure all properties are loaded (refer to #2181)
-        if (!vertex.schemaLabel().indexLabels().isEmpty()) {
+        if (!this.indexTx.indexLabelIds(vertex.schemaLabel()).isEmpty()) {
             vertex.forceLoad();
         }
         // Collect the removed vertex
@@ -905,7 +910,7 @@ public class GraphTransaction extends IndexableTransaction {
             this.locksTable.lockReads(LockUtil.EDGE_LABEL_DELETE,
                                       edge.schemaLabel().id());
             this.locksTable.lockReads(LockUtil.INDEX_LABEL_DELETE,
-                                      edge.schemaLabel().indexLabels());
+                                      this.indexTx.indexLabelIds(edge.schemaLabel()));
             // Ensure edge label still exists from edge-construct to lock
             this.graph().edgeLabel(edge.schemaLabel().id());
             /*
@@ -1844,7 +1849,7 @@ public class GraphTransaction extends IndexableTransaction {
 
         Id pkey = prop.propertyKey().id();
         Set<Id> indexIds = new HashSet<>();
-        for (Id il : schemaLabel.indexLabels()) {
+        for (Id il : this.indexTx.indexLabelIds(schemaLabel)) {
             if (graph().indexLabel(il).indexFields().contains(pkey)) {
                 indexIds.add(il);
             }
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/auth/BackendLeaseCleanupTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/auth/BackendLeaseCleanupTest.java
new file mode 100644
index 000000000..9c8909d19
--- /dev/null
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/auth/BackendLeaseCleanupTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.hugegraph.auth;
+
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.hugegraph.HugeFactory;
+import org.apache.hugegraph.auth.HugeGraphAuthProxy.Context;
+import org.apache.hugegraph.auth.HugeGraphAuthProxy.ContextTask;
+import org.apache.hugegraph.backend.store.rocksdb.RocksDBSessions;
+import org.apache.hugegraph.backend.store.rocksdb.RocksDBStore;
+import org.apache.hugegraph.core.BaseCoreTest;
+import org.apache.hugegraph.testutil.Assert;
+import org.apache.hugegraph.testutil.Whitebox;
+import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.structure.Transaction;
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+
+public class BackendLeaseCleanupTest extends BaseCoreTest {
+
+    @Before
+    public void initSchema() {
+        graph().schema().vertexLabel("lifecycle").useCustomizeNumberId().create();
+        HugeFactory.closeCurrentThreadTransactions();
+    }
+
+    @After
+    public void cleanup() {
+        try {
+            HugeFactory.closeCurrentThreadTransactions();
+        } finally {
+            HugeGraphAuthProxy.resetContext();
+        }
+    }
+
+    @Test
+    public void testFailedTaskRollsBackDespiteCommitOnClose() {
+        AtomicInteger rollbackEvents = new AtomicInteger();
+        HugeGraphAuthProxy.setContext(Context.admin());
+        IllegalStateException failure = new IllegalStateException("task failed after writing");
+        ContextTask task = new ContextTask(() -> {
+            Transaction tx = graph().tx();
+            tx.onClose(Transaction.CLOSE_BEHAVIOR.COMMIT);
+            tx.onReadWrite(Transaction.READ_WRITE_BEHAVIOR.MANUAL);
+            tx.open();
+            tx.addTransactionListener(status -> rollbackEvents.incrementAndGet());
+            graph().addVertex(T.label, "lifecycle", T.id, 1L);
+            throw failure;
+        });
+        HugeGraphAuthProxy.resetContext();
+
+        Assert.assertSame(failure, Assert.assertThrows(IllegalStateException.class, task::run));
+        Assert.assertFalse(graph().tx().isOpen());
+        ThreadLocal<?> backend = Whitebox.getInternalState(graph().tx(), "transactions");
+        Assert.assertNull(backend.get());
+        Assert.assertNull(HugeGraphAuthProxy.getContext());
+        Assert.assertEquals(1, rollbackEvents.get());
+        // Reuse the worker thread: AUTO and default rollback must be restored.
+        Assert.assertEquals(0L, graph().traversal().V().count().next().longValue());
+        graph().addVertex(T.label, "lifecycle", T.id, 2L);
+        graph().tx().close();
+        Assert.assertEquals(0L, graph().traversal().V().count().next().longValue());
+        Assert.assertEquals(1, rollbackEvents.get());
+    }
+
+    @Test
+    public void testSuccessfulTaskKeepsExplicitCommitAndResetsThreadState() {
+        AtomicInteger commitEvents = new AtomicInteger();
+        HugeGraphAuthProxy.setContext(Context.admin());
+        ContextTask task = new ContextTask(() -> {
+            Transaction tx = graph().tx();
+            tx.onClose(Transaction.CLOSE_BEHAVIOR.COMMIT);
+            tx.onReadWrite(Transaction.READ_WRITE_BEHAVIOR.MANUAL);
+            tx.open();
+            tx.addTransactionListener(status -> commitEvents.incrementAndGet());
+            graph().addVertex(T.label, "lifecycle", T.id, 1L);
+            tx.commit();
+        });
+        HugeGraphAuthProxy.resetContext();
+
+        task.run();
+
+        Assert.assertFalse(graph().tx().isOpen());
+        ThreadLocal<?> backend = Whitebox.getInternalState(graph().tx(), "transactions");
+        Assert.assertNull(backend.get());
+        Assert.assertNull(HugeGraphAuthProxy.getContext());
+        Assert.assertEquals(1L, graph().traversal().V().count().next().longValue());
+        graph().addVertex(T.label, "lifecycle", T.id, 2L);
+        graph().tx().close();
+        Assert.assertEquals(1L, graph().traversal().V().count().next().longValue());
+        Assert.assertEquals(1, commitEvents.get());
+    }
+    @Test
+    public void testCommittedRocksDBWorkerReturnsBackendLeases() throws Exception {
+        Assume.assumeTrue("rocksdb".equals(graph().backend()));
+        ExecutorService worker = Executors.newSingleThreadExecutor();
+        try {
+            worker.submit(() -> {
+                try {
+                    graph().addVertex(T.label, "lifecycle", T.id, 1L);
+                    graph().tx().commit();
+                    RocksDBStore store = Whitebox.getInternalState(params().graphTransaction(), "store");
+                    Map<String, RocksDBSessions> databases = Whitebox.getInternalState(store, "dbs");
+                    long threadId = Thread.currentThread().getId();
+                    for (RocksDBSessions sessions : databases.values()) {
+                        Map<Long, ?> leases = Whitebox.getInternalState(sessions, "sessions");
+                        Assert.assertTrue(leases.containsKey(threadId));
+                    }
+                    HugeFactory.closeCurrentThreadTransactions();
+                    for (RocksDBSessions sessions : databases.values()) {
+                        Map<Long, ?> leases = Whitebox.getInternalState(sessions, "sessions");
+                        Assert.assertFalse(leases.containsKey(threadId));
+                    }
+                    // Reuse this exact worker and confirm the committed row survives.
+                    Assert.assertEquals(1L, graph().traversal().V().count().next().longValue());
+                } finally {
+                    HugeFactory.closeCurrentThreadTransactions();
+                }
+            }).get(30, TimeUnit.SECONDS);
+        } finally {
+            worker.shutdownNow();
+        }
+    }
+
+}
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/backend/tx/GraphTransactionTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/backend/tx/GraphTransactionTest.java
index a41ee5ffc..d5ed38aa4 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/backend/tx/GraphTransactionTest.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/backend/tx/GraphTransactionTest.java
@@ -19,6 +19,7 @@ package org.apache.hugegraph.backend.tx;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 import org.apache.hugegraph.HugeFactory;
@@ -49,6 +50,23 @@ import org.mockito.Mockito;
 
 public class GraphTransactionTest {
 
+    @Test
+    public void testOlapDeletionDoesNotScanBaseVertexEdges() {
+        GraphTransaction transaction = Mockito.mock(GraphTransaction.class, Mockito.CALLS_REAL_METHODS);
+        HugeVertex vertex = Mockito.mock(HugeVertex.class);
+        Mockito.when(vertex.schemaLabel()).thenReturn(VertexLabel.OLAP_VL);
+        Id id = IdGenerator.of(1L);
+        Mockito.when(vertex.id()).thenReturn(id);
+
+        // Preserve OLAP's original rejection before any ordinary edge scan.
+        IllegalStateException error = Assert.assertThrows(IllegalStateException.class, () -> {
+            transaction.prepareDeletions(Collections.singletonMap(id, vertex), Collections.emptyMap());
+        });
+        Assert.assertTrue(error.getMessage().startsWith("Graph is null of schema"));
+        Mockito.verify(transaction, Mockito.never()).queryEdgesFromBackend(Mockito.any());
+        Mockito.verify(transaction, Mockito.never()).doRemove(Mockito.any());
+    }
+
     @Test
     public void testBatchDecisionsRemainFixedAfterOriginChanges() {
         ConditionQuery root = new ConditionQuery(HugeType.VERTEX);
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/AuthTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/AuthTest.java
index 12afccd1f..66680e70c 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/AuthTest.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/AuthTest.java
@@ -27,7 +27,7 @@ import java.util.Objects;
 import javax.security.sasl.AuthenticationException;
 
 import org.apache.commons.collections.CollectionUtils;
-import org.apache.hugegraph.exception.HugeException;
+import org.apache.hugegraph.HugeFactory;
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.HugeGraphParams;
 import org.apache.hugegraph.auth.AuthConstant;
@@ -45,6 +45,8 @@ import org.apache.hugegraph.auth.StandardAuthManager;
 import org.apache.hugegraph.auth.TokenGenerator;
 import org.apache.hugegraph.auth.UserWithRole;
 import org.apache.hugegraph.backend.cache.Cache;
+import org.apache.hugegraph.exception.HugeException;
+import org.apache.hugegraph.exception.NotFoundException;
 import org.apache.hugegraph.id.Id;
 import org.apache.hugegraph.id.IdGenerator;
 import org.apache.hugegraph.struct.schema.VertexLabel;
@@ -141,8 +143,7 @@ public class AuthTest extends BaseCoreTest {
         }
 
         //FIXME: support project in hstore
-        boolean isHstore = Objects.equals("hstore", System.getProperty("backend")) ||
-                           (System.getProperty("backend") == null);
+        boolean isHstore = Objects.equals("hstore", graph.backend());
 
         if (!isHstore) {
             for (HugeProject project : authManager.listAllProject(-1)) {
@@ -1778,6 +1779,90 @@ public class AuthTest extends BaseCoreTest {
         });
     }
 
+    @Test
+    public void testProjectCleanupRepeatsWithoutBackendSystemProperty() {
+        Assume.assumeTrue("skip this test for hstore", !Objects.equals("hstore", graph().backend()));
+        String previous = System.getProperty("backend");
+        try {
+            System.clearProperty("backend");
+            for (int attempt = 0; attempt < 2; attempt++) {
+                this.testDeleteProjectAfterRequestCleanup();
+                this.clearAll();
+                Assert.assertEquals(0, graph().authManager().listAllProject(-1).size());
+            }
+        } finally {
+            if (previous == null) {
+                System.clearProperty("backend");
+            } else {
+                System.setProperty("backend", previous);
+            }
+        }
+    }
+
+    @Test
+    public void testDeleteProjectAfterRequestCleanup() {
+        Assume.assumeTrue("skip this test for hstore", !Objects.equals("hstore", graph().backend()));
+        AuthManager authManager = graph().authManager();
+        HugeProject project = makeProject("cleanup_project", "");
+        Id projectId = authManager.createProject(project);
+        HugeProject saved = authManager.getProject(projectId);
+        Id retainedId = authManager.createProject(makeProject("retained_project", ""));
+        HugeProject retained = authManager.getProject(retainedId);
+        Assert.assertEquals(3, authManager.listAccessByTarget(saved.targetId(), -1).size());
+        Id userId = authManager.createUser(makeUser("membership_user", "pass"));
+        authManager.createBelong(makeBelong(userId, saved.adminGroupId()));
+        Id retainedBelongId = authManager.createBelong(makeBelong(userId, retained.adminGroupId()));
+
+        // Match FINISHED cleanup between REST creation and deletion requests.
+        HugeFactory.closeCurrentThreadTransactions();
+        authManager.deleteProject(projectId);
+        HugeFactory.closeCurrentThreadTransactions();
+
+        Assert.assertEquals(0, authManager.listAccessByGroup(saved.adminGroupId(), -1).size());
+        Assert.assertEquals(0, authManager.listAccessByGroup(saved.opGroupId(), -1).size());
+        Assert.assertEquals(0, authManager.listAccessByTarget(saved.targetId(), -1).size());
+        Assert.assertEquals(3, authManager.listAccessByTarget(retained.targetId(), -1).size());
+        Assert.assertEquals(0, authManager.listBelongByGroup(saved.adminGroupId(), -1).size());
+        Assert.assertEquals(1, authManager.listBelongByUser(userId, -1).size());
+        Assert.assertEquals(retainedBelongId, authManager.getBelong(retainedBelongId).id());
+        Assert.assertEquals(userId, authManager.getUser(userId).id());
+        Assert.assertEquals(projectId, authManager.createProject(makeProject("cleanup_project", "")));
+        Assert.assertEquals(0, authManager.listBelongByGroup(saved.adminGroupId(), -1).size());
+    }
+
+    @Test
+    public void testDeleteUserWithRelationsAfterRequestCleanup() {
+        Assume.assumeTrue("skip this test for hstore", !Objects.equals("hstore", graph().backend()));
+        AuthManager authManager = graph().authManager();
+        Id deletedUser = authManager.createUser(makeUser("deleted_member", "pass"));
+        Id retainedUser = authManager.createUser(makeUser("retained_member", "pass"));
+        Id group = authManager.createGroup(makeGroup("shared_group"));
+        authManager.createBelong(makeBelong(deletedUser, group));
+        Id retainedBelong = authManager.createBelong(makeBelong(retainedUser, group));
+
+        HugeFactory.closeCurrentThreadTransactions();
+        authManager.deleteUser(deletedUser);
+        HugeFactory.closeCurrentThreadTransactions();
+
+        Assert.assertThrows(NotFoundException.class, () -> authManager.getUser(deletedUser));
+        Assert.assertEquals(0, authManager.listBelongByUser(deletedUser, -1).size());
+        Assert.assertEquals(1, authManager.listBelongByGroup(group, -1).size());
+        Assert.assertEquals(retainedBelong, authManager.getBelong(retainedBelong).id());
+        Assert.assertEquals(retainedUser, authManager.getUser(retainedUser).id());
+    }
+
+    @Test
+    public void testDeleteUserWithoutRelationsAfterRequestCleanup() {
+        Assume.assumeTrue("skip this test for hstore", !Objects.equals("hstore", graph().backend()));
+        AuthManager authManager = graph().authManager();
+        Id userId = authManager.createUser(makeUser("isolated_user", "pass"));
+        HugeFactory.closeCurrentThreadTransactions();
+        authManager.deleteUser(userId);
+        HugeFactory.closeCurrentThreadTransactions();
+        Assert.assertThrows(NotFoundException.class, () -> authManager.getUser(userId));
+        Assert.assertEquals(0, authManager.listBelongByUser(userId, -1).size());
+    }
+
     @Test
     public void testUpdateProject() {
         Assume.assumeTrue("skip this test for hstore",
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/CoreTestSuite.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/CoreTestSuite.java
index 5d33f9281..9b45f40a6 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/CoreTestSuite.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/CoreTestSuite.java
@@ -18,6 +18,7 @@
 package org.apache.hugegraph.core;
 
 import org.apache.hugegraph.HugeGraph;
+import org.apache.hugegraph.auth.BackendLeaseCleanupTest;
 import org.apache.hugegraph.constant.ServiceConstant;
 import org.apache.hugegraph.dist.RegisterUtil;
 import org.apache.hugegraph.masterelection.GlobalMasterInfo;
@@ -34,6 +35,7 @@ import org.slf4j.Logger;
 
 @RunWith(Suite.class)
 @Suite.SuiteClasses({
+        BackendLeaseCleanupTest.class,
         PropertyKeyCoreTest.class,
         VertexLabelCoreTest.class,
         EdgeLabelCoreTest.class,
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java
index 617440b66..0c9a0af01 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java
@@ -44,6 +44,7 @@ import org.apache.hugegraph.unit.cache.CacheTest;
 import org.apache.hugegraph.unit.cache.CachedGraphTransactionTest;
 import org.apache.hugegraph.unit.cache.CachedSchemaTransactionTest;
 import org.apache.hugegraph.unit.cache.RamTableTest;
+import org.apache.hugegraph.unit.cache.RequestCacheLifetimeTest;
 import org.apache.hugegraph.unit.cmd.InitStoreConfigTest;
 import org.apache.hugegraph.unit.config.GremlinConfigCompatibilityTest;
 import org.apache.hugegraph.unit.config.GraphBinarySchemaCompatibilityTest;
@@ -148,6 +149,7 @@ import org.junit.runners.Suite;
         MetaManagerSchemaCacheClearEventTest.class,
         EtcdMetaDriverTest.class,
         CachedGraphTransactionTest.class,
+        RequestCacheLifetimeTest.class,
         CacheManagerTest.class,
         RamTableTest.class,