1// Copyright 2016 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5//go:build cgo && !windows
6
7// Issue 18146: pthread_create failure during syscall.Exec.
8
9package cgotest
10
11import (
12	"bytes"
13	"crypto/md5"
14	"os"
15	"os/exec"
16	"runtime"
17	"syscall"
18	"testing"
19	"time"
20)
21
22func test18146(t *testing.T) {
23	if testing.Short() {
24		t.Skip("skipping in short mode")
25	}
26
27	if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
28		t.Skipf("skipping flaky test on %s; see golang.org/issue/18202", runtime.GOOS)
29	}
30
31	if runtime.GOARCH == "mips" || runtime.GOARCH == "mips64" {
32		t.Skipf("skipping on %s", runtime.GOARCH)
33	}
34
35	attempts := 1000
36	threads := 4
37
38	// Restrict the number of attempts based on RLIMIT_NPROC.
39	// Tediously, RLIMIT_NPROC was left out of the syscall package,
40	// probably because it is not in POSIX.1, so we define it here.
41	// It is not defined on Solaris.
42	var nproc int
43	setNproc := true
44	switch runtime.GOOS {
45	default:
46		setNproc = false
47	case "aix":
48		nproc = 9
49	case "linux":
50		nproc = 6
51	case "darwin", "dragonfly", "freebsd", "netbsd", "openbsd":
52		nproc = 7
53	}
54	if setNproc {
55		var rlim syscall.Rlimit
56		if syscall.Getrlimit(nproc, &rlim) == nil {
57			max := int(rlim.Cur) / (threads + 5)
58			if attempts > max {
59				t.Logf("lowering attempts from %d to %d for RLIMIT_NPROC", attempts, max)
60				attempts = max
61			}
62		}
63	}
64
65	if os.Getenv("test18146") == "exec" {
66		runtime.GOMAXPROCS(1)
67		for n := threads; n > 0; n-- {
68			go func() {
69				for {
70					_ = md5.Sum([]byte("Hello, !"))
71				}
72			}()
73		}
74		runtime.GOMAXPROCS(threads)
75		argv := append(os.Args, "-test.run=^$")
76		if err := syscall.Exec(os.Args[0], argv, os.Environ()); err != nil {
77			t.Fatal(err)
78		}
79	}
80
81	var cmds []*exec.Cmd
82	defer func() {
83		for _, cmd := range cmds {
84			cmd.Process.Kill()
85		}
86	}()
87
88	args := append(append([]string(nil), os.Args[1:]...), "-test.run=^Test18146$")
89	for n := attempts; n > 0; n-- {
90		cmd := exec.Command(os.Args[0], args...)
91		cmd.Env = append(os.Environ(), "test18146=exec")
92		buf := bytes.NewBuffer(nil)
93		cmd.Stdout = buf
94		cmd.Stderr = buf
95		if err := cmd.Start(); err != nil {
96			// We are starting so many processes that on
97			// some systems (problem seen on Darwin,
98			// Dragonfly, OpenBSD) the fork call will fail
99			// with EAGAIN.
100			if pe, ok := err.(*os.PathError); ok {
101				err = pe.Err
102			}
103			if se, ok := err.(syscall.Errno); ok && (se == syscall.EAGAIN || se == syscall.EMFILE) {
104				time.Sleep(time.Millisecond)
105				continue
106			}
107
108			t.Error(err)
109			return
110		}
111		cmds = append(cmds, cmd)
112	}
113
114	failures := 0
115	for _, cmd := range cmds {
116		err := cmd.Wait()
117		if err == nil {
118			continue
119		}
120
121		t.Errorf("syscall.Exec failed: %v\n%s", err, cmd.Stdout)
122		failures++
123	}
124
125	if failures > 0 {
126		t.Logf("Failed %v of %v attempts.", failures, len(cmds))
127	}
128}
129