diff --git a/docs/03-github-orchestrator/07-advanced-topics/01-caching.mdx b/docs/03-github-orchestrator/07-advanced-topics/01-caching.mdx index 58ba09da..63327a30 100644 --- a/docs/03-github-orchestrator/07-advanced-topics/01-caching.mdx +++ b/docs/03-github-orchestrator/07-advanced-topics/01-caching.mdx @@ -134,6 +134,11 @@ Use `cacheRetentionDays` to automatically remove old cache entries from storage: | `30` | Main and develop branch caches | | `90` | Release branch caches | +When `childWorkspacesEnabled` is set, `cacheRetentionDays` also age-sweeps cached child workspaces +under `childWorkspaceCacheRoot` — a cached child workspace is the same kind of disk-space liability +as a local Library cache entry, so it shares the same retention setting rather than needing a +separate one. + You can vary retention by branch: ```yaml @@ -336,6 +341,31 @@ clear error. Serialise output-emitting steps. If a job needs to emit several outputs from parallel sub-jobs, collect them into a single output-emitting step at the end. +### Retained-workspace lock outliving a failed build + +`maxRetainedWorkspaces` locks are plain marker objects in the storage backend (S3 or rclone remote) +with no TTL or liveness check — a lock is considered held for as long as the marker object exists, +regardless of whether the process that created it is still running. A build that throws before it +reaches its own cleanup step used to leave that marker behind permanently, shrinking the retained +workspace pool by one on every crash until someone deleted the object by hand. + +Release the lock on every exit path, not just the success path — including the one a thrown error +takes. Releasing an already-released (or never-acquired) lock should be a no-op, so the release call +is safe to make unconditionally rather than only after confirming a lock is actually held. + +### Orphaned background cache-save lock from a killed process + +A background (detached, `copy-directory`) cache save writes a PID-stamped lock file before it starts +and removes it on completion. If the process saving the cache is killed mid-copy (OOM, runner +crash, forced job cancellation), the lock file survives it. A lock check that only runs reactively — +triggered by a later save/restore call that happens to target the same cache key — never revisits a +cache key this run doesn't touch, so that lock can outlive the process that created it indefinitely. + +Sweep lock files proactively at the start of a build, across every cache key under the cache root, +not only the one this run is about to use. Treat a lock as stale when the PID it names is no longer +alive (or the file can't be parsed) — a plain existence check on the marker file, without a liveness +check on the process it names, cannot tell a genuinely in-progress save from an orphaned one. + ### Path-filter on the workflow entrypoint to skip docs-only commits Docs-only pushes (markdown changes, README updates) shouldn't trigger Unity CI runs. A diff --git a/docs/03-github-orchestrator/07-advanced-topics/15-large-projects.mdx b/docs/03-github-orchestrator/07-advanced-topics/15-large-projects.mdx index 2940de68..7a46fe05 100644 --- a/docs/03-github-orchestrator/07-advanced-topics/15-large-projects.mdx +++ b/docs/03-github-orchestrator/07-advanced-topics/15-large-projects.mdx @@ -55,18 +55,26 @@ child-workspaces/ Library/ ``` -The orchestrator manages this layout automatically when `retainedWorkspaces: true` is set. Child -workspaces are created on first build and reused on subsequent builds of the same target. Only -changed files from git delta sync are applied to each child. +The orchestrator manages this layout automatically when `childWorkspacesEnabled: true` is set. +Child workspaces are named per build target and cached under `childWorkspaceCacheRoot`. Each is +created on first build and reused on subsequent builds of the same target. Only changed files from +git delta sync are applied to each child. ```yaml - uses: game-ci/unity-builder@v4 with: - retainedWorkspaces: true - workspaceRoot: /mnt/build-storage/my-game + childWorkspacesEnabled: true + childWorkspaceName: ${{ matrix.targetPlatform }} + childWorkspaceCacheRoot: /mnt/build-storage/my-game/workspaces targetPlatform: StandaloneLinux64 ``` +`childWorkspacePreserveGit` (default `true`) keeps `.git` in the cached child workspace so +incremental sync strategies (see [Incremental Sync](#incremental-sync) below) keep working across +builds. `childWorkspaceSeparateLibrary` (default `true`) caches each engine cache folder (e.g. +`Library`) independently from the rest of the workspace, so it restores and saves on its own move +rather than moving with the whole workspace. + ## Move-Centric Caching Traditional caching copies files: archive → upload → download → extract. For a 50 GB Library folder @@ -79,8 +87,9 @@ in milliseconds. ```yaml - uses: game-ci/unity-builder@v4 with: + localCacheEnabled: true localCacheRoot: /mnt/build-storage/cache - cacheStrategy: move + localCacheMode: move-directory ``` The cache lifecycle for a Library folder: @@ -91,14 +100,21 @@ The cache lifecycle for a Library folder: 4. Next build - Library is already warm at cache location This eliminates the archive/upload/download/extract cycle entirely for builds running on retained -storage. Remote cache fallback (S3, GCS, Azure Blob via rclone) is still available for cold runners -that do not have local cache access. - -| Cache Strategy | Library Move Time | Suitable For | -| -------------- | ------------------------ | ------------------------------- | -| `move` | Milliseconds | Retained storage, build farms | -| `rclone` | Minutes (size-dependent) | Remote cache, ephemeral runners | -| `github-cache` | Minutes (10 GB limit) | Small projects only | +storage. Remote cache fallback (S3, GCS, Azure Blob via rclone) is a separate mechanism (see +[Storage](storage)) for cold runners that do not have local cache access. + +| `localCacheMode` | Library Restore/Save Time | Suitable For | +| ----------------- | ---------------------------------- | ----------------------------------------------------- | +| `move-directory` | Milliseconds (same-volume rename) | Retained storage, build farms (default) | +| `copy-directory` | Seconds-minutes (size-dependent) | Cache root on a different volume than the workspace | +| `tar` | Minutes (archive + extract) | Cache needs to be transferred off-box afterward | + +`move-directory` requires the cache root and the workspace to be on the same filesystem volume — a +same-volume rename is what makes the move O(1). A cross-volume rename fails at the OS level, so +point `localCacheRoot` at a path on the same drive as the workspace, or use `copy-directory` when +that cannot be guaranteed. Fallback-key restores (see +[Fallback Keys](build-services#fallback-keys)) are always copied rather than moved, regardless of +`localCacheMode`, so the original cache entry is left intact for other builds. ## Custom LFS Transfer Agents @@ -186,11 +202,11 @@ git-delta so that code changes and asset changes are both handled incrementally. - uses: game-ci/unity-builder@v4 with: syncStrategy: git-delta - retainedWorkspaces: true + childWorkspacesEnabled: true ``` -Together, retained workspaces and git-delta sync deliver the minimal possible import work on every -build: Unity sees only the files that actually changed. +Together, retained child workspaces and git-delta sync deliver the minimal possible import work on +every build: Unity sees only the files that actually changed. ## Build Performance Tips @@ -214,13 +230,20 @@ Library folders. under predictable paths (`Assets/Platforms/Linux/`, `Assets/Platforms/WebGL/`). This makes `lfsStoragePaths` filtering straightforward and predictable. -**Reserve timeouts generously.** Set `buildTimeout` to account for cold-start scenarios, even when -warm builds are expected. The first build after a runner restart will be cold. +**Reserve timeouts generously.** There is no orchestrator-level build timeout input — set +`timeout-minutes` on the GitHub Actions job itself to account for cold-start scenarios, even when +warm builds are expected. The first build after a runner restart will be cold. Separately, +`gcTimeoutMinutes` forces a garbage-collection pass if a build overruns that many minutes (see +[Build Reliability](build-reliability)) — it is a cache-hygiene safety net, not a job timeout. ```yaml -- uses: game-ci/unity-builder@v4 - with: - buildTimeout: 360 # minutes +jobs: + build: + timeout-minutes: 360 + steps: + - uses: game-ci/unity-builder@v4 + with: + targetPlatform: StandaloneLinux64 ``` **Monitor Library folder health.** Occasional full reimports are necessary when Unity upgrades or @@ -229,13 +252,16 @@ mid-sprint when a runner's Library becomes stale. ## Inputs Reference -| Input | Description | -| ---------------------- | ------------------------------------------------------- | -| `retainedWorkspaces` | Keep child workspaces between builds (`true` / `false`) | -| `workspaceRoot` | Base path for root and child workspace storage | -| `localCacheRoot` | Local filesystem path for move-centric Library cache | -| `cacheStrategy` | Cache approach: `move`, `rclone`, `github-cache` | -| `lfsTransferAgent` | Name or path of a custom LFS transfer agent binary | -| `lfsTransferAgentArgs` | Additional arguments passed to the LFS transfer agent | -| `lfsStoragePaths` | Comma-separated asset paths to limit LFS hydration | -| `buildTimeout` | Maximum build duration in minutes | +| Input | Description | +| ----------------------------- | ----------------------------------------------------------------- | +| `childWorkspacesEnabled` | Enable per-build-target child workspaces (`true` / `false`) | +| `childWorkspaceName` | Cache slot name for this child workspace, usually the target platform | +| `childWorkspaceCacheRoot` | Base path for cached child workspaces | +| `childWorkspacePreserveGit` | Keep `.git` in the cached child workspace (default `true`) | +| `childWorkspaceSeparateLibrary` | Cache each engine cache folder independently (default `true`) | +| `localCacheEnabled` | Enable the local move-centric Library/LFS cache (`true` / `false`) | +| `localCacheRoot` | Local filesystem path for the move-centric Library cache | +| `localCacheMode` | Restore/save strategy: `move-directory`, `copy-directory`, `tar` | +| `lfsTransferAgent` | Name or path of a custom LFS transfer agent binary | +| `lfsTransferAgentArgs` | Additional arguments passed to the LFS transfer agent | +| `lfsStoragePaths` | Comma-separated asset paths to limit LFS hydration |