Skip to content

Commit 29edf0f

Browse files
author
Bryan C. Mills
committed
runtime: poll libc to deliver signals under TSAN
fixes #18717 Change-Id: I7244463d2e7489e0b0fe3b74c4b782e71210beb2 Reviewed-on: https://go-review.googlesource.com/35494 Run-TryBot: Bryan Mills <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent d71f36b commit 29edf0f

File tree

8 files changed

+122
-4
lines changed

8 files changed

+122
-4
lines changed

misc/cgo/testsanitizers/test.bash

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,9 @@ if test "$tsan" = "yes"; then
198198

199199
# This test requires rebuilding runtime/cgo with -fsanitize=thread.
200200
testtsan tsan7.go "CGO_CFLAGS=-fsanitize=thread CGO_LDFLAGS=-fsanitize=thread" "-installsuffix=tsan"
201+
202+
# This test requires rebuilding runtime/cgo with -fsanitize=thread.
203+
testtsan tsan10.go "CGO_CFLAGS=-fsanitize=thread CGO_LDFLAGS=-fsanitize=thread" "-installsuffix=tsan"
201204
fi
202205
fi
203206

misc/cgo/testsanitizers/tsan10.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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+
// This program hung when run under the C/C++ ThreadSanitizer.
8+
// TSAN defers asynchronous signals until the signaled thread calls into libc.
9+
// Since the Go runtime makes direct futex syscalls, Go runtime threads could
10+
// run for an arbitrarily long time without triggering the libc interceptors.
11+
// See https://golang.org/issue/18717.
12+
13+
import (
14+
"os"
15+
"os/signal"
16+
"syscall"
17+
)
18+
19+
/*
20+
#cgo CFLAGS: -g -fsanitize=thread
21+
#cgo LDFLAGS: -g -fsanitize=thread
22+
*/
23+
import "C"
24+
25+
func main() {
26+
c := make(chan os.Signal, 1)
27+
signal.Notify(c, syscall.SIGUSR1)
28+
defer signal.Stop(c)
29+
syscall.Kill(syscall.Getpid(), syscall.SIGUSR1)
30+
<-c
31+
}

src/runtime/cgo.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import "unsafe"
1616
//go:linkname _cgo_notify_runtime_init_done _cgo_notify_runtime_init_done
1717
//go:linkname _cgo_callers _cgo_callers
1818
//go:linkname _cgo_set_context_function _cgo_set_context_function
19+
//go:linkname _cgo_yield _cgo_yield
1920

2021
var (
2122
_cgo_init unsafe.Pointer
@@ -24,6 +25,7 @@ var (
2425
_cgo_notify_runtime_init_done unsafe.Pointer
2526
_cgo_callers unsafe.Pointer
2627
_cgo_set_context_function unsafe.Pointer
28+
_cgo_yield unsafe.Pointer
2729
)
2830

2931
// iscgo is set to true by the runtime/cgo package

src/runtime/cgo/callbacks.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,5 +92,15 @@ var _cgo_notify_runtime_init_done = &x_cgo_notify_runtime_init_done
9292
var x_cgo_set_context_function byte
9393
var _cgo_set_context_function = &x_cgo_set_context_function
9494

95+
// Calls a libc function to execute background work injected via libc
96+
// interceptors, such as processing pending signals under the thread
97+
// sanitizer.
98+
//
99+
// Left as a nil pointer if no libc interceptors are expected.
100+
101+
//go:cgo_import_static _cgo_yield
102+
//go:linkname _cgo_yield _cgo_yield
103+
var _cgo_yield unsafe.Pointer
104+
95105
//go:cgo_export_static _cgo_topofstack
96106
//go:cgo_export_dynamic _cgo_topofstack

src/runtime/cgo/gcc_util.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,39 @@ x_cgo_thread_start(ThreadStart *arg)
2222

2323
_cgo_sys_thread_start(ts); /* OS-dependent half */
2424
}
25+
26+
#ifndef CGO_TSAN
27+
void(* const _cgo_yield)() = NULL;
28+
#else
29+
30+
#include <string.h>
31+
32+
/*
33+
Stub for allowing libc interceptors to execute.
34+
35+
_cgo_yield is set to NULL if we do not expect libc interceptors to exist.
36+
*/
37+
static void
38+
x_cgo_yield()
39+
{
40+
/*
41+
The libc function(s) we call here must form a no-op and include at least one
42+
call that triggers TSAN to process pending asynchronous signals.
43+
44+
sleep(0) would be fine, but it's not portable C (so it would need more header
45+
guards).
46+
free(NULL) has a fast-path special case in TSAN, so it doesn't
47+
trigger signal delivery.
48+
free(malloc(0)) would work (triggering the interceptors in malloc), but
49+
it also runs a bunch of user-supplied malloc hooks.
50+
51+
So we choose strncpy(_, _, 0): it requires an extra header,
52+
but it's standard and should be very efficient.
53+
*/
54+
char nothing = 0;
55+
strncpy(&nothing, &nothing, 0);
56+
}
57+
58+
void(* const _cgo_yield)() = &x_cgo_yield;
59+
60+
#endif /* GO_TSAN */

