mirror of
https://github.com/tailscale/tailscale.git
synced 2026-09-21 11:05:14 -04:00
Tests that exec a fresh CopyTo copy of the tailscale/tailscaled binaries occasionally failed with "text file busy" on GitHub Actions, previously worked around with the retry loop in awaitTailscaledRunnable. The root cause: CopyTo's Linux hardlink fast path only works while the built binary is still linked somewhere. The kernel refuses to hardlink an inode whose link count is zero, even via the still-open FD, so once the building test's TempDir (and every other test's copy) has been cleaned up, later tests silently fall through to the byte-copy path. That path writes the new copy from the test process itself, and if another parallel test forks a child while the write FD is open, the child holds the inherited FD (O_CLOEXEC only closes at exec, not at fork) and a subsequent exec of the fresh copy fails with ETXTBSY. This is golang.org/issue/22315. On macOS and the BSDs, CopyTo always takes the byte-copy path, so every copy races there. Fix it the way the syscall package documents: hold ForkLock for reading across the copy so that no fork overlaps the lifetime of the write FD. A standalone stress program reproduced the race in 86 of 200 execs under a fork storm and in 0 of 200 with the lock held. Updates #15868 Updates #15865 Change-Id: I8a6be72826c9073a9187d7e6c90c8733d2dadb05 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>