jprinet opened a new issue, #1919:
URL: https://github.com/apache/maven-resolver/issues/1919

   ### Affected version
   
   `1.9.26` / `1.9.27` (`maven-resolver-transport-http`). The same code is 
present on `master`/2.x in `maven-resolver-transport-apache` 
(`ApacheTransporter`), so it appears to affect 2.x as well.
   
   ### Bug description
   
   **Summary**
   
   Since 1.9.26, `HttpTransporter` stores its Apache HttpClient `AuthCache` in 
the **session-level `RepositoryCache`** under a key derived from 
`getClass().getSimpleName()`. That key is **classloader-agnostic**, so when the 
transport classes are loaded in more than one `ClassRealm` in the same build, 
both realms compute the **same** cache key while each has its **own** 
`org.apache.http.*` classes — which happens when an `extensions=true` plugin 
pulls its own (typically different) version of HttpClient into its realm than 
the one Maven core provides. `AuthCache`/`BasicAuthCache` then resolve to 
different `Class` instances per realm, and the read-back cast fails:
   
   ```
   java.lang.ClassCastException: class 
org.apache.http.impl.client.BasicAuthCache
   cannot be cast to class org.apache.http.client.AuthCache
   (... BasicAuthCache is in unnamed module of loader ClassRealm @<a>;
        AuthCache    is in unnamed module of loader ClassRealm @<b>)
       at 
org.eclipse.aether.transport.http.HttpTransporter.<init>(HttpTransporter.java:372)
       at 
org.eclipse.aether.transport.http.HttpTransporterFactory.newInstance(HttpTransporterFactory.java:95)
       ...
       at 
org.eclipse.aether.internal.impl.DefaultMetadataResolver$ResolveTask.run(DefaultMetadataResolver.java:555)
   ```
   
   **Location & cause**
   
   `maven-resolver-transport-http/.../HttpTransporter.java` (1.9.27), lines 
368–378:
   
   ```java
   if (session.getCache() != null) {
       String authCacheKey = getClass().getSimpleName() + "-" + 
repository.getId() + "-"
               + StringDigestUtil.sha1(repository.toString());          // 
classloader-agnostic key
       synchronized (AUTH_CACHE_MUTEX) {
           AuthCache cache = (AuthCache) session.getCache().get(session, 
authCacheKey);  // line 372 — throws
           if (cache == null) {
               cache = new BasicAuthCache();
               session.getCache().put(session, authCacheKey, cache);    // 
stored into the per-session shared cache
           }
           this.authCache = cache;
       }
   }
   ```
   
   `session.getCache()` is a single per-session `RepositoryCache` visible to 
every `ClassRealm`. Because the key only contains `"HttpTransporter"` + repo id 
+ repo hash (no classloader identity), a transporter loaded by realm A and one 
loaded by realm B share the same entry — but their `AuthCache`/`BasicAuthCache` 
are different `Class` objects, so the cast at line 372 throws. 
`AUTH_CACHE_MUTEX` only serializes access; it does not isolate by realm.
   
   This was introduced by #1790 ("Drastically simplify auth caching", GH-1768), 
which moved the `AuthCache` from per-transporter state into the shared session 
cache. It is the same family as #1296 / MRESOLVER-628 ("session 
prioritized-component caching → cross-realm `ClassCastException`", fixed in 
2.0.4) — but that fix covered component factories, not this `AuthCache` 
instance. #1902 edits this block for thread-safety but keeps the cast, so it 
does not address this.
   
   **When it occurs**
   
   It is a probabilistic, classloader-topology-dependent failure. It requires:
   - the transport loaded in **two realms** — classically an `extensions=true` 
plugin that bundles its own `maven-resolver-transport-http` + `httpclient` 
alongside the copies Maven core provides, and
   - cold resolution that actually builds an HTTP transporter 
(`DefaultMetadataResolver` thread pool fetching remote metadata).
   
   It typically presents as an **intermittent** "passes on rerun / warm cache" 
CI failure.
   
   **Reproduction**
   
   A Maven 3.9.x build using `spring-cloud-contract-maven-plugin` >= 5.0.3 
(declared `<extensions>true</extensions>` per its docs; it bundles 
`maven-resolver-transport-http` 1.9.27 + `httpclient` 4.5.14 at compile scope) 
on a **cold** local repository with a non-trivial dependency graph:
   
   ```
   mvn -pl <module-using-the-plugin> validate    # cold ~/.m2 -> BasicAuthCache 
cannot be cast to AuthCache
   ```
   
   5.0.2 (which bundles resolver 1.9.25) does not reproduce, because 1.9.25's 
`HttpTransporter` does not use the session cache for the auth cache.
   
   **Proposed fix**
   
   Make the cache key classloader-aware so each realm keeps its own entry 
(preserving the intended per-session reuse within a realm):
   
   ```diff
   -            String authCacheKey = getClass().getSimpleName() + "-" + 
repository.getId() + "-"
   -                    + StringDigestUtil.sha1(repository.toString());
   +            String authCacheKey = getClass().getName() + "@"
   +                    + 
Integer.toHexString(System.identityHashCode(getClass().getClassLoader())) + "-"
   +                    + repository.getId() + "-"
   +                    + StringDigestUtil.sha1(repository.toString());
   ```
   
   An equivalent alternative is to keep auth-cache state per-transporter (as in 
<=1.9.25) rather than in the session cache. The same change applies to 
`master`/2.x `ApacheTransporter`.
   


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