Skip to content

llgo v0.12.2 on macOS amd64: concurrent temp-file operations panic with bad file descriptor #663

@luoliwoshang

Description

@luoliwoshang

Background

In PR #662, the CI matrix fails only on macos amd64 with llgo 0.12.2.
Other matrix targets (linux arm64/amd64, macos arm64) do not show this failure.

Failing test:

--- FAIL: TestEnd2End/sqlite3/3.49.1 (19.45s)

Failure Symptom

From the failed artifact (macos-15-intel-log.zip):

panic: close /var/folders/.../compose_1723566520.h: bad file descriptor

[0x02352E6D github.com/goplus/llcppg/cmd/llcppg.main+0x28, SP = 0x70d]
[0x02355A67 main+0x4, SP = 0x27]
[0x0F9AC530 start+0x5, SP = 0xbf0]

This aligns with the TestEnd2End/sqlite3/3.49.1 failure window.

Environment

  • OS: macos-15-intel (darwin/amd64)
  • llgo: v0.12.2-0.20260210235731-9c8b6b3df1ac (darwin/amd64)
  • Go (control): go1.23.6 darwin/amd64

Minimal Reproduction

This reproducer does not depend on llcppg logic. It only does concurrent CreateTemp + Close + Remove.

package main

import (
	"fmt"
	"os"
	"sync"
)

const (
	goroutines = 2
	iterations = 1000
)

func worker(n int, errs chan<- error) {
	for i := 0; i < n; i++ {
		f, err := os.CreateTemp("", "compose_*.h")
		if err != nil {
			errs <- err
			return
		}
		name := f.Name()
		if err := f.Close(); err != nil {
			errs <- fmt.Errorf("close %s: %w", name, err)
			return
		}
		if err := os.Remove(name); err != nil {
			errs <- err
			return
		}
	}
}

func main() {
	errs := make(chan error, goroutines)
	var wg sync.WaitGroup

	for i := 0; i < goroutines; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			worker(iterations, errs)
		}()
	}

	wg.Wait()
	close(errs)

	for err := range errs {
		if err != nil {
			panic(err)
		}
	}

	fmt.Printf("ok goroutines=%d iterations=%d\n", goroutines, iterations)
}

Run:

# Fails under llgo (concurrent case)
llgo run repro.go
# panic: close ... bad file descriptor

# Passes under Go toolchain
go run repro.go
# ok goroutines=2 iterations=1000

Additional Repro (single-thread, deterministic)

The issue is reproducible even without concurrency:

package main

import (
	"fmt"
	"os"
	"path/filepath"
	"syscall"
)

func main() {
	p := filepath.Join(os.TempDir(), fmt.Sprintf("open-fail-%d.tmp", os.Getpid()))
	_ = os.WriteFile(p, []byte("x"), 0600)
	defer os.Remove(p)

	fd, err := syscall.Open(p, syscall.O_CREAT|syscall.O_EXCL|syscall.O_RDWR, 0600)
	fmt.Printf("fd=%d hex=%#x err=%v\n", fd, uint64(fd), err)
}

Observed output:

# go run
fd=4294967295 hex=0xffffffff err=file exists

# llgo run
fd=4294967295 hex=0xffffffff err=<nil>

This means the syscall failed (invalid fd 0xffffffff) but err was lost under llgo.

Root Cause Analysis (likely)

  • os.CreateTemp internally relies on OpenFile(..., O_CREATE|O_EXCL, ...).
  • Under contention, open may return EEXIST (normal behavior).
  • With llgo on darwin/amd64, this failure can be interpreted as err=nil while returning invalid fd (0xffffffff).
  • The invalid fd then propagates into *os.File, and later operations (Write / Stat / Close) fail with bad file descriptor.
  • Concurrency amplifies filename-collision opportunities, so the bug appears mostly in concurrent test runs.

Likely implementation mismatch in llgo syscall lowering:

  • errno is derived only when r1 == ^uintptr(0).
  • on this path, failing C calls may produce a 32-bit -1 shape (0x00000000ffffffff) rather than full-width ^uintptr(0) (0xffffffffffffffff) on amd64.
  • then errno is not captured, causing err=nil on failure.

Expected Behavior

No panic; temp file descriptors should remain valid and close cleanly under concurrency.

Actual Behavior

On llgo v0.12.2 + darwin/amd64, concurrent temp-file usage can panic with bad file descriptor.

Impact on llcppg

End-to-end concurrent tests (notably sqlite3) can fail on this specific toolchain/platform combination.

Temporary Workarounds

  • CI: reduce concurrency or skip macos amd64 + llgo 0.12.2.
  • Code: avoid holding temp-file descriptors longer than necessary (helpful, but does not fully eliminate a runtime-level issue).

Artifacts

  • /Users/heulucklu/Downloads/logs_58617825193.zip
  • /Users/heulucklu/Downloads/macos-15-intel-log.zip

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions