runtime: use a pipe to wake up signal_recv on Darwin
The implementation of semaphores, and therefore notes, used on Darwin
is not async-signal-safe. The runtime has one case where a note needs
to be woken up from a signal handler: the call to notewakeup in sigsend.
That notewakeup call is only called on a single note, and it doesn't
need the full functionality of notes: nothing ever does a timed wait on it.
So change that one note to use a different implementation on Darwin,
based on a pipe. This lets the wakeup code use the write call, which is
async-signal-safe.
Fixes #31264
Change-Id: If705072d7a961dd908ea9d639c8d12b222c64806
Reviewed-on: https://go-review.googlesource.com/c/go/+/184169
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
diff --git a/src/runtime/defs_darwin.go b/src/runtime/defs_darwin.go
index 61ae7a4..0cd133f 100644
--- a/src/runtime/defs_darwin.go
+++ b/src/runtime/defs_darwin.go
@@ -116,7 +116,11 @@
PTHREAD_CREATE_DETACHED = C.PTHREAD_CREATE_DETACHED
F_SETFD = C.F_SETFD
+ F_GETFL = C.F_GETFL
+ F_SETFL = C.F_SETFL
FD_CLOEXEC = C.FD_CLOEXEC
+
+ O_NONBLOCK = C.O_NONBLOCK
)
type StackT C.struct_sigaltstack
diff --git a/src/runtime/defs_darwin_386.go b/src/runtime/defs_darwin_386.go
index 43dc08a..83928e7 100644
--- a/src/runtime/defs_darwin_386.go
+++ b/src/runtime/defs_darwin_386.go
@@ -94,7 +94,11 @@
_PTHREAD_CREATE_DETACHED = 0x2
_F_SETFD = 0x2
+ _F_GETFL = 0x3
+ _F_SETFL = 0x4
_FD_CLOEXEC = 0x1
+
+ _O_NONBLOCK = 4
)
type stackt struct {
diff --git a/src/runtime/defs_darwin_amd64.go b/src/runtime/defs_darwin_amd64.go
index 59b0eff..45c34a8 100644
--- a/src/runtime/defs_darwin_amd64.go
+++ b/src/runtime/defs_darwin_amd64.go
@@ -94,7 +94,11 @@
_PTHREAD_CREATE_DETACHED = 0x2
_F_SETFD = 0x2
+ _F_GETFL = 0x3
+ _F_SETFL = 0x4
_FD_CLOEXEC = 0x1
+
+ _O_NONBLOCK = 4
)
type stackt struct {
diff --git a/src/runtime/defs_darwin_arm.go b/src/runtime/defs_darwin_arm.go
index 243f52a..5e2af97 100644
--- a/src/runtime/defs_darwin_arm.go
+++ b/src/runtime/defs_darwin_arm.go
@@ -96,7 +96,11 @@
_PTHREAD_CREATE_DETACHED = 0x2
_F_SETFD = 0x2
+ _F_GETFL = 0x3
+ _F_SETFL = 0x4
_FD_CLOEXEC = 0x1
+
+ _O_NONBLOCK = 4
)
type stackt struct {
diff --git a/src/runtime/defs_darwin_arm64.go b/src/runtime/defs_darwin_arm64.go
index 7056074..f673eb7 100644
--- a/src/runtime/defs_darwin_arm64.go
+++ b/src/runtime/defs_darwin_arm64.go
@@ -94,7 +94,11 @@
_PTHREAD_CREATE_DETACHED = 0x2
_F_SETFD = 0x2
+ _F_GETFL = 0x3
+ _F_SETFL = 0x4
_FD_CLOEXEC = 0x1
+
+ _O_NONBLOCK = 4
)
type stackt struct {
diff --git a/src/runtime/os_darwin.go b/src/runtime/os_darwin.go
index 819aaac..1614b66 100644
--- a/src/runtime/os_darwin.go
+++ b/src/runtime/os_darwin.go
@@ -75,6 +75,52 @@
pthread_mutex_unlock(&mp.mutex)
}
+// The read and write file descriptors used by the sigNote functions.
+var sigNoteRead, sigNoteWrite int32
+
+// sigNoteSetup initializes an async-signal-safe note.
+//
+// The current implementation of notes on Darwin is not async-signal-safe,
+// because the functions pthread_mutex_lock, pthread_cond_signal, and
+// pthread_mutex_unlock, called by semawakeup, are not async-signal-safe.
+// There is only one case where we need to wake up a note from a signal
+// handler: the sigsend function. The signal handler code does not require
+// all the features of notes: it does not need to do a timed wait.
+// This is a separate implementation of notes, based on a pipe, that does
+// not support timed waits but is async-signal-safe.
+func sigNoteSetup(*note) {
+ if sigNoteRead != 0 || sigNoteWrite != 0 {
+ throw("duplicate sigNoteSetup")
+ }
+ var errno int32
+ sigNoteRead, sigNoteWrite, errno = pipe()
+ if errno != 0 {
+ throw("pipe failed")
+ }
+ closeonexec(sigNoteRead)
+ closeonexec(sigNoteWrite)
+
+ // Make the write end of the pipe non-blocking, so that if the pipe
+ // buffer is somehow full we will not block in the signal handler.
+ // Leave the read end of the pipe blocking so that we will block
+ // in sigNoteSleep.
+ setNonblock(sigNoteWrite)
+}
+
+// sigNoteWakeup wakes up a thread sleeping on a note created by sigNoteSetup.
+func sigNoteWakeup(*note) {
+ var b byte
+ write(uintptr(sigNoteWrite), unsafe.Pointer(&b), 1)
+}
+
+// sigNoteSleep waits for a note created by sigNoteSetup to be woken.
+func sigNoteSleep(*note) {
+ entersyscallblock()
+ var b byte
+ read(sigNoteRead, unsafe.Pointer(&b), 1)
+ exitsyscall()
+}
+
// BSD interface for threading.
func osinit() {
// pthread_create delayed until end of goenvs so that we
diff --git a/src/runtime/sigqueue.go b/src/runtime/sigqueue.go
index a425433..b2ebb2b 100644
--- a/src/runtime/sigqueue.go
+++ b/src/runtime/sigqueue.go
@@ -105,6 +105,10 @@
break Send
case sigReceiving:
if atomic.Cas(&sig.state, sigReceiving, sigIdle) {
+ if GOOS == "darwin" {
+ sigNoteWakeup(&sig.note)
+ break Send
+ }
notewakeup(&sig.note)
break Send
}
@@ -136,6 +140,10 @@
throw("signal_recv: inconsistent state")
case sigIdle:
if atomic.Cas(&sig.state, sigIdle, sigReceiving) {
+ if GOOS == "darwin" {
+ sigNoteSleep(&sig.note)
+ break Receive
+ }
notetsleepg(&sig.note, -1)
noteclear(&sig.note)
break Receive
@@ -188,6 +196,10 @@
// to use for initialization. It does not pass
// signal information in m.
sig.inuse = true // enable reception of signals; cannot disable
+ if GOOS == "darwin" {
+ sigNoteSetup(&sig.note)
+ return
+ }
noteclear(&sig.note)
return
}
diff --git a/src/runtime/sigqueue_note.go b/src/runtime/sigqueue_note.go
new file mode 100644
index 0000000..16aeeb2
--- /dev/null
+++ b/src/runtime/sigqueue_note.go
@@ -0,0 +1,25 @@
+// Copyright 2019 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.
+
+// The current implementation of notes on Darwin is not async-signal-safe,
+// so on Darwin the sigqueue code uses different functions to wake up the
+// signal_recv thread. This file holds the non-Darwin implementations of
+// those functions. These functions will never be called.
+
+// +build !darwin
+// +build !plan9
+
+package runtime
+
+func sigNoteSetup(*note) {
+ throw("sigNoteSetup")
+}
+
+func sigNoteSleep(*note) {
+ throw("sigNoteSleep")
+}
+
+func sigNoteWakeup(*note) {
+ throw("sigNoteWakeup")
+}
diff --git a/src/runtime/sys_darwin.go b/src/runtime/sys_darwin.go
index b50d441..376f76d 100644
--- a/src/runtime/sys_darwin.go
+++ b/src/runtime/sys_darwin.go
@@ -197,6 +197,13 @@
}
func read_trampoline()
+func pipe() (r, w int32, errno int32) {
+ var p [2]int32
+ errno = libcCall(unsafe.Pointer(funcPC(pipe_trampoline)), noescape(unsafe.Pointer(&p)))
+ return p[0], p[1], errno
+}
+func pipe_trampoline()
+
//go:nosplit
//go:cgo_unsafe_args
func closefd(fd int32) int32 {
@@ -395,6 +402,12 @@
fcntl(fd, _F_SETFD, _FD_CLOEXEC)
}
+//go:nosplit
+func setNonblock(fd int32) {
+ flags := fcntl(fd, _F_GETFL, 0)
+ fcntl(fd, _F_SETFL, flags|_O_NONBLOCK)
+}
+
// Tell the linker that the libc_* functions are to be found
// in a system library, with the libc_ prefix missing.
@@ -409,6 +422,7 @@
//go:cgo_import_dynamic libc_close close "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic libc_read read "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic libc_write write "/usr/lib/libSystem.B.dylib"
+//go:cgo_import_dynamic libc_pipe pipe "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic libc_mmap mmap "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic libc_munmap munmap "/usr/lib/libSystem.B.dylib"
diff --git a/src/runtime/sys_darwin_386.s b/src/runtime/sys_darwin_386.s
index 9a0b360..ac5f4e4 100644
--- a/src/runtime/sys_darwin_386.s
+++ b/src/runtime/sys_darwin_386.s
@@ -84,6 +84,21 @@
POPL BP
RET
+TEXT runtime·pipe_trampoline(SB),NOSPLIT,$0
+ PUSHL BP
+ MOVL SP, BP
+ SUBL $8, SP
+ MOVL 16(SP), CX // arg 1 pipefd
+ MOVL AX, 0(SP)
+ CALL libc_pipe(SB)
+ TESTL AX, AX
+ JEQ 3(PC)
+ CALL libc_error(SB) // return negative errno value
+ NEGL AX
+ MOVL BP, SP
+ POPL BP
+ RET
+
TEXT runtime·mmap_trampoline(SB),NOSPLIT,$0
PUSHL BP
MOVL SP, BP
diff --git a/src/runtime/sys_darwin_amd64.s b/src/runtime/sys_darwin_amd64.s
index bbe6bc1..87c8db8 100644
--- a/src/runtime/sys_darwin_amd64.s
+++ b/src/runtime/sys_darwin_amd64.s
@@ -59,6 +59,17 @@
POPQ BP
RET
+TEXT runtime·pipe_trampoline(SB),NOSPLIT,$0
+ PUSHQ BP
+ MOVQ SP, BP
+ CALL libc_pipe(SB) // pointer already in DI
+ TESTL AX, AX
+ JEQ 3(PC)
+ CALL libc_error(SB) // return negative errno value
+ NEGL AX
+ POPQ BP
+ RET
+
TEXT runtime·setitimer_trampoline(SB),NOSPLIT,$0
PUSHQ BP
MOVQ SP, BP
diff --git a/src/runtime/sys_darwin_arm.s b/src/runtime/sys_darwin_arm.s
index b1c22b3..996f802 100644
--- a/src/runtime/sys_darwin_arm.s
+++ b/src/runtime/sys_darwin_arm.s
@@ -41,6 +41,14 @@
BL libc_read(SB)
RET
+TEXT runtime·pipe_trampoline(SB),NOSPLIT,$0
+ BL libc_pipe(SB) // pointer already in R0
+ CMP $0, R0
+ BEQ 3(PC)
+ BL libc_error(SB) // return negative errno value
+ RSB $0, R0, R0
+ RET
+
TEXT runtime·exit_trampoline(SB),NOSPLIT|NOFRAME,$0
MOVW 0(R0), R0 // arg 0 code
BL libc_exit(SB)
diff --git a/src/runtime/sys_darwin_arm64.s b/src/runtime/sys_darwin_arm64.s
index af03af3..ac3ca74 100644
--- a/src/runtime/sys_darwin_arm64.s
+++ b/src/runtime/sys_darwin_arm64.s
@@ -44,6 +44,14 @@
BL libc_read(SB)
RET
+TEXT runtime·pipe_trampoline(SB),NOSPLIT,$0
+ BL libc_pipe(SB) // pointer already in R0
+ CMP $0, R0
+ BEQ 3(PC)
+ BL libc_error(SB) // return negative errno value
+ NEG R0, R0
+ RET
+
TEXT runtime·exit_trampoline(SB),NOSPLIT|NOFRAME,$0
MOVW 0(R0), R0
BL libc_exit(SB)