Serialize Gradle containers sharing a cache volume #22

Merged
justinzeus merged 1 commit from fm/wolfskin-14-gradle-lock into main 2026-10-27 17:03:04 +01:00
Owner

Change

Acquire a host-side flock before running Gradle containers, keeping it through container exit. Contending callers print one waiting line and queue. Default: /var/tmp/${WOLFSKIN_GRADLE_VOLUME}.gradle.lock, with wolfskin-gradle as the volume default; override with WOLFSKIN_GRADLE_LOCK_FILE. This intentionally differs from the temporary outer /tmp/wolfskin-gradle.lock used by other lanes. Document dependencies and the shared-path requirement.

Reconciliation

Fetched origin/main at 761a5a6145809d00e951e44698ae6c461c3585a3; its docker/gradle.sh has no host lock. Live Docker has the wolfskin-gradle named volume (created October 22); no Wolfskin app container or pre-existing build image was present. This changes the build wrapper only, not app deployment.

Verification

bash -n docker/gradle.sh passed. Started two real make apk-debug commands together, without outer flock; both exited 0. UTC timestamps:

Invocation Started Finished Gradle result
First 2026-10-27T14:21:07.630211Z 2026-10-27T14:27:28.582079Z BUILD SUCCESSFUL in 6m 7s
Second 2026-10-27T14:21:07.632251Z 2026-10-27T14:30:22.448270Z BUILD SUCCESSFUL in 2m 50s

Second invocation printed exactly one Waiting for Gradle cache lock: /var/tmp/wolfskin-gradle.gradle.lock before Gradle ran. Docker image builds can overlap; Gradle containers queue. Full logs are under /root/.treehouse/firstmate-7bab20/2/firstmate/data/wolfskin-14-gradle-lock/build-{1,2}.log.

Constraints

All callers sharing a Docker volume must use the same host lock path; do not unlink the lock while callers run or wait. Callers bypassing the wrapper are not serialized. Forgejo Actions is disabled; no CI status is expected. AI-assisted implementation and PR description.

Closes justinzeus/wolfskin#14

## Change Acquire a host-side flock before running Gradle containers, keeping it through container exit. Contending callers print one waiting line and queue. Default: `/var/tmp/${WOLFSKIN_GRADLE_VOLUME}.gradle.lock`, with `wolfskin-gradle` as the volume default; override with `WOLFSKIN_GRADLE_LOCK_FILE`. This intentionally differs from the temporary outer `/tmp/wolfskin-gradle.lock` used by other lanes. Document dependencies and the shared-path requirement. ## Reconciliation Fetched origin/main at `761a5a6145809d00e951e44698ae6c461c3585a3`; its docker/gradle.sh has no host lock. Live Docker has the wolfskin-gradle named volume (created October 22); no Wolfskin app container or pre-existing build image was present. This changes the build wrapper only, not app deployment. ## Verification `bash -n docker/gradle.sh` passed. Started two real `make apk-debug` commands together, without outer flock; both exited 0. UTC timestamps: | Invocation | Started | Finished | Gradle result | | --- | --- | --- | --- | | First | 2026-10-27T14:21:07.630211Z | 2026-10-27T14:27:28.582079Z | BUILD SUCCESSFUL in 6m 7s | | Second | 2026-10-27T14:21:07.632251Z | 2026-10-27T14:30:22.448270Z | BUILD SUCCESSFUL in 2m 50s | Second invocation printed exactly one `Waiting for Gradle cache lock: /var/tmp/wolfskin-gradle.gradle.lock` before Gradle ran. Docker image builds can overlap; Gradle containers queue. Full logs are under `/root/.treehouse/firstmate-7bab20/2/firstmate/data/wolfskin-14-gradle-lock/build-{1,2}.log`. ## Constraints All callers sharing a Docker volume must use the same host lock path; do not unlink the lock while callers run or wait. Callers bypassing the wrapper are not serialized. Forgejo Actions is disabled; no CI status is expected. AI-assisted implementation and PR description. Closes justinzeus/wolfskin#14
justinzeus added 1 commit 2026-10-27 15:30:58 +01:00
Serialize Gradle containers sharing a cache volume
All checks were successful
review/code-claude Approve: lock held across container, no deadlock, releases on all exits
221f623c87
Author
Owner

Review verdict: approve (no blocking findings) — reviewed SHA 221f623c871bf2a041fe5416a02b00d623864df4, code review by Claude. Closes the Gradle-cache-lock ticket (justinzeus/wolfskin#14).

Checked

  • Lock held across the whole container run: exec {lock_fd}>"$lock" + flock happen before docker run, and docker run is a child of the script (no exec), so the fd stays open until the script exits. Stub-docker test: B's docker-enter came 7 ms after A's docker-exit (3 s runs), B printed one Waiting for Gradle cache lock: ... line.
  • Real run: two docker/gradle.sh debug --version started together (no outer flock) both exited 0; one printed the waiting line; they finished 5.4 s and 7.4 s after start, i.e. queued.
  • PR logs (build-1.log/build-2.log): both started 14:21:07.63Z; first ended 14:27:28Z; second printed the single waiting line, then BUILD SUCCESSFUL in 2m 50s ending 14:30:22Z, so its Gradle started about 14:27:32Z, after the first finished. Both EXIT 0.
  • No deadlock with the interim outer flock /tmp/wolfskin-gradle.lock make ...: default lock is /var/tmp/wolfskin-gradle.gradle.lock, a different file. Verified flock /tmp/wolfskin-gradle.lock bash docker/gradle.sh ... returns rc=0 immediately.
  • Override WOLFSKIN_GRADLE_LOCK_FILE works (custom path created, A/B queued on it). Different WOLFSKIN_GRADLE_VOLUME values get independent locks and run in parallel.
  • Release on every exit path: non-zero docker exit (rc=1) then an immediate next run acquires at once; Ctrl-C (SIGINT to the process group) ends the run with rc=130 and the waiter proceeds. Single normal run unaffected (rc=0).
  • Missing override parent dir fails clearly: x.lock: No such file or directory, rc=1. README mentions the lock, default, override, and shared-path requirement.

Notes (non-blocking)

  1. Missing flock binary does fail (rc=127) but the output is misleading: flock: command not found, then a bogus Waiting for Gradle cache lock: ..., then a second command not found, because if ! flock --nonblock treats 127 as "contended". A command -v flock guard, or checking for exit status 1 specifically, would make it clean. The README does list flock as a prerequisite.
  2. The lock file is created with the caller's umask; here it is 0600 root. Callers running as different uids on the same host would get a permission error opening it; fine while every lane is root.
  3. The lock is released when the last holder of the fd exits. If the script is SIGKILLed while docker run survives, the lock stays held by the docker CLI until it exits (the safe direction).
  4. Docker image builds are not covered by the lock (the PR says so).
**Review verdict: approve (no blocking findings)** — reviewed SHA `221f623c871bf2a041fe5416a02b00d623864df4`, code review by Claude. Closes the Gradle-cache-lock ticket (justinzeus/wolfskin#14). **Checked** - Lock held across the whole container run: `exec {lock_fd}>"$lock"` + `flock` happen before `docker run`, and `docker run` is a child of the script (no `exec`), so the fd stays open until the script exits. Stub-docker test: B's `docker-enter` came 7 ms after A's `docker-exit` (3 s runs), B printed one `Waiting for Gradle cache lock: ...` line. - Real run: two `docker/gradle.sh debug --version` started together (no outer flock) both exited 0; one printed the waiting line; they finished 5.4 s and 7.4 s after start, i.e. queued. - PR logs (`build-1.log`/`build-2.log`): both started 14:21:07.63Z; first ended 14:27:28Z; second printed the single waiting line, then `BUILD SUCCESSFUL in 2m 50s` ending 14:30:22Z, so its Gradle started about 14:27:32Z, after the first finished. Both EXIT 0. - No deadlock with the interim outer `flock /tmp/wolfskin-gradle.lock make ...`: default lock is `/var/tmp/wolfskin-gradle.gradle.lock`, a different file. Verified `flock /tmp/wolfskin-gradle.lock bash docker/gradle.sh ...` returns rc=0 immediately. - Override `WOLFSKIN_GRADLE_LOCK_FILE` works (custom path created, A/B queued on it). Different `WOLFSKIN_GRADLE_VOLUME` values get independent locks and run in parallel. - Release on every exit path: non-zero docker exit (rc=1) then an immediate next run acquires at once; Ctrl-C (SIGINT to the process group) ends the run with rc=130 and the waiter proceeds. Single normal run unaffected (rc=0). - Missing override parent dir fails clearly: `x.lock: No such file or directory`, rc=1. README mentions the lock, default, override, and shared-path requirement. **Notes (non-blocking)** 1. Missing `flock` binary does fail (rc=127) but the output is misleading: `flock: command not found`, then a bogus `Waiting for Gradle cache lock: ...`, then a second `command not found`, because `if ! flock --nonblock` treats 127 as "contended". A `command -v flock` guard, or checking for exit status 1 specifically, would make it clean. The README does list `flock` as a prerequisite. 2. The lock file is created with the caller's umask; here it is `0600` root. Callers running as different uids on the same host would get a permission error opening it; fine while every lane is root. 3. The lock is released when the last holder of the fd exits. If the script is SIGKILLed while `docker run` survives, the lock stays held by the docker CLI until it exits (the safe direction). 4. Docker image builds are not covered by the lock (the PR says so).
justinzeus merged commit d78a23a80a into main 2026-10-27 17:03:04 +01:00
Sign in to join this conversation.
No reviewers
No labels
exploring
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: justinzeus/wolfskin#22
No description provided.