Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
114 commits
Select commit Hold shift + click to select a range
f243930
feat: Clean cache command
d3xter666 May 22, 2026
12ff67a
refactor: Use single place for DB manipulation
d3xter666 May 29, 2026
c8fc9f7
refactor: Simplify cache clean
d3xter666 May 29, 2026
8aac58e
refactor: Position correctly the CacheCleanup
d3xter666 May 29, 2026
a709e3b
refactor: Add confirmation dialog for the cache clean command
d3xter666 May 29, 2026
33ea6a5
refactor: Rename cacheVersionDir
d3xter666 May 29, 2026
d3c7601
refactor: Restore location of CacheCleanup
d3xter666 May 29, 2026
c0a8609
fix: Clean only current cache version
d3xter666 May 29, 2026
3f5bb7e
refactor: Simplify CacheCleanup
d3xter666 May 29, 2026
5290ae7
test: Improve coverage
d3xter666 May 29, 2026
deebaed
refactor: CLI package orchestrates cache cleanup
d3xter666 May 29, 2026
9ee230e
refactor: Add skip confirmation option
d3xter666 Jun 1, 2026
1997aff
fix: Windows paths for tests
d3xter666 Jun 1, 2026
bc4f3c0
refactor: Use yesno package for CLI confirmation
d3xter666 Jun 2, 2026
9b620ad
refactor: Simplify cleanup meta structure
d3xter666 Jun 2, 2026
93022ae
fix: Add guard to not accidently create a new DB
d3xter666 Jun 2, 2026
d6e4457
refactor: Reuse meta from installers in cache cleanup
d3xter666 Jun 2, 2026
92604c2
refactor: Cleanup details
d3xter666 Jun 2, 2026
eaaf573
fix: Respect datadir config
d3xter666 Jun 2, 2026
672a9dd
docs: Update cache clean --help CLI information
d3xter666 Jun 2, 2026
6d12add
refactor: Cleanup confirmation revised
d3xter666 Jun 9, 2026
b5cf051
refactor: Revise actual cleanup UX
d3xter666 Jun 9, 2026
22fe390
fix: Docs generation failure
d3xter666 Jun 9, 2026
784b031
fix: Respect cleanup locking
d3xter666 Jun 9, 2026
ec40541
refactor: Redundant cleanup summary
d3xter666 Jun 9, 2026
589edd7
fix: Cache locking race condition
d3xter666 Jun 10, 2026
222ee57
test: Fix race condition expectations
d3xter666 Jun 10, 2026
daf4304
refactor: Cleanups
d3xter666 Jun 10, 2026
979c4e8
refactor: Naming of locks
d3xter666 Jun 10, 2026
e110354
docs: Update documentation to respect recent changes
d3xter666 Jun 11, 2026
b08c11b
revert: Redundant maven installer locks
d3xter666 Jun 12, 2026
11e545d
refactor: Use pacote's internals for its own cleanup
d3xter666 Jun 12, 2026
9b95c68
revert: NPM Install sync. It's now redundant
d3xter666 Jun 12, 2026
fd6435c
docs: No mention of "incremental build"
d3xter666 Jun 12, 2026
9d5f564
refactor: Avoid hardcoded values
d3xter666 Jun 12, 2026
f0e84be
feat: Add cleanup lock for "ui5 serve"
d3xter666 Jun 15, 2026
cec5542
refactor: Move server lock into the server package and reuse in cli
d3xter666 Jun 17, 2026
571d7b3
refactor: Data dir is resolved centrally via util
d3xter666 Jun 18, 2026
03eb579
fix: Remove redundant lock cleanups after server close
d3xter666 Jun 18, 2026
4369dcb
refactor: Revise locks destinations and messages
d3xter666 Jun 19, 2026
bb4c677
refactor: Lock full build, so that it does not run into timing problems
d3xter666 Jun 19, 2026
95b4cc1
refactor: Avoid cache cleanup + build race conditions
d3xter666 Jun 19, 2026
d5b6b22
refactor: Cleanups
d3xter666 Jun 19, 2026
cefd055
refactor: Use the common test/tmp dir
d3xter666 Jun 22, 2026
6e6daa8
refactor: Move server lock to the ProjectGraph.serve
d3xter666 Jun 22, 2026
3dc00e3
refactor: Rename getDefaultUi5DataDir to resolveUi5DataDir
d3xter666 Jun 22, 2026
a650856
refactor: Do not pass dir (cwd) as argument to resolveUi5DataDir
d3xter666 Jun 22, 2026
d15d340
refactor: Cleanup of redundant lock watchers
d3xter666 Jun 22, 2026
28e5cb0
refactor: Cleanup of ProjectGraph
d3xter666 Jun 22, 2026
5d55428
refactor: Acquire locks in the project builder & server
d3xter666 Jun 22, 2026
82159c8
test: Fix stubs
d3xter666 Jun 22, 2026
88fb750
test: Update lock util tests
d3xter666 Jun 22, 2026
c356b1c
test: Server integration
d3xter666 Jun 22, 2026
c17b81d
refactor: DB size optimization
d3xter666 Jun 22, 2026
0bc92c5
refactor: Split cache command into logical chunks
d3xter666 Jun 23, 2026
3249046
refactor: Consolidate common logic for CacheManager
d3xter666 Jun 23, 2026
487d772
refactor: Consolidate repetative code
d3xter666 Jun 23, 2026
559b526
refactor: Lock cleanups
d3xter666 Jun 23, 2026
7ab4c59
test: Cleanup of repetative cases
d3xter666 Jun 23, 2026
b732625
refactor: Provide dataDir to the buildServer
d3xter666 Jun 23, 2026
52277d0
refactor: Enhance locks
d3xter666 Jun 23, 2026
0fb4d31
refactor: Cleanup locks for wrong places
d3xter666 Jun 24, 2026
4202cda
refactor: Locks for ProjectGraph
d3xter666 Jun 25, 2026
a022f2d
test: Fix unstable tests
d3xter666 Jun 25, 2026
8c4ceba
docs: JSDoc updates
d3xter666 Jun 26, 2026
ccd1b77
fix: Separate async & sync dir creation
d3xter666 Jun 26, 2026
83a26cc
refactor: Add ui5DataDir as required in ProjectGraph's constructor
d3xter666 Jun 26, 2026
989a701
refactor: Give a name for the graph's internal locking method
d3xter666 Jun 26, 2026
1b55875
refactor: Move locks to the specific lock points
d3xter666 Jun 26, 2026
cac6e7e
feat: ProjectGraph's lock now has a tolerance to wat for the cache cl…
d3xter666 Jun 26, 2026
94e0e41
refactor: Cache clean now first renames the framework dir and then wi…
d3xter666 Jun 28, 2026
de4f67b
docs: Explain why we lock the graph in enrichProjectGraph
d3xter666 Jun 29, 2026
42e647d
docs: Correctly address params in JSDoc
d3xter666 Jun 29, 2026
fa9e7ab
docs: Refactor _preventCacheClean JSDoc block
d3xter666 Jun 29, 2026
929ac6c
refactor: Explicit destruction of the ProjectGraph where needed
d3xter666 Jun 29, 2026
7f3e2cc
fix: Protect _preventCacheClean from overwriting the locks
d3xter666 Jun 29, 2026
15f0a51
docs: Update LOCK_REFRESH_INTERVAL_MS description
d3xter666 Jun 29, 2026
bf9b854
refactor: Try to explain better orphaned + framework dirs cleanup order
d3xter666 Jun 29, 2026
964a885
refactor: Optimize lock.js
d3xter666 Jun 29, 2026
9597031
refactor: Fix wrong case for isCacheDbAvailable
d3xter666 Jun 29, 2026
06ecfeb
refactor: Resolve internally getLockDir in hasActiveLocks & remove r…
d3xter666 Jun 29, 2026
e8bccdc
refactor: Promisify per module
d3xter666 Jun 29, 2026
f846ddf
refactor: Consolidate *Ui5DataDir implementation
d3xter666 Jun 29, 2026
569cd04
test: Fix failing CI tests because of a missing stub
d3xter666 Jun 29, 2026
3ccf572
refactor: Lock on cache clean command
d3xter666 Jun 29, 2026
0281bda
refactor: Always release the cache clean lock
d3xter666 Jun 29, 2026
1c18d65
docs: Update comments
d3xter666 Jun 29, 2026
3b9b38f
refactor: Reorganize output information
d3xter666 Jun 29, 2026
26d212f
fix: Adjust comments & wrongly called method
d3xter666 Jun 29, 2026
e60788a
fix: Await synchronization callback
d3xter666 Jun 29, 2026
7cc216c
revert: Restore Troubleshooting guide after merge conflict
d3xter666 Jun 29, 2026
a9f169d
refactor: No braceless returns
d3xter666 Jun 29, 2026
f273c3c
refactor: Prevent clean cache only after sealed
d3xter666 Jun 29, 2026
5e87c0e
revert: Wiped out documentation
d3xter666 Jun 29, 2026
2f8c7c2
docs: Update Troubleshooting to reflect orphaned branches
d3xter666 Jun 29, 2026
5834b55
docs: Update CLI information to reflect orphaned branches
d3xter666 Jun 29, 2026
571c275
docs: Correct JSDoc link
d3xter666 Jun 30, 2026
cd95dc5
docs: Make ui5 cache clean command info section clearer
d3xter666 Jun 30, 2026
98955ba
docs: Update internal/documentation/docs/pages/Troubleshooting.md
d3xter666 Jun 30, 2026
0cf461e
docs: Update internal/documentation/docs/pages/Troubleshooting.md
d3xter666 Jun 30, 2026
ff79e67
docs: Update internal/documentation/docs/pages/Troubleshooting.md
d3xter666 Jun 30, 2026
517892f
docs: Update internal/documentation/docs/pages/Troubleshooting.md
d3xter666 Jun 30, 2026
9788284
docs: Update packages/cli/lib/cli/commands/cache.js
d3xter666 Jun 30, 2026
b17bbec
docs: Update packages/cli/lib/cli/commands/cache.js
d3xter666 Jun 30, 2026
050d2f1
docs: Update packages/cli/lib/cli/commands/cache.js
d3xter666 Jun 30, 2026
3545ca1
docs: Update packages/cli/lib/cli/commands/cache.js
d3xter666 Jun 30, 2026
2cb6316
docs: Update packages/cli/lib/cli/commands/cache.js
d3xter666 Jun 30, 2026
c5d515a
docs: Update internal/documentation/docs/pages/Troubleshooting.md
d3xter666 Jun 30, 2026
b1c2796
docs: Update internal/documentation/docs/pages/Troubleshooting.md
d3xter666 Jul 14, 2026
d9845c9
docs: Update internal/documentation/docs/pages/Troubleshooting.md
d3xter666 Jul 14, 2026
5fcad57
docs: Add Lessons learned summary document
d3xter666 Jul 14, 2026
0e26d04
test: Add missing destroy stub to BuildServer graph fixtures
d3xter666 Jul 14, 2026
8f7dd06
docs: Locks document
d3xter666 Jul 16, 2026
9b00241
docs: Update summary to reference the lock doc
d3xter666 Jul 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions CACHE_CLEAN_SUMAMRY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Lessons Learned — `ui5 cache clean`

