runner tests: use /dev/shm tmpfs for stub exec to dodge overlayfs ETXTBSY
CI / Test (server-http) (pull_request) Successful in 6s
CI / Test (rest) (pull_request) Failing after 7s
CI / Build (windows/amd64) (pull_request) Successful in 8s
CI / Build (linux/amd64) (pull_request) Successful in 7s
CI / Lint (pull_request) Successful in 21s
CI / Build (linux/arm64) (pull_request) Successful in 7s
CI / Test (store) (pull_request) Successful in 2m10s
e2e / Playwright vs docker-compose (pull_request) Failing after 3m38s
CI / Test (server-http) (pull_request) Successful in 6s
CI / Test (rest) (pull_request) Failing after 7s
CI / Build (windows/amd64) (pull_request) Successful in 8s
CI / Build (linux/amd64) (pull_request) Successful in 7s
CI / Lint (pull_request) Successful in 21s
CI / Build (linux/arm64) (pull_request) Successful in 7s
CI / Test (store) (pull_request) Successful in 2m10s
e2e / Playwright vs docker-compose (pull_request) Failing after 3m38s
setupScript writes a small shell script then immediately fork/execs
it through the runner. The existing write-tmp-then-rename pattern
prevents the userspace ETXTBSY race on a vanilla filesystem, but
overlayfs has an additional window where the kernel's view of
"who holds a writable fd to this inode" can lag the rename — and
the new container-based CI jobs hit it from time to time
("fork/exec ...: text file busy").
Switch the test temp dir to /dev/shm when available (tmpfs has
no overlay layering), falling back to t.TempDir() when /dev/shm
isn't usable. Production code path is unaffected; this is a
pure test helper change.
This commit is contained in:
@@ -50,9 +50,16 @@ func (s *fakeSender) snapshot() []api.Envelope {
|
|||||||
// returns ETXTBSY ("text file busy"). Once the rename lands, the
|
// returns ETXTBSY ("text file busy"). Once the rename lands, the
|
||||||
// final path is a fresh dirent pointing at an inode that has no
|
// final path is a fresh dirent pointing at an inode that has no
|
||||||
// writable fd open anywhere — exec is safe.
|
// writable fd open anywhere — exec is safe.
|
||||||
|
//
|
||||||
|
// Temp dirs live under /dev/shm when it's writable (tmpfs). On a
|
||||||
|
// containerised CI runner, t.TempDir() lands on overlayfs, which
|
||||||
|
// has its own ETXTBSY surface that the rename pattern above does
|
||||||
|
// not fully close — the kernel's "writable inode" bookkeeping
|
||||||
|
// can lag the userspace close on overlay's upper layer. Falling
|
||||||
|
// back to a real tmpfs sidesteps the problem entirely.
|
||||||
func setupScript(t *testing.T, body string) string {
|
func setupScript(t *testing.T, body string) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
dir := t.TempDir()
|
dir := tempDirForExec(t)
|
||||||
final := filepath.Join(dir, "restic")
|
final := filepath.Join(dir, "restic")
|
||||||
tmp := final + ".tmp"
|
tmp := final + ".tmp"
|
||||||
if err := os.WriteFile(tmp, []byte("#!/bin/sh\n"+body+"\n"), 0o755); err != nil {
|
if err := os.WriteFile(tmp, []byte("#!/bin/sh\n"+body+"\n"), 0o755); err != nil {
|
||||||
@@ -64,6 +71,21 @@ func setupScript(t *testing.T, body string) string {
|
|||||||
return final
|
return final
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tempDirForExec returns a per-test temp dir that's safe to write
|
||||||
|
// then exec from. Uses /dev/shm (tmpfs) when available, otherwise
|
||||||
|
// falls back to t.TempDir(). See the setupScript comment for why.
|
||||||
|
func tempDirForExec(t *testing.T) string {
|
||||||
|
t.Helper()
|
||||||
|
if st, err := os.Stat("/dev/shm"); err == nil && st.IsDir() {
|
||||||
|
dir, err := os.MkdirTemp("/dev/shm", "rmrunner-")
|
||||||
|
if err == nil {
|
||||||
|
t.Cleanup(func() { _ = os.RemoveAll(dir) })
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t.TempDir()
|
||||||
|
}
|
||||||
|
|
||||||
// firstEnvOfType returns the first envelope with the given type, or
|
// firstEnvOfType returns the first envelope with the given type, or
|
||||||
// fails the test if none is found.
|
// fails the test if none is found.
|
||||||
func firstEnvOfType(t *testing.T, envs []api.Envelope, mt api.MessageType) api.Envelope {
|
func firstEnvOfType(t *testing.T, envs []api.Envelope, mt api.MessageType) api.Envelope {
|
||||||
|
|||||||
Reference in New Issue
Block a user