fix: use raw syscalls to write binary we execute (#11684)

Fixes flake seen here, I think

https://github.com/coder/coder/actions/runs/7565915337/job/20602500818

golang's file processing is complex, and in at least some cases it can return from a file.Close() call without having actually closed the file descriptor.

If we're holding open the file descriptor of an executable we just wrote, and try to execute it, it will fail with "text file busy" which is what we have seen.

So, to be extra sure, I've avoided the standard library and directly called the syscalls to open, write, and close the file we intend to use in the test.

I've also added some more logging so if it's some issue of multiple tests writing to the same location, the we might have a chance to see it.
This commit is contained in:
Spike Curtis 2024-01-18 16:21:11 +04:00 committed by GitHub
parent c5d73b86d6
commit 1f0e6ba6c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 20 additions and 1 deletions

View File

@ -123,6 +123,10 @@ func (e *executor) execParseJSON(ctx, killCtx context.Context, args, env []strin
cmd.Stdout = out
cmd.Stderr = stdErr
e.server.logger.Debug(ctx, "executing terraform command with JSON result",
slog.F("binary_path", e.binaryPath),
slog.F("args", args),
)
err := cmd.Start()
if err != nil {
return err
@ -348,6 +352,10 @@ func (e *executor) graph(ctx, killCtx context.Context) (string, error) {
cmd.Dir = e.workdir
cmd.Env = e.basicEnv()
e.server.logger.Debug(ctx, "executing terraform command graph",
slog.F("binary_path", e.binaryPath),
slog.F("args", "graph"),
)
err := cmd.Start()
if err != nil {
return "", err

View File

@ -14,6 +14,7 @@ import (
"runtime"
"sort"
"strings"
"syscall"
"testing"
"time"
@ -165,8 +166,18 @@ func TestProvision_Cancel(t *testing.T) {
// Example: exec /path/to/terrafork_fake_cancel.sh 1.2.1 apply "$@"
content := fmt.Sprintf("#!/bin/sh\nexec %q %s %s \"$@\"\n", fakeBin, terraform.TerraformVersion.String(), tt.mode)
err := os.WriteFile(binPath, []byte(content), 0o755) //#nosec
// golang's standard OS library can sometimes leave the file descriptor open even after
// "Closing" the file (which can then lead to a "text file busy" error, so we bypass this
// and use syscall directly).
fd, err := syscall.Open(binPath, syscall.O_WRONLY|syscall.O_CREAT, 0o755)
require.NoError(t, err)
n, err := syscall.Write(fd, []byte(content))
require.NoError(t, err)
require.Equal(t, len(content), n)
err = syscall.Close(fd)
require.NoError(t, err)
t.Logf("wrote fake terraform script to %s", binPath)
ctx, api := setupProvisioner(t, &provisionerServeOptions{
binaryPath: binPath,