Copilot commented on code in PR #11118:
URL: https://github.com/apache/ozone/pull/11118#discussion_r3864750898


##########
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMDirectoriesPurgeRequestWithFSO.java:
##########
@@ -255,57 +170,421 @@ public OMClientResponse 
validateAndUpdateCache(OzoneManager ozoneManager, Execut
       }
       throw new IllegalStateException(ex);
     } finally {
+      if (lockAcquired) {
+        
mergeOmLockDetails(omMetadataManager.getLock().releaseWriteLocks(BUCKET_LOCK, 
bucketLockKeys));
+      }
+      // Snapshot the mutated bucket infos for the response after releasing 
the lock. The single apply thread is the
+      // only writer, so no other transaction can mutate them between release 
and copy.
       for (Map.Entry<Pair<String, String>, OmBucketInfo> entry :
-          volBucketInfoMap.entrySet()) {
+          result.volBucketInfoMap.entrySet()) {
         entry.setValue(entry.getValue().copyObject());
       }
-      if (lockAcquired) {
-        
mergeOmLockDetails(omMetadataManager.getLock().releaseWriteLocks(BUCKET_LOCK, 
bucketLockKeys));
-      }  
     }
 
     return new OMDirectoriesPurgeResponseWithFSO(
         omResponse.build(), purgeRequests,
-        getBucketLayout(), volBucketInfoMap, fromSnapshotInfo, openKeyInfoMap);
+        getBucketLayout(), result.volBucketInfoMap, fromSnapshotInfo, 
result.openKeyInfoMap);
+  }
+
+  /**
+   * De-duplicates purged directories from the moved sub-directory set and 
updates the deletion service metrics for
+   * this transaction, returning the resulting number of sub-directories moved 
(used for the success audit).
+   */
+  private int recordDeletionMetrics(DeletingServiceMetrics 
deletingServiceMetrics, OMMetrics omMetrics,
+      long numKeysProcessed, PurgeApplyResult result) {
+    // Apply the per-entry global OM metric mutations in bulk (one increment 
each instead of once per entry) to
+    // minimize work done while holding the bucket write lock.
+    omMetrics.decNumKeys(numKeysProcessed);
+    omMetrics.incNumKeyDeletesInternal(numKeysProcessed);
+    // Remove deletedDirNames from subDirNames to avoid duplication
+    result.subDirNames.removeAll(result.deletedDirNames);
+    int numSubDirMoved = result.subDirNames.size();
+    deletingServiceMetrics.incrNumSubDirectoriesMoved(numSubDirMoved);
+    deletingServiceMetrics.incrNumSubFilesMoved(result.numSubFilesMoved);
+    deletingServiceMetrics.incrNumDirPurged(result.numDirsDeleted);
+    return numSubDirMoved;
+  }
+
+  /**
+   * Builds and emits the success audit message for a directory purge. Kept 
separate so the per-entry parameter map is
+   * only assembled when debug audit logging is enabled.
+   */
+  private void logDirectoryDeletionAuditSuccess(OzoneManager ozoneManager, 
SnapshotInfo fromSnapshotInfo,
+      int numSubDirMoved, PurgeApplyResult result) {
+    Map<String, String> auditParams = new LinkedHashMap<>();
+    if (fromSnapshotInfo != null) {
+      auditParams.put(AUDIT_PARAM_SNAPSHOT_ID, 
fromSnapshotInfo.getSnapshotId().toString());
+    }
+    auditParams.put(AUDIT_PARAM_DIRS_DELETED, 
String.valueOf(result.numDirsDeleted));
+    auditParams.put(AUDIT_PARAM_SUBDIRS_MOVED, String.valueOf(numSubDirMoved));
+    auditParams.put(AUDIT_PARAM_SUBFILES_MOVED, 
String.valueOf(result.numSubFilesMoved));
+    auditParams.put(AUDIT_PARAM_DIRS_DELETED_LIST, String.join(",", 
result.deletedDirNames));
+    auditParams.put(AUDIT_PARAM_SUBDIRS_MOVED_LIST, String.join(",", 
result.subDirNames));
+    auditParams.put(AUDIT_PARAM_SUBFILES_MOVED_LIST, String.join(",", 
result.subFileNames));
+    
AUDIT.logWriteSuccess(ozoneManager.buildAuditMessageForSuccess(OMSystemAction.DIRECTORY_DELETION,
 auditParams));
+  }
+
+  /**
+   * Logs, audits and builds the error response for a failure while resolving 
the {@code fromSnapshot}. Extracted so
+   * the {@link #validateAndUpdateCache} catch block reads as a single call.
+   */
+  private OMClientResponse directoryPurgeFailure(OzoneManager ozoneManager, 
OMResponse.Builder omResponse,
+      IOException e) {
+    LOG.error("Error occurred while performing OMDirectoriesPurge. ", e);
+    if (LOG.isDebugEnabled()) {
+      
AUDIT.logWriteFailure(ozoneManager.buildAuditMessageForFailure(OMSystemAction.DIRECTORY_DELETION,
 null, e));
+    }
+    return new 
OMDirectoriesPurgeResponseWithFSO(createErrorOMResponse(omResponse, e));
+  }
+
+  /**
+   * Resolves the {@code fromSnapshot} this purge runs against and, for 
new-format requests, validates that the
+   * previous snapshot chain has not changed. Extracted so {@link 
#validateAndUpdateCache} reads as setup → per-path
+   * apply → bookkeeping.
+   */
+  private SnapshotInfo resolveFromSnapshotInfo(OzoneManager ozoneManager, 
OmMetadataManagerImpl omMetadataManager,
+      PurgeDirectoriesRequest purgeDirsRequest, String fromSnapshot) throws 
IOException {
+    SnapshotInfo fromSnapshotInfo = fromSnapshot != null
+        ? SnapshotUtils.getSnapshotInfo(ozoneManager, fromSnapshot) : null;
+    // Checking if this request is an old request or new one.
+    if (purgeDirsRequest.hasExpectedPreviousSnapshotID()) {
+      // Validating previous snapshot since while purging deletes, a snapshot 
create request could make this purge
+      // directory request invalid on AOS since the deletedDirectory would be 
in the newly created snapshot. Adding
+      // subdirectories could lead to not being able to reclaim sub-files and 
subdirectories since the
+      // file/directory would be present in the newly created snapshot.
+      // Validating previous snapshot can ensure the chain hasn't changed.
+      UUID expectedPreviousSnapshotId = 
purgeDirsRequest.getExpectedPreviousSnapshotID().hasUuid()
+          ? 
fromProtobuf(purgeDirsRequest.getExpectedPreviousSnapshotID().getUuid()) : null;
+      validatePreviousSnapshotId(fromSnapshotInfo, 
omMetadataManager.getSnapshotChainManager(),
+          expectedPreviousSnapshotId);
+    }
+    return fromSnapshotInfo;
+  }
+
+  /**
+   * Phase 1 (no lock): parses a path's sub-directories into prepared entries, 
precomputing each delete/path key. The
+   * directory-table tombstones and bucket namespace decrements are applied 
later under the lock in
+   * {@link #applyPreparedEntries}.
+   */
+  private List<PreparedEntry> 
prepareMarkDeletedSubDirs(OzoneManagerProtocolProtos.PurgePathRequest path,
+      OmMetadataManagerImpl omMetadataManager) {
+    List<PreparedEntry> preparedSubDirs = new ArrayList<>();
+    for (OzoneManagerProtocolProtos.KeyInfo key : 
path.getMarkDeletedSubDirsList()) {
+      ProcessedKeyInfo processed = processDeleteKey(key, path, 
omMetadataManager);
+      preparedSubDirs.add(new PreparedEntry(processed, path.getBucketId(), 0L, 
null));
+    }
+    return preparedSubDirs;
+  }
+
+  /**
+   * Phase 1 (no lock): parses a path's sub-files into prepared entries, 
precomputing each delete/path key, its
+   * replicated size and, for hsync files, the open-key name to clean up. The 
file-table tombstones, hsync open-key
+   * cleanup and bucket quota decrements are applied later under the lock in 
{@link #applyPreparedEntries}.
+   */
+  private List<PreparedEntry> 
prepareMoveDeletedSubFiles(OzoneManagerProtocolProtos.PurgePathRequest path,
+      OmMetadataManagerImpl omMetadataManager) {
+    List<PreparedEntry> preparedSubFiles = new ArrayList<>();
+    for (OzoneManagerProtocolProtos.KeyInfo key : 
path.getDeletedSubFilesList()) {
+      ProcessedKeyInfo processed = processDeleteKey(key, path, 
omMetadataManager);
+      long replicatedSize = sumBlockLengths(key);
+      // If omKeyInfo has hsync metadata, its corresponding open key is 
cleaned up under the lock.
+      String hsyncClientId = getHsyncClientId(key);
+      String dbOpenKey = hsyncClientId == null ? null
+          : omMetadataManager.getOpenFileName(path.getVolumeId(), 
path.getBucketId(),
+          processed.parentObjectID, processed.fileName, hsyncClientId);
+      preparedSubFiles.add(new PreparedEntry(processed, path.getBucketId(), 
replicatedSize, dbOpenKey));
+    }
+    return preparedSubFiles;
+  }
+
+  /**
+   * Phase 1 (no lock): records a path's deleted directory for later purge 
under the lock in
+   * {@link #applyPreparedEntries}.
+   */
+  private PreparedDirPurge 
prepareDeletedDir(OzoneManagerProtocolProtos.PurgePathRequest path,
+      BucketNameInfo bucketNameInfo) {
+    return new PreparedDirPurge(bucketNameInfo.getVolumeName(), 
bucketNameInfo.getBucketName(),
+        path.getBucketId(), path.getDeletedDir());
+  }
+
+  /**
+   * Phase 2 (under the bucket write lock): applies the prepared 
directory/file tombstones, hsync open-key cleanup and
+   * per-bucket quota changes. Quota deltas are accumulated per bucket and 
applied once, instead of once per entry, to
+   * minimize the mutations done while holding the write lock.
+   */
+  private void applyPreparedEntries(List<PreparedEntry> preparedSubDirs, 
List<PreparedEntry> preparedSubFiles,
+      List<PreparedDirPurge> preparedDirPurges, OmMetadataManagerImpl 
omMetadataManager, long trxnLogIndex,
+      PurgeApplyResult result) throws IOException {
+    // Memoizes getBucketInfo lookups within this apply so that a purge 
transaction touching many keys of the same
+    // bucket resolves the bucket cache entry once instead of per key.
+    Map<Pair<String, String>, OmBucketInfo> bucketInfoCache = new HashMap<>();
+    // Quota deltas are accumulated per bucket across all three entry loops 
and applied once at the end.
+    Map<Pair<String, String>, QuotaDelta> quotaDeltas = new HashMap<>();
+
+    applyMarkedSubDirs(preparedSubDirs, omMetadataManager, trxnLogIndex, 
result, bucketInfoCache, quotaDeltas);
+    applyMovedSubFiles(preparedSubFiles, omMetadataManager, trxnLogIndex, 
result, bucketInfoCache, quotaDeltas);
+    applyDirPurges(preparedDirPurges, omMetadataManager, result, 
bucketInfoCache, quotaDeltas);
+    applyQuotaDeltas(quotaDeltas);
+  }
+
+  /**
+   * Phase 2 (under the bucket write lock): tombstones each prepared 
sub-directory in the directory table and
+   * accumulates its namespace quota decrement, when its bucket still matches 
the prepared bucket id.
+   */
+  private void applyMarkedSubDirs(List<PreparedEntry> preparedSubDirs, 
OmMetadataManagerImpl omMetadataManager,
+      long trxnLogIndex, PurgeApplyResult result, Map<Pair<String, String>, 
OmBucketInfo> bucketInfoCache,
+      Map<Pair<String, String>, QuotaDelta> quotaDeltas) {
+    for (PreparedEntry entry : preparedSubDirs) {
+      ProcessedKeyInfo processed = entry.processed;
+      result.subDirNames.add(processed.deleteKey);
+      OmBucketInfo omBucketInfo = getBucketInfoCached(omMetadataManager,
+          bucketInfoCache, processed.volumeName, processed.bucketName);
+      // bucketInfo can be null in case of delete volume or bucket
+      // or key does not belong to bucket as bucket is recreated
+      if (null != omBucketInfo && omBucketInfo.getObjectID() == 
entry.bucketId) {
+        omMetadataManager.getDirectoryTable().addCacheEntry(new 
CacheKey<>(processed.pathKey),
+            CacheValue.get(trxnLogIndex));
+        result.volBucketInfoMap.putIfAbsent(processed.volBucketPair, 
omBucketInfo);
+        quotaDeltas.computeIfAbsent(processed.volBucketPair, k -> new 
QuotaDelta(omBucketInfo)).usedNamespace += 1L;
+      }
+    }
+  }
+
+  /**
+   * Phase 2 (under the bucket write lock): tombstones each prepared sub-file 
in the file table, cleans up its hsync
+   * open key when present, and accumulates its byte and namespace quota 
decrements, when its bucket still matches the
+   * prepared bucket id.
+   */
+  private void applyMovedSubFiles(List<PreparedEntry> preparedSubFiles, 
OmMetadataManagerImpl omMetadataManager,
+      long trxnLogIndex, PurgeApplyResult result, Map<Pair<String, String>, 
OmBucketInfo> bucketInfoCache,
+      Map<Pair<String, String>, QuotaDelta> quotaDeltas) throws IOException {
+    for (PreparedEntry entry : preparedSubFiles) {
+      ProcessedKeyInfo processed = entry.processed;
+      result.subFileNames.add(processed.deleteKey);
+
+      // If omKeyInfo has hsync metadata, delete its corresponding open key as 
well
+      if (entry.dbOpenKey != null) {
+        OmKeyInfo openKeyInfo = 
omMetadataManager.getOpenKeyTable(getBucketLayout()).get(entry.dbOpenKey);
+        if (openKeyInfo != null) {
+          openKeyInfo = openKeyInfo.withMetadataMutations(
+              metadata -> metadata.put(DELETED_HSYNC_KEY, "true"));
+          result.openKeyInfoMap.put(entry.dbOpenKey, openKeyInfo);
+        }
+      }
+
+      result.numSubFilesMoved++;
+      OmBucketInfo omBucketInfo = getBucketInfoCached(omMetadataManager,
+          bucketInfoCache, processed.volumeName, processed.bucketName);
+      // bucketInfo can be null in case of delete volume or bucket
+      // or key does not belong to bucket as bucket is recreated
+      if (null != omBucketInfo && omBucketInfo.getObjectID() == 
entry.bucketId) {
+        omMetadataManager.getFileTable().addCacheEntry(new 
CacheKey<>(processed.pathKey),
+            CacheValue.get(trxnLogIndex));
+        result.volBucketInfoMap.putIfAbsent(processed.volBucketPair, 
omBucketInfo);
+        QuotaDelta quotaDelta = 
quotaDeltas.computeIfAbsent(processed.volBucketPair, k -> new 
QuotaDelta(omBucketInfo));
+        quotaDelta.usedBytes += entry.replicatedSize;
+        quotaDelta.usedNamespace += 1L;
+      }
+    }
+  }
+
+  /**
+   * Phase 2 (under the bucket write lock): records each purged directory and 
accumulates its snapshot-namespace purge,
+   * when its bucket still matches the prepared bucket id.
+   */
+  private void applyDirPurges(List<PreparedDirPurge> preparedDirPurges, 
OmMetadataManagerImpl omMetadataManager,
+      PurgeApplyResult result, Map<Pair<String, String>, OmBucketInfo> 
bucketInfoCache,
+      Map<Pair<String, String>, QuotaDelta> quotaDeltas) {
+    for (PreparedDirPurge dirPurge : preparedDirPurges) {
+      result.deletedDirNames.add(dirPurge.deletedDir);
+      OmBucketInfo omBucketInfo = getBucketInfoCached(omMetadataManager,
+          bucketInfoCache, dirPurge.volumeName, dirPurge.bucketName);
+      if (omBucketInfo != null && omBucketInfo.getObjectID() == 
dirPurge.bucketId) {
+        Pair<String, String> volBucketPair = 
Pair.of(omBucketInfo.getVolumeName(), omBucketInfo.getBucketName());
+        result.volBucketInfoMap.put(volBucketPair, omBucketInfo);
+        quotaDeltas.computeIfAbsent(volBucketPair, k -> new 
QuotaDelta(omBucketInfo)).snapshotNamespacePurge += 1L;
+      }
+      result.numDirsDeleted++;
+    }
+  }
+
+  /**
+   * Phase 2 (under the bucket write lock): applies the per-bucket accumulated 
quota changes once each, instead of once
+   * per entry, to minimize the mutations done while holding the bucket write 
lock.
+   */
+  private void applyQuotaDeltas(Map<Pair<String, String>, QuotaDelta> 
quotaDeltas) {
+    for (QuotaDelta quotaDelta : quotaDeltas.values()) {
+      if (quotaDelta.usedBytes != 0L) {
+        quotaDelta.bucketInfo.decrUsedBytes(quotaDelta.usedBytes, true);
+      }
+      if (quotaDelta.usedNamespace != 0L) {
+        quotaDelta.bucketInfo.decrUsedNamespace(quotaDelta.usedNamespace, 
true);
+      }
+      if (quotaDelta.snapshotNamespacePurge != 0L) {
+        
quotaDelta.bucketInfo.purgeSnapshotUsedNamespace(quotaDelta.snapshotNamespacePurge);
+      }
+    }
+  }
+
+  /**
+   * Phase 2 output accumulated by {@link #applyPreparedEntries} under the 
bucket write lock: the mutated bucket infos
+   * and hsync open keys used to build the response, plus the moved 
sub-dir/sub-file/deleted-dir names and counts used
+   * for the deletion metrics and the success audit.
+   */
+  private static final class PurgeApplyResult {
+    private final Map<Pair<String, String>, OmBucketInfo> volBucketInfoMap = 
new HashMap<>();
+    private final Map<String, OmKeyInfo> openKeyInfoMap = new HashMap<>();
+    private final Set<String> subDirNames = new HashSet<>();
+    private final Set<String> subFileNames = new HashSet<>();
+    private final Set<String> deletedDirNames = new HashSet<>();
+    private int numSubFilesMoved;
+    private int numDirsDeleted;
   }
 
   /**
    * Helper class to hold processed key information.
    */
   private static class ProcessedKeyInfo {
-    private final OmKeyInfo keyInfo;
     private final String deleteKey;
+    private final String pathKey;
     private final String volumeName;
     private final String bucketName;
+    private final long parentObjectID;
+    private final String fileName;
     private final Pair<String, String> volBucketPair;
 
-    ProcessedKeyInfo(OmKeyInfo keyInfo, String deleteKey, String volumeName, 
String bucketName) {
-      this.keyInfo = keyInfo;
+    ProcessedKeyInfo(String deleteKey, String pathKey, String volumeName,
+                     String bucketName, long parentObjectID, String fileName) {
       this.deleteKey = deleteKey;
+      this.pathKey = pathKey;
       this.volumeName = volumeName;
       this.bucketName = bucketName;
+      this.parentObjectID = parentObjectID;
+      this.fileName = fileName;
       this.volBucketPair = Pair.of(volumeName, bucketName);
     }
   }
 
+  /**
+   * A sub-directory or sub-file prepared (lock-free) in phase 1 for 
application under the bucket write lock in phase
+   * 2. {@code replicatedSize} is the file's replicated byte usage (0 for 
directories) and {@code dbOpenKey} is the
+   * hsync open-key to clean up, or {@code null} when the entry is not an 
hsync file.
+   */
+  private static final class PreparedEntry {
+    private final ProcessedKeyInfo processed;
+    private final long bucketId;
+    private final long replicatedSize;
+    private final String dbOpenKey;
+
+    PreparedEntry(ProcessedKeyInfo processed, long bucketId, long 
replicatedSize, String dbOpenKey) {
+      this.processed = processed;
+      this.bucketId = bucketId;
+      this.replicatedSize = replicatedSize;
+      this.dbOpenKey = dbOpenKey;
+    }
+  }
+
+  /**
+   * A deleted directory prepared (lock-free) in phase 1 for its 
snapshot-namespace purge under the bucket write lock
+   * in phase 2.
+   */
+  private static final class PreparedDirPurge {
+    private final String volumeName;
+    private final String bucketName;
+    private final long bucketId;
+    private final String deletedDir;
+
+    PreparedDirPurge(String volumeName, String bucketName, long bucketId, 
String deletedDir) {
+      this.volumeName = volumeName;
+      this.bucketName = bucketName;
+      this.bucketId = bucketId;
+      this.deletedDir = deletedDir;
+    }
+  }
+
+  /**
+   * Accumulates a single bucket's quota changes across all entries in a purge 
so they can be applied once under the
+   * write lock instead of once per entry.
+   */
+  private static final class QuotaDelta {
+    private final OmBucketInfo bucketInfo;
+    private long usedBytes;
+    private long usedNamespace;
+    private long snapshotNamespacePurge;
+
+    QuotaDelta(OmBucketInfo bucketInfo) {
+      this.bucketInfo = bucketInfo;
+    }
+  }
+
   /**
    * Process delete key info.
-   * Returns ProcessedKeyInfo containing all the processed information.
+   * Reads only the fields the purge apply path needs directly from the 
protobuf, instead of building a full
+   * {@link OmKeyInfo} (with its key-location, ACL, tag, encryption and 
checksum objects) for every entry on the
+   * single-threaded apply path.
    */
   private ProcessedKeyInfo processDeleteKey(OzoneManagerProtocolProtos.KeyInfo 
key,
                                             
OzoneManagerProtocolProtos.PurgePathRequest path,
                                             OmMetadataManagerImpl 
omMetadataManager) {
-    OmKeyInfo keyInfo = OmKeyInfo.getFromProtobuf(key);
+    long objectID = key.hasObjectID() ? key.getObjectID() : 0L;
+    long parentObjectID = key.hasParentID() ? key.getParentID() : 0L;
+    String fileName = OzoneFSUtils.getFileName(key.getKeyName());
 
     String pathKey = omMetadataManager.getOzonePathKey(path.getVolumeId(),
-        path.getBucketId(), keyInfo.getParentObjectID(), 
keyInfo.getFileName());
-    String deleteKey = omMetadataManager.getOzoneDeletePathKey(
-        keyInfo.getObjectID(), pathKey);
+        path.getBucketId(), parentObjectID, fileName);
+    String deleteKey = omMetadataManager.getOzoneDeletePathKey(objectID, 
pathKey);
 
-    String volumeName = keyInfo.getVolumeName();
-    String bucketName = keyInfo.getBucketName();
+    return new ProcessedKeyInfo(deleteKey, pathKey, key.getVolumeName(), 
key.getBucketName(),
+        parentObjectID, fileName);
+  }
 
