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:
Michael Anthony Knyszek 2018-12-20 20:21:45 +00:00 committed by Michael Knyszek
parent 90dca98d33
commit 429bae7158
4 changed files with 78 additions and 53 deletions

View file

@ -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)
} }
} }

View file

@ -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)
} }

View file

@ -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)
}

View 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)
}