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

jmclean pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new 320637a3e4 [#9139] fix(lock): Prevent NPE when timestamp is missing in 
TreeLock.unlock (#9146)
320637a3e4 is described below

commit 320637a3e4976691a77cc801decc15706de442e9
Author: Kwon Taeheon <[email protected]>
AuthorDate: Tue Nov 18 06:49:28 2025 +0900

    [#9139] fix(lock): Prevent NPE when timestamp is missing in TreeLock.unlock 
(#9146)
    
    ### What changes were proposed in this pull request?
    - Updated TreeLockNode.removeHoldingThreadTimestamp to return a nullable
    Long instead of long.
    - Modified TreeLock.unlock() to handle null timestamps safely before
    logging.
    - Ensures unlocking works correctly even when the timestamp map is
    cleared.
    
    ### Why are the changes needed?
    
    - holdingThreadTimestamp is a Map<ThreadIdentifier, Long>, and
    Map.remove() returns null when no timestamp exists.
    - The previous implementation returned long, causing a null → primitive
    unboxing and leading to a NullPointerException.
    - Unlocking a node should not depend on the presence of a timestamp
    value, so the logic must tolerate null entries.
    
    Fix: #9139
    
    ### Does this PR introduce _any_ user-facing change?
    
    - no.
    - This change only improves internal lock handling and exception safety.
    
    ### How was this patch tested?
    - Verified using the unit test included in the issue
    (testUnlockWithMissingHoldingTimestamp).
    - Confirmed all existing tests continue to pass.
---
 core/src/main/java/org/apache/gravitino/lock/TreeLock.java |  7 +++++--
 .../main/java/org/apache/gravitino/lock/TreeLockNode.java  |  2 +-
 .../test/java/org/apache/gravitino/lock/TestTreeLock.java  | 14 ++++++++++++++
 3 files changed, 20 insertions(+), 3 deletions(-)

diff --git a/core/src/main/java/org/apache/gravitino/lock/TreeLock.java 
b/core/src/main/java/org/apache/gravitino/lock/TreeLock.java
index 9ad2565dd3..a9c2d7013a 100644
--- a/core/src/main/java/org/apache/gravitino/lock/TreeLock.java
+++ b/core/src/main/java/org/apache/gravitino/lock/TreeLock.java
@@ -153,15 +153,18 @@ public class TreeLock {
       LockType type = pair.getRight();
       current.unlock(type);
 
-      long holdStartTime = 
current.removeHoldingThreadTimestamp(Thread.currentThread(), identifier);
+      Long holdStartTime = 
current.removeHoldingThreadTimestamp(Thread.currentThread(), identifier);
       if (LOG.isTraceEnabled()) {
+        long duration =
+            (holdStartTime == null) ? -1L : (System.currentTimeMillis() - 
holdStartTime);
+
         LOG.trace(
             "Node {} has been unlock with '{}' lock, hold by {} with ident 
'{}' for {} ms",
             this,
             type,
             Thread.currentThread(),
             identifier,
-            System.currentTimeMillis() - holdStartTime);
+            duration);
       }
     }
 
diff --git a/core/src/main/java/org/apache/gravitino/lock/TreeLockNode.java 
b/core/src/main/java/org/apache/gravitino/lock/TreeLockNode.java
index f9434eb7cc..599c6e2b90 100644
--- a/core/src/main/java/org/apache/gravitino/lock/TreeLockNode.java
+++ b/core/src/main/java/org/apache/gravitino/lock/TreeLockNode.java
@@ -117,7 +117,7 @@ public class TreeLockNode {
     holdingThreadTimestamp.put(ThreadIdentifier.of(currentThread, identifier), 
timestamp);
   }
 
-  long removeHoldingThreadTimestamp(Thread currentThread, NameIdentifier 
identifier) {
+  Long removeHoldingThreadTimestamp(Thread currentThread, NameIdentifier 
identifier) {
     return holdingThreadTimestamp.remove(ThreadIdentifier.of(currentThread, 
identifier));
   }
 
diff --git a/core/src/test/java/org/apache/gravitino/lock/TestTreeLock.java 
b/core/src/test/java/org/apache/gravitino/lock/TestTreeLock.java
index 2ff7c4e8e4..c48d5cf76a 100644
--- a/core/src/test/java/org/apache/gravitino/lock/TestTreeLock.java
+++ b/core/src/test/java/org/apache/gravitino/lock/TestTreeLock.java
@@ -26,6 +26,7 @@ import static org.mockito.Mockito.doThrow;
 
 import java.util.Arrays;
 import java.util.List;
+import org.apache.gravitino.NameIdentifier;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
@@ -109,4 +110,17 @@ class TestTreeLock {
     lock.unlock();
     assertThrows(IllegalStateException.class, () -> lock.unlock());
   }
+
+  @Test
+  void testUnlockWithMissingHoldingTimestamp() {
+    TreeLockNode rootNode = new TreeLockNode("root");
+    TreeLockNode childNode = rootNode.getOrCreateChild("child").getLeft();
+    TreeLock treeLock =
+        new TreeLock(Arrays.asList(rootNode, childNode), 
NameIdentifier.of("root", "child"));
+
+    treeLock.lock(LockType.WRITE);
+    childNode.getHoldingThreadTimestamp().clear();
+
+    assertDoesNotThrow(treeLock::unlock);
+  }
 }

Reply via email to