Skip to content

Lock COW branch requests during mutations#14

Closed
adamziel wants to merge 4 commits into
codex/cow-git-delete-branchesfrom
codex/cow-request-operation-locks
Closed

Lock COW branch requests during mutations#14
adamziel wants to merge 4 commits into
codex/cow-git-delete-branchesfrom
codex/cow-request-operation-locks

Conversation

@adamziel
Copy link
Copy Markdown
Contributor

@adamziel adamziel commented May 5, 2026

What it does

Adds request-side locking for the COW materialized backend. ForkPress-served WordPress requests now take a shared flock on .forkpress/cow/operations.lock, while branch create/reset/delete and Git apply continue to take the same lock exclusively.

This PR is stacked on #13.

Rationale

The COW mutation paths already had an exclusive operation lock, but normal WordPress requests did not participate. That meant a reset, delete, or Git apply could publish or remove a materialized branch tree while a ForkPress-served request was still reading or writing that tree.

The lock is advisory and scoped to ForkPress traffic. Direct editor/shell writes to ./main or ./marketing are still ordinary filesystem writes and do not automatically honor it.

Implementation

runtime/router_cow.php now parses the request path before branch lookup, keeps Git smart HTTP on its existing exclusive lock path, and acquires a shared operation lock before serving normal branch traffic. The lock is released through a shutdown callback so PHP and static file responses both keep it for the lifetime of the request.

Added tests/test_cow_router_lock.php, which holds the operation lock exclusively, spawns a router request, and verifies the branch PHP does not run until the exclusive lock is released.

Testing instructions

php -l runtime/router_cow.php
php -l tests/test_cow_router_lock.php
php -d "extension=$(pwd)/ext/branchfs.so" tests/test_cow_router_lock.php
FORKPRESS_RUNTIME_BUNDLE=/dev/null cargo test -p forkpress
make test-all
cargo build --release --target x86_64-unknown-linux-musl -p forkpress
tests/test_cow_strategy_e2e.sh target/x86_64-unknown-linux-musl/release/forkpress

@JanJakes JanJakes force-pushed the codex/cow-git-delete-branches branch from a10ea7f to e23f198 Compare May 6, 2026 15:51
@JanJakes JanJakes deleted the branch codex/cow-git-delete-branches May 6, 2026 15:51
@JanJakes JanJakes closed this May 6, 2026
@JanJakes
Copy link
Copy Markdown
Contributor

JanJakes commented May 6, 2026

This PR was auto-closed when its stacked base branch (codex/cow-git-delete-branches) was deleted after #13 was merged. I attempted to retarget/reopen it, but GitHub would not allow changing the base of the closed PR or reopening it after the base branch was gone.

Replacement PR with the same change rebased onto trunk: #31

JanJakes added a commit that referenced this pull request May 6, 2026
## What it does

Adds request-side locking for the COW materialized backend.
ForkPress-served WordPress requests now take a shared `flock` on
`.forkpress/cow/operations.lock`, while branch create/reset/delete and
Git apply continue to take the same lock exclusively.

This replaces #14, which was closed automatically when the stacked base
branch for #13 was deleted after merging.

## Rationale

The COW mutation paths already had an exclusive operation lock, but
normal WordPress requests did not participate. That meant a reset,
delete, or Git apply could publish or remove a materialized branch tree
while a ForkPress-served request was still reading or writing that tree.

The lock is advisory and scoped to ForkPress traffic. Direct
editor/shell writes to `./main` or another branch are still ordinary
filesystem writes and do not automatically honor it.

## Implementation

`runtime/router_cow.php` now parses the request path before branch
lookup, keeps Git smart HTTP on its existing exclusive lock path, and
acquires a shared operation lock before serving normal branch traffic.
The lock is released through a shutdown callback so PHP and static file
responses both keep it for the lifetime of the request.

Added `tests/test_cow_router_lock.php`, which holds the operation lock
exclusively, spawns a router request, and verifies the branch PHP does
not run until the exclusive lock is released.

## Testing instructions

```bash
php -l runtime/router_cow.php
php -l tests/test_cow_router_lock.php
php -d "extension=$(pwd)/ext/branchfs.so" tests/test_cow_router_lock.php
FORKPRESS_RUNTIME_BUNDLE=/dev/null cargo test -p forkpress
make test-all
cargo build --release --target x86_64-unknown-linux-musl -p forkpress
tests/test_cow_strategy_e2e.sh target/x86_64-unknown-linux-musl/release/forkpress
```

Co-authored-by: Adam Zieliński <adam@adamziel.com>
JanJakes pushed a commit that referenced this pull request May 6, 2026
## What it does

Adds `forkpress storage compact` for macOS APFS sparsebundle-backed COW
sites and expands `forkpress storage status` with branch/storage roots,
lock paths, branch counts, and interrupted-operation leftovers.

This is stacked on #14.

## Rationale

Sparsebundle-backed sites need a single ForkPress-managed way to stop
the site, detach storage, reclaim free image bands, and explain what is
still attached or left behind. Direct `hdiutil`/`rm -rf` workflows are
too easy to get wrong while a server or branch mutation is active.

## Implementation

`storage compact` stops this site's server unless `--keep-server` is
passed, detaches sparsebundle storage, runs `hdiutil compact`, and
leaves storage detached until `forkpress serve` or `forkpress storage
mount`.

The COW lifecycle now uses:

- `operations.lock` for branch/storage mutations and request exclusion.
- `lifecycle.lock` so background server startup and detach/compact
cannot race.
- A locked server registry so read/filter/write operations cannot
clobber a concurrently registered server.

The COW E2E now checks lifecycle diagnostics and exercises `storage
compact`; macOS CI runs that path with
`FORKPRESS_FORCE_MACOS_APFS_SPARSEBUNDLE=1`.

## Testing instructions

```bash
FORKPRESS_RUNTIME_BUNDLE=/dev/null cargo test -p forkpress
make test-all
cargo build --release --target x86_64-unknown-linux-musl -p forkpress
tests/test_cow_strategy_e2e.sh target/x86_64-unknown-linux-musl/release/forkpress
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants