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]