gnodet commented on code in PR #2108:
URL: https://github.com/apache/maven-resolver/pull/2108#discussion_r3908432905


##########
maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultArtifactResolver.java:
##########
@@ -203,20 +203,18 @@ public List<ArtifactResult> resolveArtifacts(
             throws ArtifactResolutionException {
         requireNonNull(session, "session cannot be null");
         requireNonNull(requests, "requests cannot be null");
-        try (SyncContext shared = syncContextFactory.newInstance(session, 
true);
-                SyncContext exclusive = 
syncContextFactory.newInstance(session, false)) {
-            Collection<Artifact> artifacts = new ArrayList<>(requests.size());
-            SystemDependencyScope systemDependencyScope = 
session.getSystemDependencyScope();
-            for (ArtifactRequest request : requests) {
-                if (systemDependencyScope != null
-                        && 
systemDependencyScope.getSystemPath(request.getArtifact()) != null) {
-                    continue;
-                }
-                artifacts.add(request.getArtifact());
+        SyncContext shared = syncContextFactory.newInstance(session, true);
+        SyncContext exclusive = syncContextFactory.newInstance(session, false);

Review Comment:
   **Resource leak (HIGH):** Removing try-with-resources introduces a resource 
leak. If `syncContextFactory.newInstance(session, false)` on the next line 
throws, the `shared` SyncContext created here is never closed — the `resolve()` 
method's finally block only runs if `resolve()` is actually entered.
   
   The original try-with-resources guaranteed both contexts were closed 
regardless of where an exception occurred. Since `SyncContext` wraps named 
locks, a leaked context could hold a lock open.
   
   One approach:
   ```java
   SyncContext shared = syncContextFactory.newInstance(session, true);
   SyncContext exclusive;
   try {
       exclusive = syncContextFactory.newInstance(session, false);
   } catch (RuntimeException | Error e) {
       shared.close();
       throw e;
   }
   ```
   Or alternatively, keep try-with-resources for the creation but move the 
close logic into the `resolve()` method's finally block using a pattern that 
handles both the try-with-resources close and the explicit close.



##########
maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultMetadataResolver.java:
##########
@@ -139,15 +139,14 @@ public List<MetadataResult> resolveMetadata(
             RepositorySystemSession session, Collection<? extends 
MetadataRequest> requests) {
         requireNonNull(session, "session cannot be null");
         requireNonNull(requests, "requests cannot be null");
-        try (SyncContext shared = syncContextFactory.newInstance(session, 
true);
-                SyncContext exclusive = 
syncContextFactory.newInstance(session, false)) {
-            Collection<Metadata> metadata = new ArrayList<>(requests.size());
-            for (MetadataRequest request : requests) {
-                metadata.add(request.getMetadata());
-            }
-
-            return resolve(shared, exclusive, metadata, session, requests);
+        SyncContext shared = syncContextFactory.newInstance(session, true);
+        SyncContext exclusive = syncContextFactory.newInstance(session, false);

Review Comment:
   **Resource leak (HIGH):** Same pattern as `DefaultArtifactResolver` — if 
`newInstance(session, false)` throws, the `shared` context from the previous 
line is never closed. The `resolve()` method's finally block only runs if 
`resolve()` is entered.



##########
maven-resolver-util/src/main/java/org/eclipse/aether/util/listener/ChainedRepositoryListener.java:
##########
@@ -110,12 +110,14 @@ public void remove(RepositoryListener listener) {
         }
     }
 

Review Comment:
   **JUL vs SLF4J (LOW):** This uses `java.util.logging.Logger` while the 
project's `maven-resolver-impl` module uses SLF4J exclusively. The choice is 
understandable since `maven-resolver-util` doesn't depend on SLF4J, but these 
WARNING messages will not appear in SLF4J-configured logging setups unless a 
JUL-to-SLF4J bridge is installed.
   
   Also, several fully-qualified class names are used inline 
(`java.util.logging.Logger`, `java.util.logging.Level.WARNING`) instead of 
proper import statements — same applies in `ChainedTransferListener.java` and 
the test files. Using imports would be more consistent with the rest of the 
codebase.



##########
maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultArtifactResolverTest.java:
##########
@@ -1083,4 +1083,39 @@ public void add(RepositorySystemSession session, 
LocalMetadataRegistration reque
         // message should contain present=true, available=false, filter message
         assertTrue(ex.getMessage().contains("gid:aid:ext:ver (present, but 
unavailable): REFUSED"));
     }
+
+    @Test
+    void testSyncContextIsClosedExactlyOnce() throws Exception {
+        java.util.concurrent.atomic.AtomicInteger closeCount = new 
java.util.concurrent.atomic.AtomicInteger(0);
+
+        org.eclipse.aether.SyncContext countingSyncContext = new 
org.eclipse.aether.SyncContext() {
+            @Override
+            public void acquire(
+                    Collection<? extends Artifact> artifacts,
+                    Collection<? extends org.eclipse.aether.metadata.Metadata> 
metadatas) {}
+
+            @Override
+            public void close() {
+                closeCount.incrementAndGet();

Review Comment:
   **Misleading test (MEDIUM):** The factory `(s, shared) -> 
countingSyncContext` ignores its parameters and always returns the same 
instance. So `shared` and `exclusive` inside the resolver both point to the 
same object.
   
   The comment on line 1119 says "2 instances were created" but only 1 exists — 
it is closed twice. If the code had a bug where it closed one context twice and 
the other never, this test would not catch it.
   
   Consider using two separate instances with separate counters:
   ```java
   AtomicInteger sharedCloseCount = new AtomicInteger();
   AtomicInteger exclusiveCloseCount = new AtomicInteger();
   // ... factory returns distinct instances based on the `shared` parameter
   ```



##########
maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultArtifactResolver.java:
##########
@@ -203,20 +203,18 @@ public List<ArtifactResult> resolveArtifacts(
             throws ArtifactResolutionException {
         requireNonNull(session, "session cannot be null");
         requireNonNull(requests, "requests cannot be null");
-        try (SyncContext shared = syncContextFactory.newInstance(session, 
true);
-                SyncContext exclusive = 
syncContextFactory.newInstance(session, false)) {
-            Collection<Artifact> artifacts = new ArrayList<>(requests.size());
-            SystemDependencyScope systemDependencyScope = 
session.getSystemDependencyScope();
-            for (ArtifactRequest request : requests) {
-                if (systemDependencyScope != null
-                        && 
systemDependencyScope.getSystemPath(request.getArtifact()) != null) {
-                    continue;
-                }
-                artifacts.add(request.getArtifact());
+        SyncContext shared = syncContextFactory.newInstance(session, true);
+        SyncContext exclusive = syncContextFactory.newInstance(session, false);

Review Comment:
   **Resource leak (HIGH):** Removing try-with-resources introduces a resource 
leak. If `syncContextFactory.newInstance(session, false)` on the next line 
throws, the `shared` SyncContext created here is never closed — the `resolve()` 
method's finally block only runs if `resolve()` is actually entered.
   
   The original try-with-resources guaranteed both contexts were closed 
regardless of where an exception occurred. Since `SyncContext` wraps named 
locks, a leaked context could hold a lock open.
   
   One approach:
   ```java
   SyncContext shared = syncContextFactory.newInstance(session, true);
   SyncContext exclusive;
   try {
       exclusive = syncContextFactory.newInstance(session, false);
   } catch (RuntimeException | Error e) {
       shared.close();
       throw e;
   }
   ```
   Or alternatively, keep try-with-resources for the creation but move the 
close logic into the `resolve()` method's finally block using a pattern that 
handles both the try-with-resources close and the explicit close.



##########
maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultArtifactResolverTest.java:
##########
@@ -1083,4 +1083,39 @@ public void add(RepositorySystemSession session, 
LocalMetadataRegistration reque
         // message should contain present=true, available=false, filter message
         assertTrue(ex.getMessage().contains("gid:aid:ext:ver (present, but 
unavailable): REFUSED"));
     }
+
+    @Test
+    void testSyncContextIsClosedExactlyOnce() throws Exception {
+        java.util.concurrent.atomic.AtomicInteger closeCount = new 
java.util.concurrent.atomic.AtomicInteger(0);
+
+        org.eclipse.aether.SyncContext countingSyncContext = new 
org.eclipse.aether.SyncContext() {
+            @Override
+            public void acquire(
+                    Collection<? extends Artifact> artifacts,
+                    Collection<? extends org.eclipse.aether.metadata.Metadata> 
metadatas) {}
+
+            @Override
+            public void close() {
+                closeCount.incrementAndGet();

Review Comment:
   **Misleading test (MEDIUM):** The factory `(s, shared) -> 
countingSyncContext` ignores its parameters and always returns the same 
instance. So `shared` and `exclusive` inside the resolver both point to the 
same object.
   
   The comment on line 1119 says "2 instances were created" but only 1 exists — 
it is closed twice. If the code had a bug where it closed one context twice and 
the other never, this test would not catch it.
   
   Consider using two separate instances with separate counters:
   ```java
   AtomicInteger sharedCloseCount = new AtomicInteger();
   AtomicInteger exclusiveCloseCount = new AtomicInteger();
   // ... factory returns distinct instances based on the `shared` parameter
   ```



##########
maven-resolver-util/src/main/java/org/eclipse/aether/util/listener/ChainedRepositoryListener.java:
##########
@@ -110,12 +110,14 @@ public void remove(RepositoryListener listener) {
         }
     }
 

Review Comment:
   **JUL vs SLF4J (LOW):** This uses `java.util.logging.Logger` while the 
project's `maven-resolver-impl` module uses SLF4J exclusively. The choice is 
understandable since `maven-resolver-util` doesn't depend on SLF4J, but these 
WARNING messages will not appear in SLF4J-configured logging setups unless a 
JUL-to-SLF4J bridge is installed.
   
   Also, several fully-qualified class names are used inline 
(`java.util.logging.Logger`, `java.util.logging.Level.WARNING`) instead of 
proper import statements — same applies in `ChainedTransferListener.java` and 
the test files. Using imports would be more consistent with the rest of the 
codebase.



##########
maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultMetadataResolver.java:
##########
@@ -139,15 +139,14 @@ public List<MetadataResult> resolveMetadata(
             RepositorySystemSession session, Collection<? extends 
MetadataRequest> requests) {
         requireNonNull(session, "session cannot be null");
         requireNonNull(requests, "requests cannot be null");
-        try (SyncContext shared = syncContextFactory.newInstance(session, 
true);
-                SyncContext exclusive = 
syncContextFactory.newInstance(session, false)) {
-            Collection<Metadata> metadata = new ArrayList<>(requests.size());
-            for (MetadataRequest request : requests) {
-                metadata.add(request.getMetadata());
-            }
-
-            return resolve(shared, exclusive, metadata, session, requests);
+        SyncContext shared = syncContextFactory.newInstance(session, true);
+        SyncContext exclusive = syncContextFactory.newInstance(session, false);

Review Comment:
   **Resource leak (HIGH):** Same pattern as `DefaultArtifactResolver` — if 
`newInstance(session, false)` throws, the `shared` context from the previous 
line is never closed. The `resolve()` method's finally block only runs if 
`resolve()` is entered.



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