gnodet commented on issue #13519:
URL: https://github.com/apache/maven/issues/13519#issuecomment-6170928606
## Cache Invalidation Design Analysis
### Context
Both **m2e** and **mvnd** independently override the same Maven caches to
add invalidation, using different strategies. A unified SPI should let both
drop their overrides.
### How m2e invalidates caches — imperative, project-scoped
m2e defines an `IManagedCache` interface with `removeProject(File pom,
ArtifactKey, boolean forceDependencyUpdate)`. Five cache classes extend Maven's
defaults and embed a `ProjectCachePlunger<Key>` that maintains a bidirectional
index (`pom ↔ cacheKeys`). When the IDE detects a workspace change,
`ProjectRegistryManager.flushCaches()` calls `removeProject()` on each cache —
removing entries, disposing ClassRealms, and cascading to affected projects
when `forceDependencyUpdate` is set.
- **Trigger**: IDE workspace events (pom.xml change, dependency update)
- **Granularity**: Per-project, with cascade
- **5 overrides**: `PluginRealmCache`, `ExtensionRealmCache`,
`ProjectRealmCache`, `PluginArtifactsCache`, `MavenMetadataCache`
### How mvnd invalidates caches — file-system-driven, automatic
mvnd introduces `CacheRecord` (entries expose `getDependencyPaths()` +
`invalidate()` callback), `Cache<K, V>` (generic cache), and `CacheFactory`
(pluggable invalidation strategy). Two strategies exist:
- **`TimestampCacheFactory`** (Linux/macOS): On `get()`, compares each
dependency path's `lastModifiedTime` + `fileKey` against the snapshot at
`put()` time. Changed → invalidate.
- **`WatchServiceCacheFactory`** (Windows): Uses
`java.nio.file.WatchService` to watch dependency path parents. On `get()`,
polls for events.
Additionally, `InvalidatingRealmCacheEventSpy` fires at build-end to evict
entries whose JARs are reactor artifacts (preventing cross-build poisoning).
- **Trigger**: Automatic file-system detection (lazy, on `get()`) +
build-boundary eviction
- **Granularity**: Per-file (dependency paths)
- **5 overrides**: `PluginRealmCache`, `ExtensionRealmCache`,
`PluginArtifactsCache`, `ProjectArtifactsCache`, `PluginDescriptorCache`
### Common ground
Both override the **same Maven caches**, both need to:
1. Track what a cache entry depends on (files for mvnd, projects for m2e)
2. Invalidate/dispose entries (especially ClassRealm disposal)
3. Hook into cache lifecycle (entry creation, removal)
---
## Design Options
### Option A: Per-cache `invalidate()` methods (PR #13094's current approach)
Add `invalidate(Artifact)` to each cache interface individually.
**Pros**: Minimal change, backward compatible, incremental.
**Cons**: Doesn't unify — m2e and mvnd still override all 5 caches
independently. Doesn't address dependency tracking. Five separate APIs instead
of one.
### Option B: `CacheRecord` + `CacheFactory` SPI (inspired by mvnd)
Move mvnd's cache abstraction into Maven core:
```java
// Cache entry contract
public interface CacheRecord {
Set<Path> getDependencyPaths(); // what files this entry depends on
void invalidate(); // cleanup callback (dispose realm,
etc.)
}
// Pluggable cache storage with invalidation strategy
public interface CacheFactory {
<K, V extends CacheRecord> Cache<K, V> createCache();
}
```
Maven's default caches use `CacheFactory` for internal storage. Embedders
provide their own:
- **CLI**: `DefaultCacheFactory` — no invalidation (single-shot build)
- **mvnd**: `TimestampCacheFactory` / `WatchServiceCacheFactory` —
file-system-driven
- **m2e**: `WorkspaceCacheFactory` — imperative invalidation from IDE events
**Pros**: One SPI covers all caches. Embedders provide ONE factory, not 5
overrides. Proven pattern from mvnd. Dependency tracking is built-in.
**Cons**: Requires refactoring all cache implementations to use the
abstraction. More upfront work.
### Option C: `CacheManager` service with unified eviction
```java
public interface CacheManager {
void invalidate(Artifact artifact);
void invalidateProject(Path pom);
void flush();
void addListener(CacheEventListener listener);
}
```
**Pros**: Single entry point for all invalidation. Easy for embedders.
**Cons**: Coarse-grained — doesn't address staleness *detection*. Still
needs `CacheRecord` underneath for realm disposal.
### Option D: Session-scoped caches
Make caches session-scoped instead of singleton.
**Pros**: No invalidation API needed.
**Cons**: Kills performance for mvnd (caching across builds is the point).
Bad for m2e too. Non-starter as sole approach.
---
## Recommendation: Option B + C
1. **Adopt `CacheRecord` + `CacheFactory`** from mvnd into Maven core —
provides dependency-tracking and pluggable invalidation
2. **Add a `CacheManager` facade** for embedders that want simple imperative
invalidation (m2e's `flushCaches()` → `cacheManager.invalidateProject(pom)`)
3. **mvnd drops its 5 cache overrides** and provides a `CacheFactory`
implementation
4. **m2e drops its 5 cache overrides** and uses `CacheManager` or provides a
`CacheFactory`
5. **CLI** uses the default no-invalidation factory
This unifies three solutions (CLI's "no invalidation", mvnd's
"file-watching", m2e's "imperative flush") under one SPI.
_Claude Code on behalf of Guillaume Nodet_
--
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]