summaryrefslogtreecommitdiffstats
path: root/src/runtime/testdata/testprog/lockosthread.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
commitccd992355df7192993c666236047820244914598 (patch)
treef00fea65147227b7743083c6148396f74cd66935 /src/runtime/testdata/testprog/lockosthread.go
parentInitial commit. (diff)
downloadgolang-1.21-ccd992355df7192993c666236047820244914598.tar.xz
golang-1.21-ccd992355df7192993c666236047820244914598.zip
Adding upstream version 1.21.8.upstream/1.21.8
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/runtime/testdata/testprog/lockosthread.go')
-rw-r--r--src/runtime/testdata/testprog/lockosthread.go246
1 files changed, 246 insertions, 0 deletions
diff --git a/src/runtime/testdata/testprog/lockosthread.go b/src/runtime/testdata/testprog/lockosthread.go
new file mode 100644
index 0000000..90d98e4
--- /dev/null
+++ b/src/runtime/testdata/testprog/lockosthread.go
@@ -0,0 +1,246 @@
+// 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"
+ "sync"
+ "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)
+
+ registerInit("LockOSThreadAvoidsStatePropagation", func() {
+ // Lock the OS thread now so main runs on the main thread.
+ runtime.LockOSThread()
+ })
+ register("LockOSThreadAvoidsStatePropagation", LockOSThreadAvoidsStatePropagation)
+ register("LockOSThreadTemplateThreadRace", LockOSThreadTemplateThreadRace)
+}
+
+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")
+}
+
+func LockOSThreadAvoidsStatePropagation() {
+ // This test is similar to LockOSThreadAlt in that it will detect if a thread
+ // which should have died is still running. However, rather than do this with
+ // thread IDs, it does this by unsharing state on that thread. This way, it
+ // also detects whether new threads were cloned from the dead thread, and not
+ // from a clean thread. Cloning from a locked thread is undesirable since
+ // cloned threads will inherit potentially unwanted OS state.
+ //
+ // unshareFs, getcwd, and chdir("/tmp") are only guaranteed to work on
+ // Linux, so on other platforms this just checks that the runtime doesn't
+ // do anything terrible.
+ //
+ // This is running locked to the main OS thread.
+
+ // GOMAXPROCS=1 makes this fail much more reliably if a tainted thread is
+ // cloned from.
+ if runtime.GOMAXPROCS(-1) != 1 {
+ println("requires GOMAXPROCS=1")
+ os.Exit(1)
+ }
+
+ if err := chdir("/"); err != nil {
+ println("failed to chdir:", err.Error())
+ os.Exit(1)
+ }
+ // On systems other than Linux, cwd == "".
+ cwd, err := getcwd()
+ if err != nil {
+ println("failed to get cwd:", err.Error())
+ os.Exit(1)
+ }
+ if cwd != "" && cwd != "/" {
+ println("unexpected cwd", cwd, " wanted /")
+ os.Exit(1)
+ }
+
+ ready := make(chan bool, 1)
+ go func() {
+ // This goroutine must be running on a new thread.
+ runtime.LockOSThread()
+
+ // Unshare details about the FS, like the CWD, with
+ // the rest of the process on this thread.
+ // On systems other than Linux, this is a no-op.
+ if err := unshareFs(); err != nil {
+ if err == errNotPermitted {
+ println("unshare not permitted")
+ os.Exit(0)
+ }
+ println("failed to unshare fs:", err.Error())
+ os.Exit(1)
+ }
+ // Chdir to somewhere else on this thread.
+ // On systems other than Linux, this is a no-op.
+ if err := chdir(os.TempDir()); err != nil {
+ println("failed to chdir:", err.Error())
+ os.Exit(1)
+ }
+
+ // The state on this thread is now considered "tainted", but it
+ // should no longer be observable in any other context.
+
+ ready <- true
+ // Exit with the thread locked.
+ }()
+ <-ready
+
+ // Spawn yet another goroutine and lock it. Since GOMAXPROCS=1, if
+ // for some reason state from the (hopefully dead) locked thread above
+ // propagated into a newly created thread (via clone), or that thread
+ // is actually being re-used, then we should get scheduled on such a
+ // thread with high likelihood.
+ done := make(chan bool)
+ go func() {
+ runtime.LockOSThread()
+
+ // Get the CWD and check if this is the same as the main thread's
+ // CWD. Every thread should share the same CWD.
+ // On systems other than Linux, wd == "".
+ wd, err := getcwd()
+ if err != nil {
+ println("failed to get cwd:", err.Error())
+ os.Exit(1)
+ }
+ if wd != cwd {
+ println("bad state from old thread propagated after it should have died")
+ os.Exit(1)
+ }
+ <-done
+
+ runtime.UnlockOSThread()
+ }()
+ done <- true
+ runtime.UnlockOSThread()
+ println("OK")
+}
+
+func LockOSThreadTemplateThreadRace() {
+ // This test attempts to reproduce the race described in
+ // golang.org/issue/38931. To do so, we must have a stop-the-world
+ // (achieved via ReadMemStats) racing with two LockOSThread calls.
+ //
+ // While this test attempts to line up the timing, it is only expected
+ // to fail (and thus hang) around 2% of the time if the race is
+ // present.
+
+ // Ensure enough Ps to actually run everything in parallel. Though on
+ // <4 core machines, we are still at the whim of the kernel scheduler.
+ runtime.GOMAXPROCS(4)
+
+ go func() {
+ // Stop the world; race with LockOSThread below.
+ var m runtime.MemStats
+ for {
+ runtime.ReadMemStats(&m)
+ }
+ }()
+
+ // Try to synchronize both LockOSThreads.
+ start := time.Now().Add(10 * time.Millisecond)
+
+ var wg sync.WaitGroup
+ wg.Add(2)
+
+ for i := 0; i < 2; i++ {
+ go func() {
+ for time.Now().Before(start) {
+ }
+
+ // Add work to the local runq to trigger early startm
+ // in handoffp.
+ go func() {}()
+
+ runtime.LockOSThread()
+ runtime.Gosched() // add a preemption point.
+ wg.Done()
+ }()
+ }
+
+ wg.Wait()
+ // If both LockOSThreads completed then we did not hit the race.
+ println("OK")
+}