github.com/guyezi/gofrontend@v0.0.0-20200228202240-7a62a49e62c0/libgo/go/runtime/testdata/testprog/lockosthread.go (about) 1 // Copyright 2017 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 package main 6 7 import ( 8 "os" 9 "runtime" 10 "time" 11 ) 12 13 var mainTID int 14 15 func init() { 16 registerInit("LockOSThreadMain", func() { 17 // init is guaranteed to run on the main thread. 18 mainTID = gettid() 19 }) 20 register("LockOSThreadMain", LockOSThreadMain) 21 22 registerInit("LockOSThreadAlt", func() { 23 // Lock the OS thread now so main runs on the main thread. 24 runtime.LockOSThread() 25 }) 26 register("LockOSThreadAlt", LockOSThreadAlt) 27 28 registerInit("LockOSThreadAvoidsStatePropagation", func() { 29 // Lock the OS thread now so main runs on the main thread. 30 runtime.LockOSThread() 31 }) 32 register("LockOSThreadAvoidsStatePropagation", LockOSThreadAvoidsStatePropagation) 33 } 34 35 func LockOSThreadMain() { 36 // gettid only works on Linux, so on other platforms this just 37 // checks that the runtime doesn't do anything terrible. 38 39 // This requires GOMAXPROCS=1 from the beginning to reliably 40 // start a goroutine on the main thread. 41 if runtime.GOMAXPROCS(-1) != 1 { 42 println("requires GOMAXPROCS=1") 43 os.Exit(1) 44 } 45 46 ready := make(chan bool, 1) 47 go func() { 48 // Because GOMAXPROCS=1, this *should* be on the main 49 // thread. Stay there. 50 runtime.LockOSThread() 51 if mainTID != 0 && gettid() != mainTID { 52 println("failed to start goroutine on main thread") 53 os.Exit(1) 54 } 55 // Exit with the thread locked, which should exit the 56 // main thread. 57 ready <- true 58 }() 59 <-ready 60 time.Sleep(1 * time.Millisecond) 61 // Check that this goroutine is still running on a different 62 // thread. 63 if mainTID != 0 && gettid() == mainTID { 64 println("goroutine migrated to locked thread") 65 os.Exit(1) 66 } 67 println("OK") 68 } 69 70 func LockOSThreadAlt() { 71 // This is running locked to the main OS thread. 72 73 var subTID int 74 ready := make(chan bool, 1) 75 go func() { 76 // This goroutine must be running on a new thread. 77 runtime.LockOSThread() 78 subTID = gettid() 79 ready <- true 80 // Exit with the thread locked. 81 }() 82 <-ready 83 runtime.UnlockOSThread() 84 for i := 0; i < 100; i++ { 85 time.Sleep(1 * time.Millisecond) 86 // Check that this goroutine is running on a different thread. 87 if subTID != 0 && gettid() == subTID { 88 println("locked thread reused") 89 os.Exit(1) 90 } 91 exists, supported := tidExists(subTID) 92 if !supported || !exists { 93 goto ok 94 } 95 } 96 println("sub thread", subTID, "still running") 97 return 98 ok: 99 println("OK") 100 } 101 102 func LockOSThreadAvoidsStatePropagation() { 103 // This test is similar to LockOSThreadAlt in that it will detect if a thread 104 // which should have died is still running. However, rather than do this with 105 // thread IDs, it does this by unsharing state on that thread. This way, it 106 // also detects whether new threads were cloned from the dead thread, and not 107 // from a clean thread. Cloning from a locked thread is undesirable since 108 // cloned threads will inherit potentially unwanted OS state. 109 // 110 // unshareFs, getcwd, and chdir("/tmp") are only guaranteed to work on 111 // Linux, so on other platforms this just checks that the runtime doesn't 112 // do anything terrible. 113 // 114 // This is running locked to the main OS thread. 115 116 // GOMAXPROCS=1 makes this fail much more reliably if a tainted thread is 117 // cloned from. 118 if runtime.GOMAXPROCS(-1) != 1 { 119 println("requires GOMAXPROCS=1") 120 os.Exit(1) 121 } 122 123 if err := chdir("/"); err != nil { 124 println("failed to chdir:", err.Error()) 125 os.Exit(1) 126 } 127 // On systems other than Linux, cwd == "". 128 cwd, err := getcwd() 129 if err != nil { 130 println("failed to get cwd:", err.Error()) 131 os.Exit(1) 132 } 133 if cwd != "" && cwd != "/" { 134 println("unexpected cwd", cwd, " wanted /") 135 os.Exit(1) 136 } 137 138 ready := make(chan bool, 1) 139 go func() { 140 // This goroutine must be running on a new thread. 141 runtime.LockOSThread() 142 143 // Unshare details about the FS, like the CWD, with 144 // the rest of the process on this thread. 145 // On systems other than Linux, this is a no-op. 146 if err := unshareFs(); err != nil { 147 if err == errNotPermitted { 148 println("unshare not permitted") 149 os.Exit(0) 150 } 151 println("failed to unshare fs:", err.Error()) 152 os.Exit(1) 153 } 154 // Chdir to somewhere else on this thread. 155 // On systems other than Linux, this is a no-op. 156 if err := chdir("/tmp"); err != nil { 157 println("failed to chdir:", err.Error()) 158 os.Exit(1) 159 } 160 161 // The state on this thread is now considered "tainted", but it 162 // should no longer be observable in any other context. 163 164 ready <- true 165 // Exit with the thread locked. 166 }() 167 <-ready 168 169 // Spawn yet another goroutine and lock it. Since GOMAXPROCS=1, if 170 // for some reason state from the (hopefully dead) locked thread above 171 // propagated into a newly created thread (via clone), or that thread 172 // is actually being re-used, then we should get scheduled on such a 173 // thread with high likelihood. 174 done := make(chan bool) 175 go func() { 176 runtime.LockOSThread() 177 178 // Get the CWD and check if this is the same as the main thread's 179 // CWD. Every thread should share the same CWD. 180 // On systems other than Linux, wd == "". 181 wd, err := getcwd() 182 if err != nil { 183 println("failed to get cwd:", err.Error()) 184 os.Exit(1) 185 } 186 if wd != cwd { 187 println("bad state from old thread propagated after it should have died") 188 os.Exit(1) 189 } 190 <-done 191 192 runtime.UnlockOSThread() 193 }() 194 done <- true 195 runtime.UnlockOSThread() 196 println("OK") 197 }