-
Notifications
You must be signed in to change notification settings - Fork 17.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
runtime: terminate locked OS thread if its goroutine exits
runtime.LockOSThread is sometimes used when the caller intends to put the OS thread into an unusual state. In this case, we never want to return this thread to the runtime thread pool. However, currently exiting the goroutine implicitly unlocks its OS thread. Fix this by terminating the locked OS thread when its goroutine exits, rather than simply returning it to the pool. Fixes #20395. Change-Id: I3dcec63b200957709965f7240dc216fa84b62ad9 Reviewed-on: https://go-review.googlesource.com/46038 Run-TryBot: Austin Clements <austin@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Keith Randall <khr@golang.org>
- Loading branch information
Showing
9 changed files
with
303 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// 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. | ||
|
||
// +build linux | ||
|
||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// 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. | ||
|
||
// +build !linux | ||
|
||
package main | ||
|
||
func gettid() int { | ||
return 0 | ||
} | ||
|
||
func tidExists(tid int) (exists, supported bool) { | ||
return false, false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
// 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 ( | ||
"os" | ||
"runtime" | ||
"time" | ||
) | ||
|
||
var mainTID int | ||
|
||
func init() { | ||
registerInit("LockOSThreadMain", func() { | ||
// init is guaranteed to run on the main thread. | ||
mainTID = gettid() | ||
}) | ||
register("LockOSThreadMain", LockOSThreadMain) | ||
|
||
registerInit("LockOSThreadAlt", func() { | ||
// Lock the OS thread now so main runs on the main thread. | ||
runtime.LockOSThread() | ||
}) | ||
register("LockOSThreadAlt", LockOSThreadAlt) | ||
} | ||
|
||
func LockOSThreadMain() { | ||
// gettid only works on Linux, so on other platforms this just | ||
// checks that the runtime doesn't do anything terrible. | ||
|
||
// This requires GOMAXPROCS=1 from the beginning to reliably | ||
// start a goroutine on the main thread. | ||
if runtime.GOMAXPROCS(-1) != 1 { | ||
println("requires GOMAXPROCS=1") | ||
os.Exit(1) | ||
} | ||
|
||
ready := make(chan bool, 1) | ||
go func() { | ||
// Because GOMAXPROCS=1, this *should* be on the main | ||
// thread. Stay there. | ||
runtime.LockOSThread() | ||
if mainTID != 0 && gettid() != mainTID { | ||
println("failed to start goroutine on main thread") | ||
os.Exit(1) | ||
} | ||
// Exit with the thread locked, which should exit the | ||
// main thread. | ||
ready <- true | ||
}() | ||
<-ready | ||
time.Sleep(1 * time.Millisecond) | ||
// Check that this goroutine is still running on a different | ||
// thread. | ||
if mainTID != 0 && gettid() == mainTID { | ||
println("goroutine migrated to locked thread") | ||
os.Exit(1) | ||
} | ||
println("OK") | ||
} | ||
|
||
func LockOSThreadAlt() { | ||
// This is running locked to the main OS thread. | ||
|
||
var subTID int | ||
ready := make(chan bool, 1) | ||
go func() { | ||
// This goroutine must be running on a new thread. | ||
runtime.LockOSThread() | ||
subTID = gettid() | ||
ready <- true | ||
// Exit with the thread locked. | ||
}() | ||
<-ready | ||
runtime.UnlockOSThread() | ||
for i := 0; i < 100; i++ { | ||
time.Sleep(1 * time.Millisecond) | ||
// Check that this goroutine is running on a different thread. | ||
if subTID != 0 && gettid() == subTID { | ||
println("locked thread reused") | ||
os.Exit(1) | ||
} | ||
exists, supported := tidExists(subTID) | ||
if !supported || !exists { | ||
goto ok | ||
} | ||
} | ||
println("sub thread", subTID, "still running") | ||
return | ||
ok: | ||
println("OK") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// 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. | ||
|
||
// +build !plan9,!windows | ||
|
||
#include <stdint.h> | ||
|
||
uint32_t threadExited; | ||
|
||
void setExited(void *x) { | ||
__sync_fetch_and_add(&threadExited, 1); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
// 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. | ||
|
||
// +build !plan9,!windows | ||
|
||
package main | ||
|
||
import ( | ||
"os" | ||
"runtime" | ||
"sync/atomic" | ||
"time" | ||
"unsafe" | ||
) | ||
|
||
/* | ||
#include <pthread.h> | ||
#include <stdint.h> | ||
extern uint32_t threadExited; | ||
void setExited(void *x); | ||
*/ | ||
import "C" | ||
|
||
var mainThread C.pthread_t | ||
|
||
func init() { | ||
registerInit("LockOSThreadMain", func() { | ||
// init is guaranteed to run on the main thread. | ||
mainThread = C.pthread_self() | ||
}) | ||
register("LockOSThreadMain", LockOSThreadMain) | ||
|
||
registerInit("LockOSThreadAlt", func() { | ||
// Lock the OS thread now so main runs on the main thread. | ||
runtime.LockOSThread() | ||
}) | ||
register("LockOSThreadAlt", LockOSThreadAlt) | ||
} | ||
|
||
func LockOSThreadMain() { | ||
// This requires GOMAXPROCS=1 from the beginning to reliably | ||
// start a goroutine on the main thread. | ||
if runtime.GOMAXPROCS(-1) != 1 { | ||
println("requires GOMAXPROCS=1") | ||
os.Exit(1) | ||
} | ||
|
||
ready := make(chan bool, 1) | ||
go func() { | ||
// Because GOMAXPROCS=1, this *should* be on the main | ||
// thread. Stay there. | ||
runtime.LockOSThread() | ||
self := C.pthread_self() | ||
if C.pthread_equal(mainThread, self) == 0 { | ||
println("failed to start goroutine on main thread") | ||
os.Exit(1) | ||
} | ||
// Exit with the thread locked, which should exit the | ||
// main thread. | ||
ready <- true | ||
}() | ||
<-ready | ||
time.Sleep(1 * time.Millisecond) | ||
// Check that this goroutine is still running on a different | ||
// thread. | ||
self := C.pthread_self() | ||
if C.pthread_equal(mainThread, self) != 0 { | ||
println("goroutine migrated to locked thread") | ||
os.Exit(1) | ||
} | ||
println("OK") | ||
} | ||
|
||
func LockOSThreadAlt() { | ||
// This is running locked to the main OS thread. | ||
|
||
var subThread C.pthread_t | ||
ready := make(chan bool, 1) | ||
C.threadExited = 0 | ||
go func() { | ||
// This goroutine must be running on a new thread. | ||
runtime.LockOSThread() | ||
subThread = C.pthread_self() | ||
// Register a pthread destructor so we can tell this | ||
// thread has exited. | ||
var key C.pthread_key_t | ||
C.pthread_key_create(&key, (*[0]byte)(unsafe.Pointer(C.setExited))) | ||
C.pthread_setspecific(key, unsafe.Pointer(new(int))) | ||
ready <- true | ||
// Exit with the thread locked. | ||
}() | ||
<-ready | ||
for i := 0; i < 100; i++ { | ||
time.Sleep(1 * time.Millisecond) | ||
// Check that this goroutine is running on a different thread. | ||
self := C.pthread_self() | ||
if C.pthread_equal(subThread, self) != 0 { | ||
println("locked thread reused") | ||
os.Exit(1) | ||
} | ||
if atomic.LoadUint32((*uint32)(&C.threadExited)) != 0 { | ||
println("OK") | ||
return | ||
} | ||
} | ||
println("sub thread still running") | ||
os.Exit(1) | ||
} |