Copilot commented on code in PR #3695:
URL: https://github.com/apache/celeborn/pull/3695#discussion_r3263511187


##########
worker/src/main/java/org/apache/celeborn/service/deploy/worker/shuffledb/RocksDB.java:
##########
@@ -30,49 +36,154 @@
  * <p>Note: code copied from Apache Spark.
  */
 public class RocksDB extends DB {
-  private final org.rocksdb.RocksDB db;
+  private static final Logger logger = LoggerFactory.getLogger(RocksDB.class);
+
+  private volatile org.rocksdb.RocksDB db;
   private final WriteOptions SYNC_WRITE_OPTIONS = new 
WriteOptions().setSync(true);
+  private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
+  private final AtomicLong dbGeneration = new AtomicLong(0);
+  private final File dbFile;
+  private final StoreVersion version;
 
-  public RocksDB(org.rocksdb.RocksDB db, AbstractSource source, DBBackend 
dbBackend) {
+  public RocksDB(
+      org.rocksdb.RocksDB db,
+      AbstractSource source,
+      DBBackend dbBackend,
+      File dbFile,
+      StoreVersion version) {
     super(source, dbBackend);
     this.db = db;
+    this.dbFile = dbFile;
+    this.version = version;
+  }
+
+  private void recreateDBInstance(long failedGeneration) {
+    rwLock.writeLock().lock();
+    try {
+      if (dbGeneration.get() != failedGeneration) {
+        logger.info(
+            "RocksDB instance already recovered by another thread (generation 
{} -> {})",
+            failedGeneration,
+            dbGeneration.get());
+        return;
+      }
+
+      try {
+        if (db != null) {
+          db.close();
+        }
+      } catch (Exception e) {
+        logger.warn("Failed to close RocksDB instance", e);
+      }
+      db = RocksDBProvider.initRockDB(dbFile, version);
+      dbGeneration.incrementAndGet();
+    } catch (IOException e) {
+      logger.error(
+          "Failed to recreate RocksDB instance at {}. "
+              + "Database is unavailable, all subsequent operations will 
fail.",
+          dbFile,
+          e);
+    } finally {
+      rwLock.writeLock().unlock();
+    }
   }

Review Comment:
   Re-creating the DB on any `RocksDBException` is dangerous because 
`RocksDBProvider.initRockDB` interprets an open failure as corruption and 
deletes the entire RocksDB directory (see `RocksDBProvider.java` lines 84–100) 
before creating an empty one. If a put/get/delete throws for any reason 
(transient I/O error, lock contention, etc.) and the subsequent reopen happens 
to fail, all worker metadata will be silently wiped instead of preserved for 
manual recovery. Auto-recovery should at minimum avoid the wipe-and-recreate 
branch, or be restricted to specific RocksDB status codes (e.g., IOError) that 
are known to be recoverable by reopen.



##########
worker/src/main/java/org/apache/celeborn/service/deploy/worker/shuffledb/RocksDB.java:
##########
@@ -30,49 +36,154 @@
  * <p>Note: code copied from Apache Spark.
  */
 public class RocksDB extends DB {
-  private final org.rocksdb.RocksDB db;
+  private static final Logger logger = LoggerFactory.getLogger(RocksDB.class);
+
+  private volatile org.rocksdb.RocksDB db;
   private final WriteOptions SYNC_WRITE_OPTIONS = new 
WriteOptions().setSync(true);
+  private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
+  private final AtomicLong dbGeneration = new AtomicLong(0);
+  private final File dbFile;
+  private final StoreVersion version;
 
-  public RocksDB(org.rocksdb.RocksDB db, AbstractSource source, DBBackend 
dbBackend) {
+  public RocksDB(
+      org.rocksdb.RocksDB db,
+      AbstractSource source,
+      DBBackend dbBackend,
+      File dbFile,
+      StoreVersion version) {
     super(source, dbBackend);
     this.db = db;
+    this.dbFile = dbFile;
+    this.version = version;
+  }
+
+  private void recreateDBInstance(long failedGeneration) {
+    rwLock.writeLock().lock();
+    try {
+      if (dbGeneration.get() != failedGeneration) {
+        logger.info(
+            "RocksDB instance already recovered by another thread (generation 
{} -> {})",
+            failedGeneration,
+            dbGeneration.get());
+        return;
+      }
+
+      try {
+        if (db != null) {
+          db.close();
+        }
+      } catch (Exception e) {
+        logger.warn("Failed to close RocksDB instance", e);
+      }
+      db = RocksDBProvider.initRockDB(dbFile, version);
+      dbGeneration.incrementAndGet();
+    } catch (IOException e) {
+      logger.error(
+          "Failed to recreate RocksDB instance at {}. "
+              + "Database is unavailable, all subsequent operations will 
fail.",
+          dbFile,
+          e);
+    } finally {
+      rwLock.writeLock().unlock();
+    }

Review Comment:
   If `RocksDBProvider.initRockDB` throws `IOException`, the error is logged 
but `dbGeneration` is not incremented and the old (now-closed) `db` reference 
is retained. Every subsequent operation will read the same generation, fail 
against the closed DB, and re-enter `recreateDBInstance` — turning each request 
into another full reopen attempt under the write lock. This is both a tight 
retry loop and a serialization point under failure. Consider incrementing the 
generation regardless of success, or setting a permanent failure state that 
fast-fails operations.



##########
worker/src/main/java/org/apache/celeborn/service/deploy/worker/shuffledb/RocksDB.java:
##########
@@ -30,49 +36,154 @@
  * <p>Note: code copied from Apache Spark.
  */
 public class RocksDB extends DB {
-  private final org.rocksdb.RocksDB db;
+  private static final Logger logger = LoggerFactory.getLogger(RocksDB.class);
+
+  private volatile org.rocksdb.RocksDB db;
   private final WriteOptions SYNC_WRITE_OPTIONS = new 
WriteOptions().setSync(true);
+  private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
+  private final AtomicLong dbGeneration = new AtomicLong(0);
+  private final File dbFile;
+  private final StoreVersion version;
 
-  public RocksDB(org.rocksdb.RocksDB db, AbstractSource source, DBBackend 
dbBackend) {
+  public RocksDB(
+      org.rocksdb.RocksDB db,
+      AbstractSource source,
+      DBBackend dbBackend,
+      File dbFile,
+      StoreVersion version) {
     super(source, dbBackend);
     this.db = db;
+    this.dbFile = dbFile;
+    this.version = version;
+  }
+
+  private void recreateDBInstance(long failedGeneration) {
+    rwLock.writeLock().lock();
+    try {
+      if (dbGeneration.get() != failedGeneration) {
+        logger.info(
+            "RocksDB instance already recovered by another thread (generation 
{} -> {})",
+            failedGeneration,
+            dbGeneration.get());
+        return;
+      }
+
+      try {
+        if (db != null) {
+          db.close();
+        }
+      } catch (Exception e) {
+        logger.warn("Failed to close RocksDB instance", e);
+      }
+      db = RocksDBProvider.initRockDB(dbFile, version);
+      dbGeneration.incrementAndGet();
+    } catch (IOException e) {
+      logger.error(
+          "Failed to recreate RocksDB instance at {}. "
+              + "Database is unavailable, all subsequent operations will 
fail.",
+          dbFile,
+          e);
+    } finally {
+      rwLock.writeLock().unlock();
+    }
   }
 
   @Override
   protected void putInternal(byte[] key, byte[] value) throws RocksDBException 
{
-    db.put(key, value);
+    long generation = 0;
+    try {
+      rwLock.readLock().lock();
+      generation = dbGeneration.get();
+      try {
+        db.put(key, value);
+      } finally {
+        rwLock.readLock().unlock();
+      }
+    } catch (RocksDBException e) {
+      recreateDBInstance(generation);
+      throw e;
+    }

Review Comment:
   After `recreateDBInstance` successfully reopens the DB, the original 
`RocksDBException` is still rethrown to the caller. So even though the PR's 
stated goal is that "metadata operations can recover automatically and continue 
functioning", the failing operation is not retried and surfaces as a failure to 
callers, who must implement their own retry logic. Either document this 
contract explicitly or transparently retry the operation once on the new 
generation.



##########
worker/src/main/java/org/apache/celeborn/service/deploy/worker/shuffledb/RocksDB.java:
##########
@@ -30,49 +36,154 @@
  * <p>Note: code copied from Apache Spark.
  */
 public class RocksDB extends DB {
-  private final org.rocksdb.RocksDB db;
+  private static final Logger logger = LoggerFactory.getLogger(RocksDB.class);
+
+  private volatile org.rocksdb.RocksDB db;
   private final WriteOptions SYNC_WRITE_OPTIONS = new 
WriteOptions().setSync(true);
+  private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
+  private final AtomicLong dbGeneration = new AtomicLong(0);
+  private final File dbFile;
+  private final StoreVersion version;
 
-  public RocksDB(org.rocksdb.RocksDB db, AbstractSource source, DBBackend 
dbBackend) {
+  public RocksDB(
+      org.rocksdb.RocksDB db,
+      AbstractSource source,
+      DBBackend dbBackend,
+      File dbFile,
+      StoreVersion version) {
     super(source, dbBackend);
     this.db = db;
+    this.dbFile = dbFile;
+    this.version = version;
+  }
+
+  private void recreateDBInstance(long failedGeneration) {
+    rwLock.writeLock().lock();
+    try {
+      if (dbGeneration.get() != failedGeneration) {
+        logger.info(
+            "RocksDB instance already recovered by another thread (generation 
{} -> {})",
+            failedGeneration,
+            dbGeneration.get());
+        return;
+      }
+
+      try {
+        if (db != null) {
+          db.close();
+        }
+      } catch (Exception e) {
+        logger.warn("Failed to close RocksDB instance", e);
+      }
+      db = RocksDBProvider.initRockDB(dbFile, version);
+      dbGeneration.incrementAndGet();
+    } catch (IOException e) {
+      logger.error(
+          "Failed to recreate RocksDB instance at {}. "
+              + "Database is unavailable, all subsequent operations will 
fail.",
+          dbFile,
+          e);
+    } finally {
+      rwLock.writeLock().unlock();
+    }
   }
 
   @Override
   protected void putInternal(byte[] key, byte[] value) throws RocksDBException 
{
-    db.put(key, value);
+    long generation = 0;
+    try {
+      rwLock.readLock().lock();
+      generation = dbGeneration.get();
+      try {
+        db.put(key, value);
+      } finally {
+        rwLock.readLock().unlock();
+      }
+    } catch (RocksDBException e) {
+      recreateDBInstance(generation);
+      throw e;
+    }
   }
 
   @Override
   protected void putInternal(byte[] key, byte[] value, boolean sync) throws 
RocksDBException {
-    if (sync) {
-      db.put(SYNC_WRITE_OPTIONS, key, value);
-    } else {
-      db.put(key, value);
+    long generation = 0;
+    try {
+      rwLock.readLock().lock();
+      generation = dbGeneration.get();
+      try {
+        if (sync) {
+          db.put(SYNC_WRITE_OPTIONS, key, value);
+        } else {
+          db.put(key, value);
+        }
+      } finally {
+        rwLock.readLock().unlock();
+      }
+    } catch (RocksDBException e) {
+      recreateDBInstance(generation);
+      throw e;
     }
   }
 
   @Override
   protected byte[] getInternal(byte[] key) throws RocksDBException {
-    return db.get(key);
+    long generation = 0;
+    try {
+      rwLock.readLock().lock();
+      generation = dbGeneration.get();
+      try {
+        return db.get(key);
+      } finally {
+        rwLock.readLock().unlock();
+      }
+    } catch (RocksDBException e) {
+      recreateDBInstance(generation);
+      throw e;
+    }
   }
 
   @Override
   protected void deleteInternal(byte[] key) throws RocksDBException {
-    db.delete(key);
+    long generation = 0;
+    try {
+      rwLock.readLock().lock();
+      generation = dbGeneration.get();
+      try {
+        db.delete(key);
+      } finally {
+        rwLock.readLock().unlock();
+      }
+    } catch (RocksDBException e) {
+      recreateDBInstance(generation);
+      throw e;
+    }
   }
 
   @Override
   protected DBIterator newIterator(MetadataMetrics metrics) {
-    return new RocksDBIterator(db.newIterator(), metrics);
+    long generation = 0;
+    try {
+      rwLock.readLock().lock();
+      generation = dbGeneration.get();
+      try {
+        return new RocksDBIterator(db.newIterator(), metrics);
+      } finally {
+        rwLock.readLock().unlock();
+      }
+    } catch (RuntimeException e) {
+      recreateDBInstance(generation);
+      throw e;
+    }
   }

Review Comment:
   `newIterator` returns a `RocksDBIterator` that holds a reference to the 
underlying native iterator of the current `db`. After this method returns, the 
read lock is released and the iterator is then used by callers without any lock 
protection. If `recreateDBInstance` runs concurrently and closes the previous 
`db`, in-flight iterators reference freed native handles, leading to JVM 
crashes or undefined behavior. Recovery must either invalidate outstanding 
iterators or be deferred while iterators are alive.



##########
worker/src/main/java/org/apache/celeborn/service/deploy/worker/shuffledb/RocksDB.java:
##########
@@ -30,49 +36,154 @@
  * <p>Note: code copied from Apache Spark.
  */
 public class RocksDB extends DB {
-  private final org.rocksdb.RocksDB db;
+  private static final Logger logger = LoggerFactory.getLogger(RocksDB.class);
+
+  private volatile org.rocksdb.RocksDB db;
   private final WriteOptions SYNC_WRITE_OPTIONS = new 
WriteOptions().setSync(true);
+  private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
+  private final AtomicLong dbGeneration = new AtomicLong(0);
+  private final File dbFile;
+  private final StoreVersion version;
 
-  public RocksDB(org.rocksdb.RocksDB db, AbstractSource source, DBBackend 
dbBackend) {
+  public RocksDB(
+      org.rocksdb.RocksDB db,
+      AbstractSource source,
+      DBBackend dbBackend,
+      File dbFile,
+      StoreVersion version) {
     super(source, dbBackend);
     this.db = db;
+    this.dbFile = dbFile;
+    this.version = version;
+  }
+
+  private void recreateDBInstance(long failedGeneration) {
+    rwLock.writeLock().lock();
+    try {
+      if (dbGeneration.get() != failedGeneration) {
+        logger.info(
+            "RocksDB instance already recovered by another thread (generation 
{} -> {})",
+            failedGeneration,
+            dbGeneration.get());
+        return;
+      }
+
+      try {
+        if (db != null) {
+          db.close();
+        }
+      } catch (Exception e) {
+        logger.warn("Failed to close RocksDB instance", e);
+      }
+      db = RocksDBProvider.initRockDB(dbFile, version);
+      dbGeneration.incrementAndGet();
+    } catch (IOException e) {
+      logger.error(
+          "Failed to recreate RocksDB instance at {}. "
+              + "Database is unavailable, all subsequent operations will 
fail.",
+          dbFile,
+          e);
+    } finally {
+      rwLock.writeLock().unlock();
+    }
   }
 
   @Override
   protected void putInternal(byte[] key, byte[] value) throws RocksDBException 
{
-    db.put(key, value);
+    long generation = 0;
+    try {
+      rwLock.readLock().lock();
+      generation = dbGeneration.get();
+      try {
+        db.put(key, value);
+      } finally {
+        rwLock.readLock().unlock();
+      }
+    } catch (RocksDBException e) {
+      recreateDBInstance(generation);
+      throw e;
+    }
   }
 
   @Override
   protected void putInternal(byte[] key, byte[] value, boolean sync) throws 
RocksDBException {
-    if (sync) {
-      db.put(SYNC_WRITE_OPTIONS, key, value);
-    } else {
-      db.put(key, value);
+    long generation = 0;
+    try {
+      rwLock.readLock().lock();
+      generation = dbGeneration.get();
+      try {
+        if (sync) {
+          db.put(SYNC_WRITE_OPTIONS, key, value);
+        } else {
+          db.put(key, value);
+        }
+      } finally {
+        rwLock.readLock().unlock();
+      }
+    } catch (RocksDBException e) {
+      recreateDBInstance(generation);
+      throw e;
     }
   }
 
   @Override
   protected byte[] getInternal(byte[] key) throws RocksDBException {
-    return db.get(key);
+    long generation = 0;
+    try {
+      rwLock.readLock().lock();
+      generation = dbGeneration.get();
+      try {
+        return db.get(key);
+      } finally {
+        rwLock.readLock().unlock();
+      }
+    } catch (RocksDBException e) {
+      recreateDBInstance(generation);
+      throw e;
+    }
   }
 
   @Override
   protected void deleteInternal(byte[] key) throws RocksDBException {
-    db.delete(key);
+    long generation = 0;
+    try {
+      rwLock.readLock().lock();
+      generation = dbGeneration.get();
+      try {
+        db.delete(key);
+      } finally {
+        rwLock.readLock().unlock();
+      }
+    } catch (RocksDBException e) {
+      recreateDBInstance(generation);
+      throw e;
+    }
   }
 
   @Override
   protected DBIterator newIterator(MetadataMetrics metrics) {
-    return new RocksDBIterator(db.newIterator(), metrics);
+    long generation = 0;
+    try {
+      rwLock.readLock().lock();
+      generation = dbGeneration.get();
+      try {
+        return new RocksDBIterator(db.newIterator(), metrics);
+      } finally {
+        rwLock.readLock().unlock();
+      }
+    } catch (RuntimeException e) {
+      recreateDBInstance(generation);
+      throw e;
+    }
   }
 
   @Override
   public void close() throws IOException {
+    rwLock.writeLock().lock();
     try {
       db.close();
     } finally {
-      // WriteOptions is a native handle; release it even if db.close() throws.
+      rwLock.writeLock().unlock();
       SYNC_WRITE_OPTIONS.close();
     }
   }

Review Comment:
   `close()` acquires the write lock and closes `db` but does not null it out 
or set a "closed" flag. If a put/get/delete is called after `close()`, the 
operation will throw, fall through into the `catch (RocksDBException e)` 
branch, and `recreateDBInstance` will happily reopen the database via 
`RocksDBProvider.initRockDB`, resurrecting an instance that was explicitly 
closed and leaking native resources. Consider tracking a closed state and 
short-circuiting `recreateDBInstance`/all operations after `close()`.



##########
worker/src/main/java/org/apache/celeborn/service/deploy/worker/shuffledb/RocksDB.java:
##########
@@ -30,49 +36,154 @@
  * <p>Note: code copied from Apache Spark.
  */
 public class RocksDB extends DB {
-  private final org.rocksdb.RocksDB db;
+  private static final Logger logger = LoggerFactory.getLogger(RocksDB.class);
+
+  private volatile org.rocksdb.RocksDB db;
   private final WriteOptions SYNC_WRITE_OPTIONS = new 
WriteOptions().setSync(true);
+  private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
+  private final AtomicLong dbGeneration = new AtomicLong(0);
+  private final File dbFile;
+  private final StoreVersion version;
 
-  public RocksDB(org.rocksdb.RocksDB db, AbstractSource source, DBBackend 
dbBackend) {
+  public RocksDB(
+      org.rocksdb.RocksDB db,
+      AbstractSource source,
+      DBBackend dbBackend,
+      File dbFile,
+      StoreVersion version) {
     super(source, dbBackend);
     this.db = db;
+    this.dbFile = dbFile;
+    this.version = version;
+  }
+
+  private void recreateDBInstance(long failedGeneration) {
+    rwLock.writeLock().lock();
+    try {
+      if (dbGeneration.get() != failedGeneration) {
+        logger.info(
+            "RocksDB instance already recovered by another thread (generation 
{} -> {})",
+            failedGeneration,
+            dbGeneration.get());
+        return;
+      }
+
+      try {
+        if (db != null) {
+          db.close();
+        }
+      } catch (Exception e) {
+        logger.warn("Failed to close RocksDB instance", e);
+      }
+      db = RocksDBProvider.initRockDB(dbFile, version);
+      dbGeneration.incrementAndGet();
+    } catch (IOException e) {
+      logger.error(
+          "Failed to recreate RocksDB instance at {}. "
+              + "Database is unavailable, all subsequent operations will 
fail.",
+          dbFile,
+          e);
+    } finally {
+      rwLock.writeLock().unlock();
+    }
   }

Review Comment:
   No tests are added for this new automatic recovery behavior, even though it 
is non-trivial (concurrent recreate via generation counter, write/read lock 
ordering, exception path). Given the data-loss risk in the recreate path and 
the concurrency model, at minimum unit tests covering: (1) successful recreate 
on simulated `RocksDBException`, (2) only one recreate when multiple threads 
fail concurrently with the same generation, and (3) operations after `close()` 
not resurrecting the DB, would significantly reduce risk.



##########
worker/src/main/java/org/apache/celeborn/service/deploy/worker/shuffledb/RocksDB.java:
##########
@@ -30,49 +36,154 @@
  * <p>Note: code copied from Apache Spark.
  */
 public class RocksDB extends DB {
-  private final org.rocksdb.RocksDB db;
+  private static final Logger logger = LoggerFactory.getLogger(RocksDB.class);
+
+  private volatile org.rocksdb.RocksDB db;
   private final WriteOptions SYNC_WRITE_OPTIONS = new 
WriteOptions().setSync(true);
+  private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
+  private final AtomicLong dbGeneration = new AtomicLong(0);
+  private final File dbFile;
+  private final StoreVersion version;
 
-  public RocksDB(org.rocksdb.RocksDB db, AbstractSource source, DBBackend 
dbBackend) {
+  public RocksDB(
+      org.rocksdb.RocksDB db,
+      AbstractSource source,
+      DBBackend dbBackend,
+      File dbFile,
+      StoreVersion version) {
     super(source, dbBackend);
     this.db = db;
+    this.dbFile = dbFile;
+    this.version = version;
+  }
+
+  private void recreateDBInstance(long failedGeneration) {
+    rwLock.writeLock().lock();
+    try {
+      if (dbGeneration.get() != failedGeneration) {
+        logger.info(
+            "RocksDB instance already recovered by another thread (generation 
{} -> {})",
+            failedGeneration,
+            dbGeneration.get());
+        return;
+      }
+
+      try {
+        if (db != null) {
+          db.close();
+        }
+      } catch (Exception e) {
+        logger.warn("Failed to close RocksDB instance", e);
+      }
+      db = RocksDBProvider.initRockDB(dbFile, version);
+      dbGeneration.incrementAndGet();
+    } catch (IOException e) {
+      logger.error(
+          "Failed to recreate RocksDB instance at {}. "
+              + "Database is unavailable, all subsequent operations will 
fail.",
+          dbFile,
+          e);
+    } finally {
+      rwLock.writeLock().unlock();
+    }
   }
 
   @Override
   protected void putInternal(byte[] key, byte[] value) throws RocksDBException 
{
-    db.put(key, value);
+    long generation = 0;
+    try {
+      rwLock.readLock().lock();
+      generation = dbGeneration.get();
+      try {
+        db.put(key, value);
+      } finally {
+        rwLock.readLock().unlock();
+      }
+    } catch (RocksDBException e) {
+      recreateDBInstance(generation);
+      throw e;
+    }

Review Comment:
   The read lock is acquired inside the outer `try` block. If the `lock()` call 
itself were to throw (e.g., via thread interruption with a future lock 
variant), the `finally { rwLock.readLock().unlock(); }` would still run and 
attempt to unlock a lock that was never acquired, throwing 
`IllegalMonitorStateException` and masking the original cause. The conventional 
pattern is to call `lock()` before the `try` block. This pattern is duplicated 
in `putInternal` (both overloads), `getInternal`, `deleteInternal`, and 
`newIterator`.



##########
worker/src/main/java/org/apache/celeborn/service/deploy/worker/shuffledb/RocksDB.java:
##########
@@ -30,49 +36,154 @@
  * <p>Note: code copied from Apache Spark.
  */
 public class RocksDB extends DB {
-  private final org.rocksdb.RocksDB db;
+  private static final Logger logger = LoggerFactory.getLogger(RocksDB.class);
+
+  private volatile org.rocksdb.RocksDB db;
   private final WriteOptions SYNC_WRITE_OPTIONS = new 
WriteOptions().setSync(true);
+  private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
+  private final AtomicLong dbGeneration = new AtomicLong(0);
+  private final File dbFile;
+  private final StoreVersion version;
 
-  public RocksDB(org.rocksdb.RocksDB db, AbstractSource source, DBBackend 
dbBackend) {
+  public RocksDB(
+      org.rocksdb.RocksDB db,
+      AbstractSource source,
+      DBBackend dbBackend,
+      File dbFile,
+      StoreVersion version) {
     super(source, dbBackend);
     this.db = db;
+    this.dbFile = dbFile;
+    this.version = version;
+  }
+
+  private void recreateDBInstance(long failedGeneration) {
+    rwLock.writeLock().lock();
+    try {
+      if (dbGeneration.get() != failedGeneration) {
+        logger.info(
+            "RocksDB instance already recovered by another thread (generation 
{} -> {})",
+            failedGeneration,
+            dbGeneration.get());
+        return;
+      }
+
+      try {
+        if (db != null) {
+          db.close();
+        }
+      } catch (Exception e) {
+        logger.warn("Failed to close RocksDB instance", e);
+      }
+      db = RocksDBProvider.initRockDB(dbFile, version);
+      dbGeneration.incrementAndGet();
+    } catch (IOException e) {
+      logger.error(
+          "Failed to recreate RocksDB instance at {}. "
+              + "Database is unavailable, all subsequent operations will 
fail.",
+          dbFile,
+          e);
+    } finally {
+      rwLock.writeLock().unlock();
+    }
   }
 
   @Override
   protected void putInternal(byte[] key, byte[] value) throws RocksDBException 
{
-    db.put(key, value);
+    long generation = 0;
+    try {
+      rwLock.readLock().lock();
+      generation = dbGeneration.get();
+      try {
+        db.put(key, value);
+      } finally {
+        rwLock.readLock().unlock();
+      }
+    } catch (RocksDBException e) {
+      recreateDBInstance(generation);
+      throw e;
+    }
   }
 
   @Override
   protected void putInternal(byte[] key, byte[] value, boolean sync) throws 
RocksDBException {
-    if (sync) {
-      db.put(SYNC_WRITE_OPTIONS, key, value);
-    } else {
-      db.put(key, value);
+    long generation = 0;
+    try {
+      rwLock.readLock().lock();
+      generation = dbGeneration.get();
+      try {
+        if (sync) {
+          db.put(SYNC_WRITE_OPTIONS, key, value);
+        } else {
+          db.put(key, value);
+        }
+      } finally {
+        rwLock.readLock().unlock();
+      }
+    } catch (RocksDBException e) {
+      recreateDBInstance(generation);
+      throw e;
     }
   }
 
   @Override
   protected byte[] getInternal(byte[] key) throws RocksDBException {
-    return db.get(key);
+    long generation = 0;
+    try {
+      rwLock.readLock().lock();
+      generation = dbGeneration.get();
+      try {
+        return db.get(key);
+      } finally {
+        rwLock.readLock().unlock();
+      }
+    } catch (RocksDBException e) {
+      recreateDBInstance(generation);
+      throw e;
+    }
   }
 
   @Override
   protected void deleteInternal(byte[] key) throws RocksDBException {
-    db.delete(key);
+    long generation = 0;
+    try {
+      rwLock.readLock().lock();
+      generation = dbGeneration.get();
+      try {
+        db.delete(key);
+      } finally {
+        rwLock.readLock().unlock();
+      }
+    } catch (RocksDBException e) {
+      recreateDBInstance(generation);
+      throw e;
+    }
   }
 
   @Override
   protected DBIterator newIterator(MetadataMetrics metrics) {
-    return new RocksDBIterator(db.newIterator(), metrics);
+    long generation = 0;
+    try {
+      rwLock.readLock().lock();
+      generation = dbGeneration.get();
+      try {
+        return new RocksDBIterator(db.newIterator(), metrics);
+      } finally {
+        rwLock.readLock().unlock();
+      }
+    } catch (RuntimeException e) {
+      recreateDBInstance(generation);
+      throw e;
+    }
   }

Review Comment:
   The five operation methods all duplicate the same 
try/lock/generation/recreate scaffolding. This boilerplate is error-prone 
(e.g., easy to miss `unlock` or place `lock()` in the wrong scope) and will 
need to be repeated for every future DB method. Consider extracting a private 
`<T> T withRecovery(Callable<T>)` / `runWithRecovery(Runnable)` helper that 
encapsulates the read-lock, generation snapshot, exception capture, and 
recreate-on-failure logic.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to