src/runtime/lock_futex.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,17 @@ func notesleep(n *note) {
140140
if gp != gp.m.g0 {
141141
throw("notesleep not on g0")
142142
}
143+
ns := int64(-1)
144+
if _cgo_yield != nil {
145+
// Sleep for an arbitrary-but-moderate interval to poll libc interceptors.
146+
ns = 10e6
147+
}
143148
for atomic.Load(key32(&n.key)) == 0 {
144149
gp.m.blocked = true
145-
futexsleep(key32(&n.key), 0, -1)
150+
futexsleep(key32(&n.key), 0, ns)
151+
if _cgo_yield != nil {
152+
asmcgocall(_cgo_yield, nil)
153+
}
146154
gp.m.blocked = false
147155
}
148156
}
@@ -156,9 +164,16 @@ func notetsleep_internal(n *note, ns int64) bool {
156164
gp := getg()
157165

158166
if ns < 0 {
167+
if _cgo_yield != nil {
168+
// Sleep for an arbitrary-but-moderate interval to poll libc interceptors.
169+
ns = 10e6
170+
}
159171
for atomic.Load(key32(&n.key)) == 0 {
160172
gp.m.blocked = true
161-
futexsleep(key32(&n.key), 0, -1)
173+
futexsleep(key32(&n.key), 0, ns)
174+
if _cgo_yield != nil {
175+
asmcgocall(_cgo_yield, nil)
176+
}
162177
gp.m.blocked = false
163178
}
164179
return true

src/runtime/lock_sema.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,16 @@ func notesleep(n *note) {
163163
}
164164
// Queued. Sleep.
165165
gp.m.blocked = true
166-
semasleep(-1)
166+
if _cgo_yield == nil {
167+
semasleep(-1)
168+
} else {
169+
// Sleep for an arbitrary-but-moderate interval to poll libc interceptors.
170+
const ns = 10e6
171+
for atomic.Loaduintptr(&n.key) == 0 {
172+
semasleep(ns)
173+
asmcgocall(_cgo_yield, nil)
174+
}
175+
}
167176
gp.m.blocked = false
168177
}
169178

@@ -186,7 +195,16 @@ func notetsleep_internal(n *note, ns int64, gp *g, deadline int64) bool {
186195
if ns < 0 {
187196
// Queued. Sleep.
188197
gp.m.blocked = true
189-
semasleep(-1)
198+
if _cgo_yield == nil {
199+
semasleep(-1)
200+
} else {
201+
// Sleep for an arbitrary-but-moderate interval to poll libc interceptors.
202+
const ns = 10e6
203+
for atomic.Loaduintptr(&n.key) == 0 {
204+
semasleep(ns)
205+
asmcgocall(_cgo_yield, nil)
206+
}
207+
}
190208
gp.m.blocked = false
191209
return true
192210
}

src/runtime/proc.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1899,6 +1899,9 @@ top:
18991899
ready(gp, 0, true)
19001900
}
19011901
}
1902+
if _cgo_yield != nil {
1903+
asmcgocall(_cgo_yield, nil)
1904+
}
19021905

19031906
// local runq
19041907
if gp, inheritTime := runqget(_p_); gp != nil {

0 commit comments

Comments
 (0)