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

keith-turner pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/accumulo.git


The following commit(s) were added to refs/heads/main by this push:
     new 224104de96 Fixes fate ranged lock encoding (#6350)
224104de96 is described below

commit 224104de966bff8b5e6f29a5cf8568ca601588dc
Author: Keith Turner <[email protected]>
AuthorDate: Thu Apr 30 10:53:12 2026 -0700

    Fixes fate ranged lock encoding (#6350)
    
    Encoding the fate ranged lock to store in zookeeper was using standard
    Base64 which could include a '/'. When this happened the code would go
    into an infinite loop.  Changed to using base64 URL encoding and added a
    few checks to make the fate lock encoding/decoding more strict.
---
 .../accumulo/core/fate/zookeeper/FateLock.java     | 27 ++++++++++++++--------
 .../accumulo/core/fate/zookeeper/FateLockTest.java |  2 +-
 2 files changed, 19 insertions(+), 10 deletions(-)

diff --git 
a/core/src/main/java/org/apache/accumulo/core/fate/zookeeper/FateLock.java 
b/core/src/main/java/org/apache/accumulo/core/fate/zookeeper/FateLock.java
index 5e8c541818..fcd02f3bd4 100644
--- a/core/src/main/java/org/apache/accumulo/core/fate/zookeeper/FateLock.java
+++ b/core/src/main/java/org/apache/accumulo/core/fate/zookeeper/FateLock.java
@@ -74,7 +74,7 @@ public class FateLock implements QueueLock {
 
   public final static class FateLockEntry {
 
-    private static final String DELIMITER = "_";
+    private static final String DELIMITER = "~";
 
     final LockType lockType;
     final FateId fateId;
@@ -87,7 +87,8 @@ public class FateLock implements QueueLock {
     }
 
     private FateLockEntry(String entry) {
-      var fields = entry.split(DELIMITER, 4);
+      var fields = entry.split(DELIMITER);
+      Preconditions.checkArgument(fields.length == 4, entry);
       this.lockType = LockType.valueOf(fields[0]);
       this.fateId = FateId.from(fields[1]);
       this.range = LockRange.of(decodeRow(fields[2]), decodeRow(fields[3]));
@@ -109,13 +110,13 @@ public class FateLock implements QueueLock {
       if (row == null) {
         return "N";
       } else {
-        return "P" + 
Base64.getEncoder().encodeToString(TextUtil.getBytes(row));
+        return "P" + 
Base64.getUrlEncoder().encodeToString(TextUtil.getBytes(row));
       }
     }
 
     private Text decodeRow(String enc) {
       if (enc.charAt(0) == 'P') {
-        return new Text(Base64.getDecoder().decode(enc.substring(1)));
+        return new Text(Base64.getUrlDecoder().decode(enc.substring(1)));
       } else if (enc.charAt(0) == 'N') {
         return null;
       } else {
@@ -123,9 +124,15 @@ public class FateLock implements QueueLock {
       }
     }
 
+    private String checkForDelimiter(String s) {
+      Preconditions.checkArgument(!s.contains(DELIMITER), s);
+      return s;
+    }
+
     public String serialize() {
-      return lockType.name() + DELIMITER + fateId.canonical() + DELIMITER
-          + encodeRow(range.getStartRow()) + DELIMITER + 
encodeRow(range.getEndRow());
+      return checkForDelimiter(lockType.name()) + DELIMITER + 
checkForDelimiter(fateId.canonical())
+          + DELIMITER + checkForDelimiter(encodeRow(range.getStartRow())) + 
DELIMITER
+          + checkForDelimiter(encodeRow(range.getEndRow()));
     }
 
     public static FateLockEntry from(LockType lockType, FateId fateId, 
LockRange range) {
@@ -183,19 +190,21 @@ public class FateLock implements QueueLock {
   public long addEntry(FateLockEntry entry) {
 
     String dataString = entry.serialize();
-    Preconditions.checkState(!dataString.contains("#"));
+    Preconditions.checkState(!dataString.contains("#") && 
!dataString.contains("/"), dataString);
 
     String newPath;
     try {
       while (true) {
         try {
-          newPath =
-              zoo.putPersistentSequential(path + "/" + PREFIX + dataString + 
"#", new byte[0]);
+          var prefix = path + "/" + PREFIX + dataString + "#";
+          log.trace("Attempting to create {} in zookeeper", prefix);
+          newPath = zoo.putPersistentSequential(prefix, new byte[0]);
           String[] parts = newPath.split("/");
           String last = parts[parts.length - 1];
           return new NodeName(last).sequence;
         } catch (NoNodeException nne) {
           // the parent does not exist so try to create it
+          log.trace("Attempting to create parent {}", path.toString(), nne);
           zoo.putPersistentData(path.toString(), new byte[] {}, 
NodeExistsPolicy.SKIP);
         }
       }
diff --git 
a/core/src/test/java/org/apache/accumulo/core/fate/zookeeper/FateLockTest.java 
b/core/src/test/java/org/apache/accumulo/core/fate/zookeeper/FateLockTest.java
index 3ae75e33af..eea6f610ac 100644
--- 
a/core/src/test/java/org/apache/accumulo/core/fate/zookeeper/FateLockTest.java
+++ 
b/core/src/test/java/org/apache/accumulo/core/fate/zookeeper/FateLockTest.java
@@ -34,7 +34,7 @@ public class FateLockTest {
   public void testParsing() {
     var fateId = FateId.from(FateInstanceType.USER, UUID.randomUUID());
     // ZooKeeper docs state that sequence numbers are formatted using %010d
-    String lockData = "WRITE_" + fateId.canonical() + "_N_N";
+    String lockData = "WRITE~" + fateId.canonical() + "~N~N";
     var lockNode =
         new FateLock.NodeName(FateLock.PREFIX + lockData + "#" + 
String.format("%010d", 40));
     assertEquals(40, lockNode.sequence);

Reply via email to