Skip to content

Commit 1f0e6ba

Browse files
authored
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.
1 parent c5d73b8 commit 1f0e6ba

File tree

2 files changed

+20
-1
lines changed

2 files changed

+20
-1
lines changed

provisioner/terraform/executor.go

+8
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ func (e *executor) execParseJSON(ctx, killCtx context.Context, args, env []strin
123123
cmd.Stdout = out
124124
cmd.Stderr = stdErr
125125

126+
e.server.logger.Debug(ctx, "executing terraform command with JSON result",
127+
slog.F("binary_path", e.binaryPath),
128+
slog.F("args", args),
129+
)
126130
err := cmd.Start()
127131
if err != nil {
128132
return err
@@ -348,6 +352,10 @@ func (e *executor) graph(ctx, killCtx context.Context) (string, error) {
348352
cmd.Dir = e.workdir
349353
cmd.Env = e.basicEnv()
350354

355+
e.server.logger.Debug(ctx, "executing terraform command graph",
356+
slog.F("binary_path", e.binaryPath),
357+
slog.F("args", "graph"),
358+
)
351359
err := cmd.Start()
352360
if err != nil {
353361
return "", err

provisioner/terraform/provision_test.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"runtime"
1515
"sort"
1616
"strings"
17+
"syscall"
1718
"testing"
1819
"time"
1920

@@ -165,8 +166,18 @@ func TestProvision_Cancel(t *testing.T) {
165166

166167
// Example: exec /path/to/terrafork_fake_cancel.sh 1.2.1 apply "$@"
167168
content := fmt.Sprintf("#!/bin/sh\nexec %q %s %s \"$@\"\n", fakeBin, terraform.TerraformVersion.String(), tt.mode)
168-
err := os.WriteFile(binPath, []byte(content), 0o755) //#nosec
169+
170+
// golang's standard OS library can sometimes leave the file descriptor open even after
171+
// "Closing" the file (which can then lead to a "text file busy" error, so we bypass this
172+
// and use syscall directly).
173+
fd, err := syscall.Open(binPath, syscall.O_WRONLY|syscall.O_CREAT, 0o755)
174+
require.NoError(t, err)
175+
n, err := syscall.Write(fd, []byte(content))
176+
require.NoError(t, err)
177+
require.Equal(t, len(content), n)
178+
err = syscall.Close(fd)
169179
require.NoError(t, err)
180+
t.Logf("wrote fake terraform script to %s", binPath)
170181

171182
ctx, api := setupProvisioner(t, &provisionerServeOptions{
172183
binaryPath: binPath,

0 commit comments

Comments
 (0)