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

elharo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-shared-io.git


The following commit(s) were added to refs/heads/master by this push:
     new b7ef32d  fix #85: replace HashMap with ConcurrentHashMap for 
thread-safe cache (#100)
b7ef32d is described below

commit b7ef32dcbcdc2ce2dfdf277b7034c85001576334
Author: Elliotte Rusty Harold <[email protected]>
AuthorDate: Sun Jul 5 17:52:47 2026 +0000

    fix #85: replace HashMap with ConcurrentHashMap for thread-safe cache (#100)
    
    - Change cache field from HashMap to ConcurrentHashMap
    - Add concurrency test for cache access
---
 .../shared/io/download/DefaultDownloadManager.java |  4 +-
 .../io/download/DefaultDownloadManagerTest.java    | 44 ++++++++++++++++++++++
 2 files changed, 46 insertions(+), 2 deletions(-)

diff --git 
a/src/main/java/org/apache/maven/shared/io/download/DefaultDownloadManager.java 
b/src/main/java/org/apache/maven/shared/io/download/DefaultDownloadManager.java
index 6e40f3b..e50ec49 100644
--- 
a/src/main/java/org/apache/maven/shared/io/download/DefaultDownloadManager.java
+++ 
b/src/main/java/org/apache/maven/shared/io/download/DefaultDownloadManager.java
@@ -24,10 +24,10 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import java.nio.file.Files;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.maven.artifact.manager.WagonManager;
 import org.apache.maven.shared.io.logging.MessageHolder;
@@ -54,7 +54,7 @@ public class DefaultDownloadManager implements 
DownloadManager {
 
     private WagonManager wagonManager;
 
-    private Map<String, File> cache = new HashMap<>();
+    private Map<String, File> cache = new ConcurrentHashMap<>();
 
     /**
      * Create an instance of the {@code DefaultDownloadManager}.
diff --git 
a/src/test/java/org/apache/maven/shared/io/download/DefaultDownloadManagerTest.java
 
b/src/test/java/org/apache/maven/shared/io/download/DefaultDownloadManagerTest.java
index 27a566f..cd6f964 100644
--- 
a/src/test/java/org/apache/maven/shared/io/download/DefaultDownloadManagerTest.java
+++ 
b/src/test/java/org/apache/maven/shared/io/download/DefaultDownloadManagerTest.java
@@ -20,7 +20,12 @@ package org.apache.maven.shared.io.download;
 
 import java.io.File;
 import java.nio.file.Files;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 
 import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.apache.maven.artifact.manager.WagonManager;
@@ -313,6 +318,45 @@ class DefaultDownloadManagerTest {
         verify(wagon, wagonManager);
     }
 
+    @Test
+    void shouldDownloadConcurrentlyAndCacheResults() throws Exception {
+        File tempFile = Files.createTempFile("download-source", 
"test").toFile();
+        tempFile.deleteOnExit();
+
+        expect(wagonManager.getWagon("file")).andReturn(wagon).anyTimes();
+        
expect(wagonManager.getAuthenticationInfo(anyString())).andReturn(null).anyTimes();
+        expect(wagonManager.getProxy(anyString())).andReturn(null).anyTimes();
+        wagon.connect(anyObject(Repository.class), 
anyObject(AuthenticationInfo.class), anyObject(ProxyInfo.class));
+        expectLastCall().anyTimes();
+        wagon.get(anyString(), anyObject(File.class));
+        expectLastCall().anyTimes();
+        wagon.disconnect();
+        expectLastCall().anyTimes();
+
+        replay(wagon, wagonManager);
+
+        DefaultDownloadManager mgr = new DefaultDownloadManager(wagonManager);
+
+        ExecutorService executor = Executors.newFixedThreadPool(4);
+        List<Future<?>> futures = new ArrayList<>();
+        for (int i = 0; i < 4; i++) {
+            futures.add(executor.submit(() -> {
+                try {
+                    mgr.download(tempFile.toURI().toASCIIString(), new 
DefaultMessageHolder());
+                } catch (DownloadFailedException e) {
+                    throw new RuntimeException(e);
+                }
+            }));
+        }
+
+        for (Future<?> future : futures) {
+            future.get();
+        }
+
+        executor.shutdown();
+        verify(wagon, wagonManager);
+    }
+
     private void setupDefaultMockConfiguration() {
         assertDoesNotThrow(
                 () -> {

Reply via email to