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]

Reply via email to