rich7420 commented on code in PR #11188:
URL: https://github.com/apache/ozone/pull/11188#discussion_r3932278937


##########
hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogStateManagerImpl.java:
##########
@@ -157,14 +157,23 @@ public void 
addTransactionsToDB(ArrayList<DeletedBlocksTransaction> txs,
   @Override
   public void removeTransactionsFromDB(ArrayList<Long> txIDs, 
DeletedBlocksTransactionSummary summary)
       throws IOException {
-    if (deletingTxIDs != null) {
-      deletingTxIDs.addAll(txIDs);
-    }
-    for (Long txID : txIDs) {
-      transactionBuffer.removeFromBuffer(deletedTable, txID);
-    }
-    if (summary != null) {
-      transactionBuffer.addToBuffer(statefulConfigTable, SERVICE_NAME, 
summary.toByteString());
+    // Hold the buffer lock across the whole mark-remove-summary sequence so 
that a concurrent flush()
+    // (checkpoint download or leader transfer) cannot land between marking 
these txIDs as hidden and their
+    // removal being durably flushed. Otherwise onFlush() would reset 
deletingTxIDs while the row is still
+    // present, re-exposing it to the deletion scanner and causing the summary 
to be double-decremented.
+    transactionBuffer.lock();

Review Comment:
   This can still combine two different flush epochs. `deletedTable.iterator()` 
is initialized before `snapshotDeletingTxIDs`; 
[`RDBTable.iterator()`](https://github.com/apache/ozone/blob/3d65ad89310621211e727d21ca6bc0b82c506c0a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/db/RDBTable.java#L221-L225)
 creates the underlying RocksDB iterator at that point. If `flush()` runs 
between those two initializers, the iterator retains the deleted row while 
`onFlush()` replaces `deletingTxIDs` with a new empty set, so the row is 
returned again.
   
   I reproduced this by pausing after iterator creation, flushing the buffered 
delete, then resuming:
   
   ```
   [ERROR] A durably deleted transaction was re-exposed
   expected: <false> but was: <true>
   ```
   
   Reversing the two initializers made the same test pass. Please capture 
`deletingTxIDs` before creating the iterator, or hold the buffer read lock 
while capturing both, and cover this interleaving in the regression test.



##########
hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMHATransactionBufferMonitorTask.java:
##########
@@ -272,4 +288,122 @@ public void 
testMonitorTaskDoesNotPartialFlushDuringTransactionApply()
     assertEquals(ByteString.copyFromUtf8("value"),
         statefulServiceConfigTable.get("key"));
   }
+
+  /**
+   * Verifies that {@link SCMHADBTransactionBuffer#lock()} establishes mutual 
exclusion with
+   * {@link SCMHADBTransactionBufferImpl#flush()}'s write lock (HDDS-16145).
+   */
+  @Test
+  public void testLockBlocksConcurrentFlush() throws Exception {
+    transactionBuffer.updateLatestTrxInfo(TRX_INFO_T4);
+    transactionBuffer.flush();
+
+    transactionBuffer.lock();

Review Comment:
   nit: please release this lock in a `finally` block. If `waitFor` or the 
assertion fails before `unlock()`, the non-daemon flusher remains parked on the 
write lock until the test fork terminates.



-- 
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]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to