gnodet opened a new pull request, #328:
URL: https://github.com/apache/maven-clean-plugin/pull/328
## Summary
Refactors the fast clean (`-Dmaven.clean.fast=true`) `BackgroundCleaner` to
fix the MCLEAN-102 performance regression and three regressions introduced by
the per-module refactoring in PR #286.
### What this fixes
| # | Bug | Root Cause | Fix |
|---|------|------------|-----|
| 1 | **MCLEAN-102** — fast mode 50% slower on 12-core Windows |
`System.gc()` stop-the-world per file + per-file retry sleeps from background
thread | Batch retry: walk once without retry, sleep once (250ms), retry all
failures |
| 2 | **Thread proliferation** — 500 threads in 500-module reactor |
Per-module `BackgroundCleaner`, each with its own `ExecutorService` |
Session-scoped instance via `SessionData.computeIfAbsent()` |
| 3 | **500 session listeners** | Per-module
`session.registerListener(this)` | Single listener registered in constructor |
| 4 | **Leftover cleanup lost** | `init()` scan removed in PR #286 when
singleton was dropped | Restored: `scanForLeftovers()` queues leftover dirs for
background deletion |
### Changes
- **`BackgroundCleaner.java`** (major rewrite):
- `SessionData.Key<BackgroundCleaner>` + `getOrCreate()` factory — one
instance, one thread, one listener per session
- `deleteInBackground()` replaces `deleteSilently()` — uses
`SimpleFileVisitor` with batch retry instead of creating a `Cleaner` copy that
uses per-file retry with `System.gc()`
- `scanForLeftovers()` — scans fast directory for dirs left by killed
previous builds
- `fastDelete()` is `synchronized` for parallel build safety (`-T`)
- **`Cleaner.java`** (one-line change):
- `setWritable()`: `private static` → `static` (package-private) so
`BackgroundCleaner` can reuse it for force-delete in background
- **`CleanMojo.java`** (one-line change):
- `new BackgroundCleaner(...)` → `BackgroundCleaner.getOrCreate(...)`
### Before vs After (500-module reactor)
| Metric | Before | After |
|--------|--------|-------|
| Background threads | **500** | **1** |
| Session listeners | **500** | **1** |
| `System.gc()` calls (Windows) | thousands | **0** |
| Per-file sleeps (background) | 50–1050ms × N | **0** |
| Batch sleeps | 0 | **1 × 250ms per directory** |
| Leftover cleanup | ❌ | ✅ |
### Design notes
- **Foreground path unchanged**: the traditional `Cleaner.tryDelete()` keeps
`System.gc()` + per-file retry — it runs in the main thread where it can't
cause stop-the-world on other threads, and it may genuinely need to release
JVM-held file handles.
- **Batch retry respects `retryOnError`**: if user sets
`-Dmaven.clean.retryOnError=false`, no retry happens even in background.
- **Remaining failures deferred to next build**: any files that survive the
batch retry will be cleaned up by the leftover scan on the next `fast=true`
build.
## Test plan
- [x] `mvn verify` passes (15 tests, 0 failures)
- [ ] Manual test on Windows with large reactor to verify MCLEAN-102 fix
- [ ] Manual test: kill build mid-clean, verify leftovers cleaned on next run
- [ ] Manual test with `-T 4C` parallel build to verify session-scoped
sharing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
--
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]