diff --git a/src/cmd/link/internal/ld/macho.go b/src/cmd/link/internal/ld/macho.go index 11cbf81486..d6c28e4790 100644 --- a/src/cmd/link/internal/ld/macho.go +++ b/src/cmd/link/internal/ld/macho.go @@ -479,8 +479,11 @@ func (ctxt *Link) domacho() { var version uint32 switch ctxt.Arch.Family { case sys.AMD64: - // The version must be at least 10.9; see golang.org/issues/30488. - version = 10<<16 | 9<<8 | 0<<0 // 10.9.0 + // This must be fairly recent for Apple signing (go.dev/issue/30488). + // Having too old a version here was also implicated in some problems + // calling into macOS libraries (go.dev/issue/56784). + // In general this can be the most recent supported macOS version. + version = 10<<16 | 13<<8 | 0<<0 // 10.13.0 case sys.ARM64: version = 11<<16 | 0<<8 | 0<<0 // 11.0.0 } diff --git a/src/runtime/os_darwin.go b/src/runtime/os_darwin.go index af5c18c301..c4f3bb6a81 100644 --- a/src/runtime/os_darwin.go +++ b/src/runtime/os_darwin.go @@ -136,6 +136,8 @@ func osinit() { ncpu = getncpu() physPageSize = getPageSize() + + osinit_hack() } func sysctlbynameInt32(name []byte) (int32, int32) { diff --git a/src/runtime/sys_darwin.go b/src/runtime/sys_darwin.go index 61b7f8c728..8bff695f57 100644 --- a/src/runtime/sys_darwin.go +++ b/src/runtime/sys_darwin.go @@ -179,6 +179,45 @@ func pthread_kill(t pthread, sig uint32) { } func pthread_kill_trampoline() +// osinit_hack is a clumsy hack to work around Apple libc bugs +// causing fork+exec to hang in the child process intermittently. +// See go.dev/issue/33565 and go.dev/issue/56784 for a few reports. +// +// The stacks obtained from the hung child processes are in +// libSystem_atfork_child, which is supposed to reinitialize various +// parts of the C library in the new process. +// +// One common stack dies in _notify_fork_child calling _notify_globals +// (inlined) calling _os_alloc_once, because _os_alloc_once detects that +// the once lock is held by the parent process and then calls +// _os_once_gate_corruption_abort. The allocation is setting up the +// globals for the notification subsystem. See the source code at [1]. +// To work around this, we can allocate the globals earlier in the Go +// program's lifetime, before any execs are involved, by calling any +// notify routine that is exported, calls _notify_globals, and doesn't do +// anything too expensive otherwise. notify_is_valid_token(0) fits the bill. +// +// The other common stack dies in xpc_atfork_child calling +// _objc_msgSend_uncached which ends up in +// WAITING_FOR_ANOTHER_THREAD_TO_FINISH_CALLING_+initialize. Of course, +// whatever thread the child is waiting for is in the parent process and +// is not going to finish anything in the child process. There is no +// public source code for these routines, so it is unclear exactly what +// the problem is. An Apple engineer suggests using xpc_date_create_from_current, +// which empirically does fix the problem. +// +// So osinit_hack_trampoline (in sys_darwin_$GOARCH.s) calls +// notify_is_valid_token(0) and xpc_date_create_from_current(), which makes the +// fork+exec hangs stop happening. If Apple fixes the libc bug in +// some future version of macOS, then we can remove this awful code. +// +//go:nosplit +func osinit_hack() { + libcCall(unsafe.Pointer(abi.FuncPCABI0(osinit_hack_trampoline)), nil) + return +} +func osinit_hack_trampoline() + // mmap is used to do low-level memory allocation via mmap. Don't allow stack // splits, since this function (used by sysAlloc) is called in a lot of low-level // parts of the runtime and callers often assume it won't acquire any locks. @@ -548,3 +587,6 @@ func setNonblock(fd int32) { //go:cgo_import_dynamic libc_pthread_cond_wait pthread_cond_wait "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic libc_pthread_cond_timedwait_relative_np pthread_cond_timedwait_relative_np "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic libc_pthread_cond_signal pthread_cond_signal "/usr/lib/libSystem.B.dylib" + +//go:cgo_import_dynamic libc_notify_is_valid_token notify_is_valid_token "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic libc_xpc_date_create_from_current xpc_date_create_from_current "/usr/lib/libSystem.B.dylib" diff --git a/src/runtime/sys_darwin_amd64.s b/src/runtime/sys_darwin_amd64.s index 369b12e8f9..6eaeeb915f 100644 --- a/src/runtime/sys_darwin_amd64.s +++ b/src/runtime/sys_darwin_amd64.s @@ -597,6 +597,15 @@ TEXT runtime·pthread_kill_trampoline(SB),NOSPLIT,$0 POPQ BP RET +TEXT runtime·osinit_hack_trampoline(SB),NOSPLIT,$0 + PUSHQ BP + MOVQ SP, BP + MOVQ $0, DI // arg 1 val + CALL libc_notify_is_valid_token(SB) + CALL libc_xpc_date_create_from_current(SB) + POPQ BP + RET + // syscall calls a function in libc on behalf of the syscall package. // syscall takes a pointer to a struct like: // struct { diff --git a/src/runtime/sys_darwin_arm64.s b/src/runtime/sys_darwin_arm64.s index 4fa99cc0f9..4a51fb3a86 100644 --- a/src/runtime/sys_darwin_arm64.s +++ b/src/runtime/sys_darwin_arm64.s @@ -458,6 +458,12 @@ TEXT runtime·pthread_setspecific_trampoline(SB),NOSPLIT,$0 BL libc_pthread_setspecific(SB) RET +TEXT runtime·osinit_hack_trampoline(SB),NOSPLIT,$0 + MOVD $0, R0 // arg 1 val + BL libc_notify_is_valid_token(SB) + BL libc_xpc_date_create_from_current(SB) + RET + // syscall calls a function in libc on behalf of the syscall package. // syscall takes a pointer to a struct like: // struct {