mirror of
https://github.com/golang/go
synced 2024-11-02 08:01:26 +00:00
runtime: skip TestLockOSThreadAvoidsStatePropagation if one can't unshare
This change splits a testprog out of TestLockOSThreadExit and makes it its own test. Then, this change makes the testprog exit prematurely with a special message if unshare fails with EPERM because not all of the builders allow the user to call the unshare syscall. Also, do some minor cleanup on the TestLockOSThread* tests. Fixes #29366. Change-Id: Id8a9f6c4b16e26af92ed2916b90b0249ba226dbe Reviewed-on: https://go-review.googlesource.com/c/155437 Run-TryBot: Michael Knyszek <mknyszek@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
parent
90dca98d33
commit
429bae7158
4 changed files with 78 additions and 53 deletions
|
@ -885,23 +885,28 @@ func TestLockOSThreadNesting(t *testing.T) {
|
||||||
|
|
||||||
func TestLockOSThreadExit(t *testing.T) {
|
func TestLockOSThreadExit(t *testing.T) {
|
||||||
testLockOSThreadExit(t, "testprog")
|
testLockOSThreadExit(t, "testprog")
|
||||||
|
|
||||||
want := "OK\n"
|
|
||||||
output := runTestProg(t, "testprog", "LockOSThreadAvoidsStatePropagation", "GOMAXPROCS=1")
|
|
||||||
if output != want {
|
|
||||||
t.Errorf("want %s, got %s\n", want, output)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testLockOSThreadExit(t *testing.T, prog string) {
|
func testLockOSThreadExit(t *testing.T, prog string) {
|
||||||
output := runTestProg(t, prog, "LockOSThreadMain", "GOMAXPROCS=1")
|
output := runTestProg(t, prog, "LockOSThreadMain", "GOMAXPROCS=1")
|
||||||
want := "OK\n"
|
want := "OK\n"
|
||||||
if output != want {
|
if output != want {
|
||||||
t.Errorf("want %s, got %s\n", want, output)
|
t.Errorf("want %q, got %q", want, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
output = runTestProg(t, prog, "LockOSThreadAlt")
|
output = runTestProg(t, prog, "LockOSThreadAlt")
|
||||||
if output != want {
|
if output != want {
|
||||||
t.Errorf("want %s, got %s\n", want, output)
|
t.Errorf("want %q, got %q", want, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLockOSThreadAvoidsStatePropagation(t *testing.T) {
|
||||||
|
want := "OK\n"
|
||||||
|
skip := "unshare not permitted\n"
|
||||||
|
output := runTestProg(t, "testprog", "LockOSThreadAvoidsStatePropagation", "GOMAXPROCS=1")
|
||||||
|
if output == skip {
|
||||||
|
t.Skip("unshare syscall not permitted on this system")
|
||||||
|
} else if output != want {
|
||||||
|
t.Errorf("want %q, got %q", want, output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,6 +144,10 @@ func LockOSThreadAvoidsStatePropagation() {
|
||||||
// the rest of the process on this thread.
|
// the rest of the process on this thread.
|
||||||
// On systems other than Linux, this is a no-op.
|
// On systems other than Linux, this is a no-op.
|
||||||
if err := unshareFs(); err != nil {
|
if err := unshareFs(); err != nil {
|
||||||
|
if err == errNotPermitted {
|
||||||
|
println("unshare not permitted")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
println("failed to unshare fs:", err.Error())
|
println("failed to unshare fs:", err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
47
src/runtime/testdata/testprog/syscalls.go
vendored
47
src/runtime/testdata/testprog/syscalls.go
vendored
|
@ -2,53 +2,10 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func gettid() int {
|
var errNotPermitted = errors.New("operation not permitted")
|
||||||
return syscall.Gettid()
|
|
||||||
}
|
|
||||||
|
|
||||||
func tidExists(tid int) (exists, supported bool) {
|
|
||||||
stat, err := ioutil.ReadFile(fmt.Sprintf("/proc/self/task/%d/stat", tid))
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return false, true
|
|
||||||
}
|
|
||||||
// Check if it's a zombie thread.
|
|
||||||
state := bytes.Fields(stat)[2]
|
|
||||||
return !(len(state) == 1 && state[0] == 'Z'), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func getcwd() (string, error) {
|
|
||||||
if !syscall.ImplementsGetwd {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
// Use the syscall to get the current working directory.
|
|
||||||
// This is imperative for checking for OS thread state
|
|
||||||
// after an unshare since os.Getwd might just check the
|
|
||||||
// environment, or use some other mechanism.
|
|
||||||
var buf [4096]byte
|
|
||||||
n, err := syscall.Getcwd(buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
// Subtract one for null terminator.
|
|
||||||
return string(buf[:n-1]), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unshareFs() error {
|
|
||||||
return syscall.Unshare(syscall.CLONE_FS)
|
|
||||||
}
|
|
||||||
|
|
||||||
func chdir(path string) error {
|
|
||||||
return syscall.Chdir(path)
|
|
||||||
}
|
|
||||||
|
|
59
src/runtime/testdata/testprog/syscalls_linux.go
vendored
Normal file
59
src/runtime/testdata/testprog/syscalls_linux.go
vendored
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func gettid() int {
|
||||||
|
return syscall.Gettid()
|
||||||
|
}
|
||||||
|
|
||||||
|
func tidExists(tid int) (exists, supported bool) {
|
||||||
|
stat, err := ioutil.ReadFile(fmt.Sprintf("/proc/self/task/%d/stat", tid))
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, true
|
||||||
|
}
|
||||||
|
// Check if it's a zombie thread.
|
||||||
|
state := bytes.Fields(stat)[2]
|
||||||
|
return !(len(state) == 1 && state[0] == 'Z'), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func getcwd() (string, error) {
|
||||||
|
if !syscall.ImplementsGetwd {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
// Use the syscall to get the current working directory.
|
||||||
|
// This is imperative for checking for OS thread state
|
||||||
|
// after an unshare since os.Getwd might just check the
|
||||||
|
// environment, or use some other mechanism.
|
||||||
|
var buf [4096]byte
|
||||||
|
n, err := syscall.Getcwd(buf[:])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// Subtract one for null terminator.
|
||||||
|
return string(buf[:n-1]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unshareFs() error {
|
||||||
|
err := syscall.Unshare(syscall.CLONE_FS)
|
||||||
|
if err != nil {
|
||||||
|
errno, ok := err.(syscall.Errno)
|
||||||
|
if ok && errno == syscall.EPERM {
|
||||||
|
return errNotPermitted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func chdir(path string) error {
|
||||||
|
return syscall.Chdir(path)
|
||||||
|
}
|
Loading…
Reference in a new issue