|
22 | 22 | import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_THREAD_NUMBER_DIR_DELETION; |
23 | 23 | import static org.assertj.core.api.Assertions.assertThat; |
24 | 24 | import static org.junit.jupiter.api.Assertions.assertTrue; |
25 | | -import static org.mockito.ArgumentMatchers.any; |
26 | | -import static org.mockito.Mockito.CALLS_REAL_METHODS; |
27 | | -import static org.mockito.Mockito.mockStatic; |
28 | 25 |
|
29 | 26 | import java.io.File; |
30 | 27 | import java.io.IOException; |
31 | 28 | import java.nio.file.Files; |
32 | 29 | import java.nio.file.Path; |
33 | 30 | import java.util.ArrayList; |
| 31 | +import java.util.Collections; |
34 | 32 | import java.util.HashMap; |
| 33 | +import java.util.HashSet; |
35 | 34 | import java.util.List; |
36 | 35 | import java.util.Map; |
| 36 | +import java.util.Set; |
37 | 37 | import java.util.concurrent.CompletableFuture; |
| 38 | +import java.util.concurrent.CountDownLatch; |
| 39 | +import java.util.concurrent.ThreadPoolExecutor; |
38 | 40 | import java.util.concurrent.TimeUnit; |
39 | | -import java.util.concurrent.atomic.AtomicBoolean; |
40 | 41 | import java.util.concurrent.atomic.AtomicInteger; |
41 | | -import java.util.function.Supplier; |
42 | | -import org.apache.commons.lang3.tuple.Pair; |
43 | 42 | import org.apache.hadoop.hdds.client.RatisReplicationConfig; |
44 | 43 | import org.apache.hadoop.hdds.client.StandaloneReplicationConfig; |
45 | 44 | import org.apache.hadoop.hdds.conf.OzoneConfiguration; |
|
66 | 65 | import org.junit.jupiter.api.DisplayName; |
67 | 66 | import org.junit.jupiter.api.Test; |
68 | 67 | import org.junit.jupiter.api.io.TempDir; |
69 | | -import org.mockito.MockedStatic; |
70 | 68 | import org.mockito.Mockito; |
71 | | -import org.slf4j.Logger; |
72 | | -import org.slf4j.LoggerFactory; |
73 | 69 |
|
74 | 70 | /** |
75 | 71 | * Test Directory Deleting Service. |
76 | 72 | */ |
77 | 73 | public class TestDirectoryDeletingService { |
78 | 74 |
|
79 | | - private static final Logger LOG = LoggerFactory.getLogger(TestDirectoryDeletingService.class); |
80 | | - |
81 | 75 | @TempDir |
82 | 76 | private Path folder; |
83 | 77 | private OzoneManager om; |
@@ -189,52 +183,48 @@ public void testDeleteDirectoryCrossingSizeLimit() throws Exception { |
189 | 183 | public void testMultithreadedDirectoryDeletion() throws Exception { |
190 | 184 | int threadCount = 10; |
191 | 185 | OzoneConfiguration conf = createConfAndInitValues(threadCount); |
192 | | - OmTestManagers omTestManagers |
193 | | - = new OmTestManagers(conf); |
| 186 | + OmTestManagers omTestManagers = new OmTestManagers(conf); |
194 | 187 | OzoneManager ozoneManager = omTestManagers.getOzoneManager(); |
195 | | - AtomicBoolean isRunning = new AtomicBoolean(true); |
196 | | - try (MockedStatic mockedStatic = mockStatic(CompletableFuture.class, CALLS_REAL_METHODS)) { |
197 | | - List<Pair<Supplier, CompletableFuture>> futureList = new ArrayList<>(); |
198 | | - Thread deletionThread = new Thread(() -> { |
199 | | - while (futureList.size() < threadCount) { |
| 188 | + try { |
| 189 | + DirectoryDeletingService service = |
| 190 | + (DirectoryDeletingService) ozoneManager.getKeyManager().getDirDeletingService(); |
| 191 | + ThreadPoolExecutor threadPoolExecutor = service.getDeletionThreadPool(); |
| 192 | + assertThat(threadPoolExecutor.getCorePoolSize()).as( |
| 193 | + "core pool size should match configured directory deletion threads").isEqualTo(threadCount); |
| 194 | + |
| 195 | + CountDownLatch tasksStarted = new CountDownLatch(threadCount); |
| 196 | + CountDownLatch releaseTasks = new CountDownLatch(1); |
| 197 | + Set<String> workerThreads = Collections.synchronizedSet(new HashSet<>()); |
| 198 | + List<CompletableFuture<Void>> futures = new ArrayList<>(); |
| 199 | + |
| 200 | + for (int i = 0; i < threadCount; i++) { |
| 201 | + futures.add(CompletableFuture.runAsync(() -> { |
| 202 | + workerThreads.add(Thread.currentThread().getName()); |
| 203 | + tasksStarted.countDown(); |
200 | 204 | try { |
201 | | - Thread.sleep(100); |
| 205 | + assertTrue(releaseTasks.await(10, TimeUnit.SECONDS)); |
202 | 206 | } catch (InterruptedException e) { |
203 | | - LOG.error("Error while sleeping", e); |
| 207 | + Thread.currentThread().interrupt(); |
| 208 | + throw new AssertionError("Interrupted while waiting for release latch", e); |
204 | 209 | } |
205 | | - } |
206 | | - for (int i = futureList.size() - 1; i >= 0; i--) { |
207 | | - Pair<Supplier, CompletableFuture> pair = futureList.get(i); |
208 | | - pair.getLeft().get(); |
209 | | - assertTrue(isRunning.get()); |
210 | | - pair.getRight().complete(false); |
211 | | - try { |
212 | | - Thread.sleep(500); |
213 | | - } catch (InterruptedException e) { |
214 | | - LOG.error("Error while sleeping", e); |
215 | | - } |
216 | | - } |
217 | | - }); |
218 | | - deletionThread.start(); |
219 | | - |
220 | | - mockedStatic |
221 | | - .when(() -> CompletableFuture.supplyAsync(any(), any())) |
222 | | - .thenAnswer(invocation -> { |
223 | | - Supplier<Boolean> supplier = invocation.getArgument(0); |
224 | | - CompletableFuture<Boolean> future = new CompletableFuture<>(); |
225 | | - futureList.add(Pair.of(supplier, future)); |
226 | | - return future; |
227 | | - }); |
228 | | - ozoneManager.getKeyManager().getDirDeletingService().suspend(); |
229 | | - DirectoryDeletingService.DirDeletingTask dirDeletingTask = |
230 | | - ozoneManager.getKeyManager().getDirDeletingService().new DirDeletingTask(null); |
231 | | - |
232 | | - dirDeletingTask.processDeletedDirsForStore(null, ozoneManager.getKeyManager(), 1, 6000); |
233 | | - assertThat(futureList).hasSize(threadCount); |
234 | | - for (Pair<Supplier, CompletableFuture> pair : futureList) { |
235 | | - assertTrue(pair.getRight().isDone()); |
| 210 | + }, threadPoolExecutor)); |
236 | 211 | } |
237 | | - isRunning.set(false); |
| 212 | + |
| 213 | + assertTrue(tasksStarted.await(10, TimeUnit.SECONDS), "Expected all submitted tasks to start"); |
| 214 | + assertThat(workerThreads).as("Expected tasks to run in parallel using configured workers").hasSize(threadCount); |
| 215 | + |
| 216 | + releaseTasks.countDown(); |
| 217 | + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get(10, TimeUnit.SECONDS); |
| 218 | + |
| 219 | + // Also exercise the actual DirectoryDeletingService task path. |
| 220 | + service.suspend(); |
| 221 | + DirectoryDeletingService.DirDeletingTask dirDeletingTask = service.new DirDeletingTask(null); |
| 222 | + assertThat(threadPoolExecutor.isShutdown()).as( |
| 223 | + "deletion thread pool should be active when processing deleted dirs").isFalse(); |
| 224 | + long completedTaskCountBefore = threadPoolExecutor.getCompletedTaskCount(); |
| 225 | + dirDeletingTask.processDeletedDirsForStore(null, ozoneManager.getKeyManager(), 1, 1); |
| 226 | + GenericTestUtils.waitFor( |
| 227 | + () -> threadPoolExecutor.getCompletedTaskCount() >= completedTaskCountBefore + threadCount, 100, 5000); |
238 | 228 | } finally { |
239 | 229 | ozoneManager.stop(); |
240 | 230 | } |
|
0 commit comments