Home | History | Annotate | Download | only in test
      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 // +build !windows
      6 
      7 // Issue 18146: pthread_create failure during syscall.Exec.
      8 
      9 package cgotest
     10 
     11 import "C"
     12 
     13 import (
     14 	"bytes"
     15 	"crypto/md5"
     16 	"os"
     17 	"os/exec"
     18 	"runtime"
     19 	"syscall"
     20 	"testing"
     21 	"time"
     22 )
     23 
     24 func test18146(t *testing.T) {
     25 	if runtime.GOOS == "darwin" {
     26 		t.Skipf("skipping flaky test on %s; see golang.org/issue/18202", runtime.GOOS)
     27 	}
     28 
     29 	if runtime.GOARCH == "mips" || runtime.GOARCH == "mips64" {
     30 		t.Skipf("skipping on %s", runtime.GOARCH)
     31 	}
     32 
     33 	attempts := 1000
     34 	threads := 4
     35 
     36 	if testing.Short() {
     37 		attempts = 100
     38 	}
     39 
     40 	// Restrict the number of attempts based on RLIMIT_NPROC.
     41 	// Tediously, RLIMIT_NPROC was left out of the syscall package,
     42 	// probably because it is not in POSIX.1, so we define it here.
     43 	// It is not defined on Solaris.
     44 	var nproc int
     45 	setNproc := true
     46 	switch runtime.GOOS {
     47 	default:
     48 		setNproc = false
     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=NoSuchTestExists")
     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