Luther Monson, Staff Software Engineer at SUSE

Luther Monson

Go by way of PHP · Containers · VMs · Windows · Kubernetes

Composer Without PHP: Embedding vivacity in ePHPm

I embedded vivacity, a pure-Rust Composer-compatible installer, in ePHPm so one binary can install a PHP app's dependencies without PHP. This is what it took upstream and in ePHPm, and how it does against Composer on Windows.

10-Minute Read

Bar chart comparing Composer 2.10.3 and ephpm composer on a Laravel install on native Windows

The pitch for ePHPm is one binary in place of php-fpm, nginx and Redis. There was always a hole in it though. To deploy a PHP app you still need to run composer install. That drags PHP and composer.phar back onto the box or into the container build, just for that one step. ephpm composer install now runs a pure-Rust, Composer-compatible installer called vivacity inside the ePHPm binary. One binary serves the app and installs its dependencies. On Windows that works with no PHP installed at all.…

Windows Grab-Bag: SIGTERM That Doesn't Exist and Sparse Files That Break Docker

Go can't SIGTERM a child on Windows, so ephemerd's cloudflared processes piled up. Separately, Delivery Optimization's sparse temp files broke its Windows image layer commits.

7-Minute Read

Bar chart: 96 Cloudflare edge connections held by orphaned cloudflared processes versus 4 for the live daemon

Two short ones from the Windows node of ephemerd from the last few weeks. Neither is complicated once you know what’s going on. Both took longer than they should have to find, because the failure showed up a long way from the cause.

An Upstream PHP JIT Use-After-Free, and Why Our Default Flipped Twice

ePHPm turned PHP's tracing JIT on by default, then off three days later when stock Laravel died after three requests. The bug was upstream, and this is the bisect, the root cause and where the fix landed.

10-Minute Read

Bar chart of requests served before a crash per PHP version: 8.4.14, 8.5.0 and 8.5.4 never crash; 8.4.24 dies at 7, 8.5.5 at 1, 8.5.7 and 8.5.9 at 2

In August, ePHPm turned PHP’s tracing JIT on by default in single-site serve mode. Three days later it went back off on every platform, and it’s still off. The reason turned out to be a use-after-free in PHP’s tracing JIT that has nothing to do with ePHPm. Stock php -S with a stock Laravel skeleton dies on it in a couple of requests. The fix is now released upstream. So this seemed like the right time to write up how we got there, and what still has to happen before the default…

Cross-Platform Process Containment: Job Objects vs Process Groups vs cgroups

How shipmates bounds and tears down agent process trees on Windows, Linux and macOS. It also covers why I deleted 1,206 lines of cgroup code and the grandchild that escaped the memory cap everywhere except my dev box.

8-Minute Read

Agent process tree with the scope of kill and measurement on Windows, Linux and macOS

Every turn a shipmates crew member takes is a subprocess. It’s claude or the codex app-server. That in turn spawns shells, go test, npm or whatever the task calls for. Two things have to be true about that tree. When a turn ends or gets killed, the whole tree goes, including the grandchildren. And if somebody sets a memory or CPU budget, the budget applies to the whole tree, not just the process I happened to start.

A 1 TB Test: Building a Memory Watchdog for go test

A runaway test binary hit about 1 TB of commit charge and took my desktop down. Here's the PowerShell and POSIX sh wrapper I put around go test, and the bug in it that the Windows box couldn't show me.

8-Minute Read

go test process tree with per-package test binaries watched by the safe-test wrapper

On August 16th a server.test.exe from shipmates climbed to roughly 1 TB of commit charge on my Windows workstation. Windows Terminal was hosting several agent sessions in tabs. It hard-faulted with E_OUTOFMEMORY, and every one of those sessions died at the same moment. It wasn’t the first time either. An earlier one hit about 236 GB through an infinite filepath.Dir loop.

shipmates Short Ones: 8 Minutes Extracting Go, and Three Voice-Loop Bugs

Four small stories from shipmates, starting with setup-go stuck extracting on Windows CI and Cloudflare's 100 second timeout. The other two are a voice loop that transcribed itself and events lost to a timestamp watermark.

5-Minute Read

setup-go on Windows CI: 7 seconds to download, 473 seconds extracting before the timeout

A few shipmates bugs don’t need a whole post each. They were too good to leave in commit messages, though.

OPcache on Windows: Two Silent Failures

An ASLR fatal on Windows looked like a memory-size lottery. Separately, Windows PHP 8.3 builds never had an opcode cache, and the fix sat unpicked for three weeks.

8-Minute Read

Two ePHPm processes computing the same OPcache shared-memory name; the second reattaches and dies on the ASLR check

Windows is the platform where ePHPm gets the least traffic and the most surprises. Last week closed out two OPcache bugs that only exist there. Both were quiet in their own way. One killed the server at startup with nothing in our own logs. The other didn’t kill anything. It just ran PHP 8.3 with no opcode cache at all. For three weeks after a fixed SDK was published, every check stayed green.

Five Ways to Not Firewall a Windows Container (and the One That Works)

Five different Windows firewall approaches failed to stop a container from reaching my home network. The sixth, a different network mode for the container, finally worked.

10-Minute Read

Packet path of a Windows container on an HNS NAT network versus an L2Bridge network

ephemerd runs CI jobs from pull requests. One of the rules is that a job can reach the internet but not my LAN. On Linux that’s a few iptables rules in the FORWARD path, and I didn’t think about it much. On Windows I spent about a week finding out how many ways there are to write a firewall rule that Windows accepts, lists and never applies.

My Benchmark Was 4x Wrong Because of WSL

Launching the server with its working directory on /mnt/c cost ePHPm about 4x on a light workload. The report built a wrong explanation on top of it, and the strace shows why.

8-Minute Read

Bar chart of ePHPm requests per second by working directory: native ext4 far ahead of DrvFs, with DrvFs throughput falling as the cwd path gets deeper

I do most of my benchmarking on a Windows box in WSL2. It’s the machine on my desk with 32 threads and 62 GB of RAM. Everybody who does that learns the first rule early: don’t put your data on /mnt/c. DrvFs is the 9p-backed mount that exposes the Windows drives inside WSL, and it’s slow for file-heavy work. So the databases, the docroot and the build output all live on the WSL ext4 disk. My scaling harness for ePHPm even says so in capital letters in its README.

Recent Posts

Categories

About

Luther is a 20+ year software veteran specializing in backend APIs and architecting products. Employed at SUSE as the Team Lead for the Containers on Windows Team working on Rancher + Kubernetes