This document captures what was learned building the "Clean cache command" so each topic can be broken down and decided individually later.

What started as a simple `rm -rf ~/.ui5/framework` replacement grew into a cross-package concurrency, lifecycle, and refactoring problem. The scope creep is the central lesson — every "simple deletion" of a shared resource pulls in coordination with every process that reads or writes it.

---

## 1. General locking and locks concept

The complete locking design is documented in [doc-process-coordination-locks.md](./doc-process-coordination-locks.md). That document covers: why file-based locks are needed (separate OS processes), why the `lockfile` package was chosen, the two lock types (graph lock and cleanup lock), their lifecycles, staleness and mtime refresh mechanics, sync vs async acquisition trade-offs, release safety rules, and open questions around `ProjectGraph` ownership and v4 backward compatibility.

**What we struggled with and learned:**

Locking turned out to be the hardest part of this feature. We kept discovering new unprotected windows — first only downloads, then installs, then the whole graph lifetime. The core lesson was a shift in framing: **"where to lock" is really "who holds the resource, and for how long"** — not "which function writes or deletes it." Once we identified `ProjectGraph` as the right lock owner (it holds live references for its entire lifetime), the rest followed.

The other recurring challenge was getting release right. Every early-exit path and every async callback inside a protected section is a potential deadlock. We introduced a regression during the PR by dropping a single `await` keyword — `return callback()` instead of `return await callback()` — which released the lock before the async work finished. This kind of bug is silent and hard to spot in review.

The open questions that still need team decisions are in [Unresolved Questions](./doc-process-coordination-locks.md#unresolved-questions-and-bikeshedding) of the RFC.
---

## 2. Performance & UX

### 2.1 What information to display to the end user

**What we did — and reversed twice:**
- v1: show **byte size** of the cache. For big caches it might take significant amount of time to compute the size. Native OS variants are also slow and for different platforms there are different tools.
- v2: show **file count** — still slow (traversal of ~2M files) and "2 million files" is meaningless to a user.
- v3 (final): show **"X versions of Y libraries"** — a shallow directory traversal (project → library → version), fast and meaningful.

We also added: a pre-confirmation summary, a `--yes` flag and a post-clean success line.

**Lesson:** "How much will this free?" is expensive to answer accurately and not even what the user cares about. The useful signal was *what* is being removed (libraries/versions), not *how many bytes*. We spent significant effort computing a number we ultimately threw away. **Decide the UX metric before implementing the measurement.**

### 2.2 What, where, and how to purge data

Three distinct storage layers each needed a different purge strategy:

- **Memory** — `cacache`'s in-process memoization. Cleared via `clearMemoized()` (global, synchronous) before the rename.
- **DB** — the SQLite build cache (`buildCache/v0_7/cache.db`). Cleaned via `clearAllRecords()` inside a transaction. Crucially, we made it **version-aware**: only the current `CACHE_VERSION` is purged, because we cannot know the schema of a future cache version ("wipe everything" is not robust across schema versions). The DB *file* is intentionally kept; only records are deleted.
- **File system** — the `framework/` directory. We adopted an **atomic-rename-then-delete** strategy: rename `framework/` → `.framework_to_delete_<hash>/` (one fast syscall, makes the path disappear instantly), release the lock, then delete the staging dir outside the lock. This minimizes lock-hold time and lets a concurrent build see the path as simply absent.

The rename strategy introduced a new concept: **orphaned staging dirs** (a clean interrupted after rename but before delete). We added `cleanAdditional()` to sweep these up independently — they're safe to remove any time, outside the lock.

**Lesson:** Three storage backends = three purge semantics. The atomic rename was the key insight for the file system (short lock, no corruption), but it *created* a new failure mode (orphans) that needed its own cleanup path. Every optimization added a corner. The DB version-awareness is a genuinely important correctness constraint that should survive into any future concept.

---

## 3. Refactoring

### 3.1 DRY — installers: consolidate locking

**What we did:** `AbstractInstaller` (base for npm + maven installers) now imports the shared lock utilities instead of promisifying `lockfile` inline per call. `_synchronize` centralizes: acquire → check-for-cleanup → run callback → release.

**Open item flagged during review:** The `framework/` directory name is still hardcoded in each installer rather than abstracted in `AbstractInstaller`. The clean concern and the install concern both know about `framework/` independently. The proposed direction: abstract the `framework/` location and the locking into `AbstractInstaller`, then have the cache cleaner reuse that knowledge — so there's one source of truth for "where framework files live and how they're locked."

**Lesson:** The installers and the cleaner are two sides of the same resource but evolved in different namespaces (`ui5Framework/` vs the CLI command). They share path + lock knowledge but not code.

### 3.2 Resolution of UI5 data dir

**What we did:** Introduced [`resolveUi5DataDir()`](../packages/project/lib/utils/dataDir.js) as the single resolver (env var → config → `~/.ui5` default, always returns an absolute path). Removed a duplicate `getUi5DataDir` from `packages/cli/lib/framework/utils.js`.

**Still inconsistent (flagged):** Resolution still happens **in-place** in a few spots. The Resolvers/Installers (e.g. `Openui5Resolver`) keep a fallback `ui5DataDir || path.join(os.homedir(), ".ui5")` because they're publicly exported and can be called without a pre-resolved dir. So there isn't *one* resolution path — there's a util plus defensive fallbacks at the public API boundary.

**Lesson:** "One utility" isn't the same as "one resolution path." Public API entry points force defensive fallbacks. A clean concept needs to decide: is `ui5DataDir` resolved once at the outermost boundary and threaded through, or resolved lazily wherever needed? We have a mix, and the mix is the problem. This also introduced backward incompatibilities.

### 3.3 Location of cached resources, locks, etc.

**What we did:** Consolidated all lock files into `~/.ui5/locks/` (previously `~/.ui5/framework/locks/`). As the lock scope grew beyond framework (to build/serve/cleanup), a framework-scoped lock dir no longer made sense.

**Lesson:** The lock directory location is a consequence of lock *scope*. When scope expanded, the location had to move. This is a symptom of the same root issue: scope kept growing, and physical layout followed reactively.

**Open question (RandomByte):** Moving all locks to `~/.ui5/locks/` creates a **compatibility concern** with previous CLI versions that still write framework-installer locks to `~/.ui5/framework/locks/`. If a user runs two CLI versions in parallel — e.g. a project pinned to an older version alongside a global newer version — the old version won't see the new locks and vice versa.

One option is to keep framework-dependency-related locks under `~/.ui5/framework/locks/` and only place new, broader-scope locks (build, serve, cache-clean) under `~/.ui5/locks/`. This would preserve backward compatibility for the installer lock path while still enabling the new coordination locks.

**Analysis of mixed-version scenarios:** The actual cross-version impact is more limited than it first appears. Locks are only meaningful when shared between cooperating processes of the *same* version. The mixed-version scenarios break down as:

- **v5 only or v4 only** — nothing breaks; everything works as expected.
- **v4 and v5 in parallel** — v5's `cache clean` is the only command that uses the new lock dir. v5's locks will not prevent v4 from downloading or installing framework resources (v4 doesn't know about `~/.ui5/locks/`). The only real clash is: running v5's `cache clean` while v4's `serve` or `build` is active. In the worst case, some framework resources are deleted mid-use and the v4 command must be re-run.

**Remaining decision:** Is the v4+v5 concurrent-clean risk acceptable, or does it warrant keeping framework locks at their old path? The risk is bounded (a re-run is sufficient recovery), but it is user-visible.

---

## Summary

Every sub-topic in the comment traces back to one root: **deleting a shared resource forces you to model every reader and writer of that resource, across processes, for their full lifetime.** The complexity wasn't in the deletion — it was in proving nobody else is mid-flight. Restarting the concept should begin from that model (who owns the framework cache, its lock contract, and its lifecycle), and let the command, the installers, and the graph consume that one contract — rather than each discovering the coordination need independently, which is how we got here.
Loading
Loading