-    return new ProcessedKeyInfo(keyInfo, deleteKey, volumeName, bucketName);
+  /**
+   * Returns the cached bucket info for the given volume/bucket, memoizing the 
lookup within a single apply so that a
+   * purge transaction touching many keys of the same bucket does the {@link 
#getBucketInfo} cache lookup once. The
+   * returned instance is the same cached reference {@link #getBucketInfo} 
returns, so in-place quota mutations behave
+   * identically. {@code null} results (deleted bucket) are memoized too.
+   */
+  private static OmBucketInfo getBucketInfoCached(OmMetadataManagerImpl 
omMetadataManager,
+      Map<Pair<String, String>, OmBucketInfo> cache, String volumeName, String 
bucketName) {
+    Pair<String, String> cacheKey = Pair.of(volumeName, bucketName);
+    if (cache.containsKey(cacheKey)) {
+      return cache.get(cacheKey);
+    }
+    OmBucketInfo omBucketInfo = getBucketInfo(omMetadataManager, volumeName, 
bucketName);
+    cache.put(cacheKey, omBucketInfo);
+    return omBucketInfo;
+  }
+
+  /**
+   * Reads the HSYNC client id directly from the key's protobuf metadata, 
mirroring
+   * {@code KeyValueUtil.getFromProtobuf(...).get(HSYNC_CLIENT_ID)} without 
building the full metadata map.
+   */
+  private static String getHsyncClientId(OzoneManagerProtocolProtos.KeyInfo 
key) {
+    String hsyncClientId = null;
+    for (HddsProtos.KeyValue kv : key.getMetadataList()) {
+      if (OzoneConsts.HSYNC_CLIENT_ID.equals(kv.getKey())) {
+        hsyncClientId = kv.getValue();
+      }
+    }

Review Comment:
   getHsyncClientId scans the full metadata list even after finding 
HSYNC_CLIENT_ID. Since this method is called for every deleted sub-file during 
purge, breaking early avoids unnecessary iteration on the apply path.



##########
hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestDirectoryDeletingService.java:
##########
@@ -331,4 +331,63 @@ void testPurgeDirectoriesBatching() throws Exception {
     org.apache.commons.io.FileUtils.deleteDirectory(testDir);
   }
 
+  @Test
+  @DisplayName("DirectoryDeletingService submits one bucket per 
PurgeDirectories transaction")
+  void testPurgeDirectoriesGroupedByBucketPerTransaction() throws Exception {
+    OzoneConfiguration conf = new OzoneConfiguration();
+    File testDir = Files.createTempDirectory("TestDDS-BucketGroup").toFile();
+    ServerUtils.setOzoneMetaDirPath(conf, testDir.toString());
+    conf.setTimeDuration(OMConfigKeys.OZONE_DIR_DELETING_SERVICE_INTERVAL, 
100, TimeUnit.MILLISECONDS);
+    conf.setQuietMode(false);

Review Comment:
   This test creates a temp directory and deletes it at the end of the method, 
but not in a finally block. If an assertion fails (or an exception is thrown) 
before the final deleteDirectory call, the temporary directory will be leaked 
on the test machine.



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