Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/runtime/crash_cgo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,19 @@ func TestCgoLockOSThreadExit(t *testing.T) {
testLockOSThreadExit(t, "testprogcgo")
}

func TestCgoLockOSThreadParkExit(t *testing.T) {
if runtime.GOOS != "aix" {
// Elsewhere the per-M semaphore is not a limited
// kernel object, so a leak does not cause a failure.
t.Skip("skipping AIX-only test")
}
t.Parallel()
got := runTestProg(t, "testprogcgo", "LockOSThreadParkExit")
if want := "OK\n"; got != want {
t.Errorf("want %q, got %q", want, got)
}
}

func TestWindowsStackMemoryCgo(t *testing.T) {
if runtime.GOOS != "windows" {
t.Skip("skipping windows specific test")
Expand Down
17 changes: 17 additions & 0 deletions src/runtime/os2_aix.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var (
//go:cgo_import_dynamic libc_clock_gettime clock_gettime "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_close close "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_exit _exit "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_free free "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_getpid getpid "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_getsystemcfg getsystemcfg "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_kill kill "libc.a/shr_64.o"
Expand All @@ -46,6 +47,7 @@ var (
//go:cgo_import_dynamic libc_raise raise "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_read read "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_sched_yield sched_yield "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_sem_destroy sem_destroy "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_sem_init sem_init "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_sem_post sem_post "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_sem_timedwait sem_timedwait "libc.a/shr_64.o"
Expand Down Expand Up @@ -77,6 +79,7 @@ var (
//go:linkname libc_clock_gettime libc_clock_gettime
//go:linkname libc_close libc_close
//go:linkname libc_exit libc_exit
//go:linkname libc_free libc_free
//go:linkname libc_getpid libc_getpid
//go:linkname libc_getsystemcfg libc_getsystemcfg
//go:linkname libc_kill libc_kill
Expand All @@ -90,6 +93,7 @@ var (
//go:linkname libc_raise libc_raise
//go:linkname libc_read libc_read
//go:linkname libc_sched_yield libc_sched_yield
//go:linkname libc_sem_destroy libc_sem_destroy
//go:linkname libc_sem_init libc_sem_init
//go:linkname libc_sem_post libc_sem_post
//go:linkname libc_sem_timedwait libc_sem_timedwait
Expand Down Expand Up @@ -123,6 +127,7 @@ var (
libc_clock_gettime,
libc_close,
libc_exit,
libc_free,
libc_getpid,
libc_getsystemcfg,
libc_kill,
Expand All @@ -136,6 +141,7 @@ var (
libc_raise,
libc_read,
libc_sched_yield,
libc_sem_destroy,
libc_sem_init,
libc_sem_post,
libc_sem_timedwait,
Expand Down Expand Up @@ -574,12 +580,23 @@ func malloc(size uintptr) unsafe.Pointer {
return unsafe.Pointer(r)
}

//go:nosplit
func free(p unsafe.Pointer) {
syscall1(&libc_free, uintptr(p))
}

//go:nosplit
func sem_init(sem *semt, pshared int32, value uint32) int32 {
r, _ := syscall3(&libc_sem_init, uintptr(unsafe.Pointer(sem)), uintptr(pshared), uintptr(value))
return int32(r)
}

//go:nosplit
func sem_destroy(sem *semt) int32 {
r, _ := syscall1(&libc_sem_destroy, uintptr(unsafe.Pointer(sem)))
return int32(r)
}

//go:nosplit
func sem_wait(sem *semt) (int32, int32) {
r, err := syscall1(&libc_sem_wait, uintptr(unsafe.Pointer(sem)))
Expand Down
11 changes: 11 additions & 0 deletions src/runtime/os_aix.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,17 @@ func unminit() {
//
//go:nowritebarrierrec
func mdestroy(mp *m) {
if mp.waitsema != 0 {
// semacreate allocated the semaphore on the C heap and
// registered it with sem_init. On AIX, sem_init fails
// with ENOSPC once SEM_NSEMS_MAX semaphores exist (32768
// on AIX 7.3), so release both the semaphore and the
// storage when the M goes away.
sem := (*semt)(unsafe.Pointer(mp.waitsema))
sem_destroy(sem)
free(unsafe.Pointer(sem))
mp.waitsema = 0
}
}

// tstart is a function descriptor to _tstart defined in assembly.
Expand Down
31 changes: 31 additions & 0 deletions src/runtime/testdata/testprogcgo/lockosthread_park.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2026 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 "runtime"

func init() {
register("LockOSThreadParkExit", LockOSThreadParkExit)
}

// LockOSThreadParkExit repeatedly starts a goroutine that locks its
// thread, parks once, and returns while still locked, so the runtime
// exits the thread. A parked M owns an OS semaphore on some systems.
// On AIX, sem_init fails once SEM_NSEMS_MAX semaphores exist (32768
// on AIX 7.3), so this fails there if the runtime does not release
// the semaphore when the thread exits.
func LockOSThreadParkExit() {
const n = 40000
for range n {
done := make(chan bool)
go func() {
runtime.LockOSThread()
runtime.Gosched()
close(done)
}()
<-done
}
println("OK")
}
Loading