gatus/vendor/modernc.org/libc/pthreads.go
2021-10-03 22:15:20 -04:00

627 lines
14 KiB
Go

// Copyright 2021 The Libc Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//TODO darwin,amd64
//TODO darwin,arm64
//TODO freebsd,amd64
//TODO linux,386
//TODO linux,arm
//TODO linux,arm64
//TODO linux,s390x
//go:build linux && amd64
// +build linux,amd64
package libc // import "modernc.org/libc"
import (
"runtime"
"sync"
"sync/atomic"
"time"
"unsafe"
"modernc.org/libc/errno"
"modernc.org/libc/pthread"
"modernc.org/libc/sys/types"
ctime "modernc.org/libc/time"
)
var (
mutexes = map[uintptr]*mutex{}
mutexesMu sync.Mutex
threads = map[int32]*TLS{}
threadsMu sync.Mutex
threadKey pthread.Pthread_key_t
threadKeyDestructors = map[pthread.Pthread_key_t][]uintptr{} // key: []destructor
threadsKeysMu sync.Mutex
conds = map[uintptr]*cond{}
condsMu sync.Mutex
)
// Thread local storage.
type TLS struct {
errnop uintptr
pthreadData
stack stackHeader
ID int32
reentryGuard int32 // memgrind
stackHeaderBalance int32
}
var errno0 int32 // Temp errno for NewTLS
func NewTLS() *TLS {
return newTLS(false)
}
func newTLS(detached bool) *TLS {
id := atomic.AddInt32(&tid, 1)
t := &TLS{ID: id, errnop: uintptr(unsafe.Pointer(&errno0))}
t.pthreadData.init(t, detached)
if memgrind {
atomic.AddInt32(&tlsBalance, 1)
}
t.errnop = t.Alloc(int(unsafe.Sizeof(int32(0))))
*(*int32)(unsafe.Pointer(t.errnop)) = 0
return t
}
// Pthread specific part of a TLS.
type pthreadData struct {
done chan struct{}
kv map[pthread.Pthread_key_t]uintptr
retVal uintptr
wait chan struct{} // cond var interaction
detached bool
}
func (d *pthreadData) init(t *TLS, detached bool) {
d.detached = detached
d.wait = make(chan struct{}, 1)
if detached {
return
}
d.done = make(chan struct{})
threadsMu.Lock()
defer threadsMu.Unlock()
threads[t.ID] = t
}
// int pthread_attr_init(pthread_attr_t *attr);
func Xpthread_attr_init(t *TLS, pAttr uintptr) int32 {
*(*pthread.Pthread_attr_t)(unsafe.Pointer(pAttr)) = pthread.Pthread_attr_t{}
return 0
}
// int pthread_attr_destroy(pthread_attr_t *attr);
func Xpthread_attr_destroy(t *TLS, pAttr uintptr) int32 {
return 0
}
// int pthread_attr_setscope(pthread_attr_t *attr, int contentionscope);
func Xpthread_attr_setscope(t *TLS, pAttr uintptr, contentionScope int32) int32 {
switch contentionScope {
case pthread.PTHREAD_SCOPE_SYSTEM:
return 0
default:
panic(todo("", contentionScope))
}
}
// int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
func Xpthread_attr_setstacksize(t *TLS, attr uintptr, stackSize types.Size_t) int32 {
panic(todo(""))
}
// Go side data of pthread_cond_t.
type cond struct {
sync.Mutex
waiters map[*TLS]struct{}
}
func newCond() *cond {
return &cond{
waiters: map[*TLS]struct{}{},
}
}
func (c *cond) signal(all bool) int32 {
if c == nil {
return errno.EINVAL
}
c.Lock()
defer c.Unlock()
// The pthread_cond_broadcast() and pthread_cond_signal() functions shall have
// no effect if there are no threads currently blocked on cond.
for tls := range c.waiters {
tls.wait <- struct{}{}
delete(c.waiters, tls)
if !all {
break
}
}
return 0
}
// The pthread_cond_init() function shall initialize the condition variable
// referenced by cond with attributes referenced by attr. If attr is NULL, the
// default condition variable attributes shall be used; the effect is the same
// as passing the address of a default condition variable attributes object.
// Upon successful initialization, the state of the condition variable shall
// become initialized.
//
// If successful, the pthread_cond_destroy() and pthread_cond_init() functions
// shall return zero; otherwise, an error number shall be returned to indicate
// the error.
//
// int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
func Xpthread_cond_init(t *TLS, pCond, pAttr uintptr) int32 {
if pCond == 0 {
return errno.EINVAL
}
if pAttr != 0 {
panic(todo("%#x %#x", pCond, pAttr))
}
condsMu.Lock()
defer condsMu.Unlock()
conds[pCond] = newCond()
return 0
}
// int pthread_cond_destroy(pthread_cond_t *cond);
func Xpthread_cond_destroy(t *TLS, pCond uintptr) int32 {
if pCond == 0 {
return errno.EINVAL
}
condsMu.Lock()
defer condsMu.Unlock()
cond := conds[pCond]
if cond == nil {
return errno.EINVAL
}
cond.Lock()
defer cond.Unlock()
if len(cond.waiters) != 0 {
return errno.EBUSY
}
delete(conds, pCond)
return 0
}
// int pthread_cond_signal(pthread_cond_t *cond);
func Xpthread_cond_signal(t *TLS, pCond uintptr) int32 {
return condSignal(pCond, false)
}
// int pthread_cond_broadcast(pthread_cond_t *cond);
func Xpthread_cond_broadcast(t *TLS, pCond uintptr) int32 {
return condSignal(pCond, true)
}
func condSignal(pCond uintptr, all bool) int32 {
if pCond == 0 {
return errno.EINVAL
}
condsMu.Lock()
cond := conds[pCond]
condsMu.Unlock()
return cond.signal(all)
}
// int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
func Xpthread_cond_wait(t *TLS, pCond, pMutex uintptr) int32 {
if pCond == 0 {
return errno.EINVAL
}
condsMu.Lock()
cond := conds[pCond]
if cond == nil { // static initialized condition variables are valid
cond = newCond()
conds[pCond] = cond
}
cond.Lock()
cond.waiters[t] = struct{}{}
cond.Unlock()
condsMu.Unlock()
mutexesMu.Lock()
mu := mutexes[pMutex]
mutexesMu.Unlock()
mu.Unlock()
<-t.wait
mu.Lock()
return 0
}
// int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
func Xpthread_cond_timedwait(t *TLS, pCond, pMutex, pAbsTime uintptr) int32 {
if pCond == 0 {
return errno.EINVAL
}
condsMu.Lock()
cond := conds[pCond]
if cond == nil { // static initialized condition variables are valid
cond = newCond()
conds[pCond] = cond
}
cond.Lock()
cond.waiters[t] = struct{}{}
cond.Unlock()
condsMu.Unlock()
mutexesMu.Lock()
mu := mutexes[pMutex]
mutexesMu.Unlock()
deadlineSecs := (*ctime.Timespec)(unsafe.Pointer(pAbsTime)).Ftv_sec
deadlineNsecs := (*ctime.Timespec)(unsafe.Pointer(pAbsTime)).Ftv_nsec
deadline := time.Unix(int64(deadlineSecs), int64(deadlineNsecs))
d := deadline.Sub(time.Now())
switch {
case d <= 0:
return errno.ETIMEDOUT
default:
to := time.After(d)
mu.Unlock()
defer mu.Lock()
select {
case <-t.wait:
return 0
case <-to:
cond.Lock()
defer cond.Unlock()
delete(cond.waiters, t)
return errno.ETIMEDOUT
}
}
}
// Go side data of pthread_mutex_t
type mutex struct {
sync.Mutex
typ int // PTHREAD_MUTEX_NORMAL, ...
wait sync.Mutex
id int32 // owner's t.ID
cnt int32
robust bool
}
func newMutex(typ int) *mutex {
return &mutex{
typ: typ,
}
}
func (m *mutex) lock(id int32) int32 {
if m.robust {
panic(todo(""))
}
// If successful, the pthread_mutex_lock() and pthread_mutex_unlock() functions
// shall return zero; otherwise, an error number shall be returned to indicate
// the error.
switch m.typ {
case pthread.PTHREAD_MUTEX_NORMAL:
// If the mutex type is PTHREAD_MUTEX_NORMAL, deadlock detection shall not be
// provided. Attempting to relock the mutex causes deadlock. If a thread
// attempts to unlock a mutex that it has not locked or a mutex which is
// unlocked, undefined behavior results.
m.Lock()
m.id = id
return 0
case pthread.PTHREAD_MUTEX_RECURSIVE:
for {
m.Lock()
switch m.id {
case 0:
m.cnt = 1
m.id = id
m.wait.Lock()
m.Unlock()
return 0
case id:
m.cnt++
m.Unlock()
return 0
}
m.Unlock()
m.wait.Lock()
m.wait.Unlock()
}
default:
panic(todo("", m.typ))
}
}
func (m *mutex) tryLock(id int32) int32 {
if m.robust {
panic(todo(""))
}
switch m.typ {
case pthread.PTHREAD_MUTEX_NORMAL:
return errno.EBUSY
case pthread.PTHREAD_MUTEX_RECURSIVE:
m.Lock()
switch m.id {
case 0:
m.cnt = 1
m.id = id
m.wait.Lock()
m.Unlock()
return 0
case id:
m.cnt++
m.Unlock()
return 0
}
m.Unlock()
return errno.EBUSY
default:
panic(todo("", m.typ))
}
}
func (m *mutex) unlock() int32 {
if m.robust {
panic(todo(""))
}
// If successful, the pthread_mutex_lock() and pthread_mutex_unlock() functions
// shall return zero; otherwise, an error number shall be returned to indicate
// the error.
switch m.typ {
case pthread.PTHREAD_MUTEX_NORMAL:
// If the mutex type is PTHREAD_MUTEX_NORMAL, deadlock detection shall not be
// provided. Attempting to relock the mutex causes deadlock. If a thread
// attempts to unlock a mutex that it has not locked or a mutex which is
// unlocked, undefined behavior results.
m.id = 0
m.Unlock()
return 0
case pthread.PTHREAD_MUTEX_RECURSIVE:
m.Lock()
m.cnt--
if m.cnt == 0 {
m.id = 0
m.wait.Unlock()
}
m.Unlock()
return 0
default:
panic(todo("", m.typ))
}
}
// The pthread_mutex_init() function shall initialize the mutex referenced by
// mutex with attributes specified by attr. If attr is NULL, the default mutex
// attributes are used; the effect shall be the same as passing the address of
// a default mutex attributes object. Upon successful initialization, the state
// of the mutex becomes initialized and unlocked.
//
// If successful, the pthread_mutex_destroy() and pthread_mutex_init()
// functions shall return zero; otherwise, an error number shall be returned to
// indicate the error.
//
// int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
func Xpthread_mutex_init(t *TLS, pMutex, pAttr uintptr) int32 {
typ := pthread.PTHREAD_MUTEX_DEFAULT
if pAttr != 0 {
typ = int(X__ccgo_pthreadMutexattrGettype(t, pAttr))
}
mutexesMu.Lock()
defer mutexesMu.Unlock()
mutexes[pMutex] = newMutex(typ)
return 0
}
// int pthread_mutex_destroy(pthread_mutex_t *mutex);
func Xpthread_mutex_destroy(t *TLS, pMutex uintptr) int32 {
mutexesMu.Lock()
defer mutexesMu.Unlock()
delete(mutexes, pMutex)
return 0
}
// int pthread_mutex_lock(pthread_mutex_t *mutex);
func Xpthread_mutex_lock(t *TLS, pMutex uintptr) int32 {
mutexesMu.Lock()
mu := mutexes[pMutex]
if mu == nil { // static initialized mutexes are valid
mu = newMutex(int(X__ccgo_getMutexType(t, pMutex)))
mutexes[pMutex] = mu
}
mutexesMu.Unlock()
return mu.lock(t.ID)
}
// int pthread_mutex_trylock(pthread_mutex_t *mutex);
func Xpthread_mutex_trylock(t *TLS, pMutex uintptr) int32 {
mutexesMu.Lock()
mu := mutexes[pMutex]
if mu == nil { // static initialized mutexes are valid
mu = newMutex(int(X__ccgo_getMutexType(t, pMutex)))
mutexes[pMutex] = mu
}
mutexesMu.Unlock()
return mu.tryLock(t.ID)
}
// int pthread_mutex_unlock(pthread_mutex_t *mutex);
func Xpthread_mutex_unlock(t *TLS, pMutex uintptr) int32 {
mutexesMu.Lock()
defer mutexesMu.Unlock()
return mutexes[pMutex].unlock()
}
// int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
func Xpthread_key_create(t *TLS, pKey, destructor uintptr) int32 {
threadsKeysMu.Lock()
defer threadsKeysMu.Unlock()
threadKey++
r := threadKey
if destructor != 0 {
threadKeyDestructors[r] = append(threadKeyDestructors[r], destructor)
}
*(*pthread.Pthread_key_t)(unsafe.Pointer(pKey)) = pthread.Pthread_key_t(r)
return 0
}
// int pthread_key_delete(pthread_key_t key);
func Xpthread_key_delete(t *TLS, key pthread.Pthread_key_t) int32 {
if _, ok := t.kv[key]; ok {
delete(t.kv, key)
return 0
}
panic(todo(""))
}
// void *pthread_getspecific(pthread_key_t key);
func Xpthread_getspecific(t *TLS, key pthread.Pthread_key_t) uintptr {
return t.kv[key]
}
// int pthread_setspecific(pthread_key_t key, const void *value);
func Xpthread_setspecific(t *TLS, key pthread.Pthread_key_t, value uintptr) int32 {
if t.kv == nil {
t.kv = map[pthread.Pthread_key_t]uintptr{}
}
t.kv[key] = value
return 0
}
// int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
func Xpthread_create(t *TLS, pThread, pAttr, startRoutine, arg uintptr) int32 {
fn := (*struct {
f func(*TLS, uintptr) uintptr
})(unsafe.Pointer(&struct{ uintptr }{startRoutine})).f
detached := pAttr != 0 && X__ccgo_pthreadAttrGetDetachState(t, pAttr) == pthread.PTHREAD_CREATE_DETACHED
tls := newTLS(detached)
*(*pthread.Pthread_t)(unsafe.Pointer(pThread)) = pthread.Pthread_t(tls.ID)
go func() {
Xpthread_exit(tls, fn(tls, arg))
}()
return 0
}
// int pthread_detach(pthread_t thread);
func Xpthread_detach(t *TLS, thread pthread.Pthread_t) int32 {
threadsMu.Lock()
threads[int32(thread)].detached = true
threadsMu.Unlock()
return 0
}
// int pthread_equal(pthread_t t1, pthread_t t2);
func Xpthread_equal(t *TLS, t1, t2 pthread.Pthread_t) int32 {
return Bool32(t1 == t1)
}
// void pthread_exit(void *value_ptr);
func Xpthread_exit(t *TLS, value uintptr) {
t.retVal = value
// At thread exit, if a key value has a non-NULL destructor pointer, and the
// thread has a non-NULL value associated with that key, the value of the key
// is set to NULL, and then the function pointed to is called with the
// previously associated value as its sole argument. The order of destructor
// calls is unspecified if more than one destructor exists for a thread when it
// exits.
for k, v := range t.kv {
if v == 0 {
continue
}
threadsKeysMu.Lock()
destructors := threadKeyDestructors[k]
threadsKeysMu.Unlock()
for _, destructor := range destructors {
delete(t.kv, k)
panic(todo("%#x", destructor)) //TODO call destructor(v)
}
}
switch {
case t.detached:
threadsMu.Lock()
delete(threads, t.ID)
threadsMu.Unlock()
default:
close(t.done)
}
runtime.Goexit()
}
// int pthread_join(pthread_t thread, void **value_ptr);
func Xpthread_join(t *TLS, thread pthread.Pthread_t, pValue uintptr) int32 {
threadsMu.Lock()
tls := threads[int32(thread)]
delete(threads, int32(thread))
threadsMu.Unlock()
<-tls.done
if pValue != 0 {
*(*uintptr)(unsafe.Pointer(pValue)) = tls.retVal
}
return 0
}
// pthread_t pthread_self(void);
func Xpthread_self(t *TLS) pthread.Pthread_t {
return pthread.Pthread_t(t.ID)
}