mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 11:37:35 +00:00
182 lines
7.1 KiB
C
182 lines
7.1 KiB
C
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
|
│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │
|
|
╞══════════════════════════════════════════════════════════════════════════════╡
|
|
│ Copyright 2022 Justine Alexandra Roberts Tunney │
|
|
│ │
|
|
│ Permission to use, copy, modify, and/or distribute this software for │
|
|
│ any purpose with or without fee is hereby granted, provided that the │
|
|
│ above copyright notice and this permission notice appear in all copies. │
|
|
│ │
|
|
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
|
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
|
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
|
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
|
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
|
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
|
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
|
│ PERFORMANCE OF THIS SOFTWARE. │
|
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
|
#include "libc/atomic.h"
|
|
#include "libc/cosmo.h"
|
|
#include "libc/errno.h"
|
|
#include "libc/intrin/strace.h"
|
|
#include "libc/mem/mem.h"
|
|
#include "libc/thread/posixthread.internal.h"
|
|
#include "libc/thread/thread.h"
|
|
|
|
struct AtFork {
|
|
struct AtFork *p[2];
|
|
atfork_f f[3];
|
|
};
|
|
|
|
struct AtForks {
|
|
atomic_uint once;
|
|
pthread_mutex_t lock;
|
|
struct AtFork *list;
|
|
};
|
|
|
|
static struct AtForks _atforks = {
|
|
.lock = PTHREAD_MUTEX_INITIALIZER,
|
|
};
|
|
|
|
static void pthread_atfork_clear(void) {
|
|
struct AtFork *a, *b;
|
|
for (a = _atforks.list; a; a = b) {
|
|
b = a->p[0];
|
|
free(a);
|
|
}
|
|
}
|
|
|
|
static void pthread_atfork_init(void) {
|
|
atexit(pthread_atfork_clear);
|
|
}
|
|
|
|
static void _pthread_onfork(int i, const char *op) {
|
|
struct AtFork *a;
|
|
for (a = _atforks.list; a; a = a->p[!i]) {
|
|
if (a->f[i]) {
|
|
STRACE("pthread_atfork(%s, %t)", op, a->f[i]);
|
|
a->f[i]();
|
|
}
|
|
_atforks.list = a;
|
|
}
|
|
}
|
|
|
|
void _pthread_onfork_prepare(void) {
|
|
_pthread_mutex_lock(&_atforks.lock);
|
|
_pthread_onfork(0, "prepare");
|
|
}
|
|
|
|
void _pthread_onfork_parent(void) {
|
|
_pthread_onfork(1, "parent");
|
|
_pthread_mutex_unlock(&_atforks.lock);
|
|
}
|
|
|
|
void _pthread_onfork_child(void) {
|
|
_pthread_mutex_wipe_np(&_atforks.lock);
|
|
_pthread_onfork(2, "child");
|
|
}
|
|
|
|
/**
|
|
* Registers fork callbacks.
|
|
*
|
|
* When fork happens, your prepare functions will be called in the
|
|
* reverse order they were registered. Then, in the parent and child
|
|
* processes, their callbacks will be called in the same order they were
|
|
* registered.
|
|
*
|
|
* One big caveat with fork() is that it hard kills all threads except
|
|
* the calling thread. So let's say one of those threads was printing to
|
|
* stdout while it was killed. In that case, the stdout lock will still
|
|
* be held when your child process comes alive, which means that the
|
|
* child will deadlock next time it tries to print.
|
|
*
|
|
* The solution for that is simple. Every lock in your process should be
|
|
* registered with this interface. However there's one highly important
|
|
* thing you need to know. Locks must follow a consistent hierarchy. So
|
|
* the order in which you register locks matters. If nested locks aren't
|
|
* acquired in the same order globally, then rarely occurring deadlocks
|
|
* will happen. So what we recommend is that you hunt down all the locks
|
|
* that exist in your app and its dependencies, and register them all at
|
|
* once from your main() function at startup. This ensures a clear order
|
|
* and if you aren't sure what that order should be, cosmo libc has got
|
|
* you covered. Simply link your program with the `cosmocc -mdbg` flag
|
|
* and cosmo will detect locking violations with your `pthread_mutex_t`
|
|
* objects and report them by printing the strongly connected component.
|
|
* This will include the demangled symbol name of each mutex, assuming
|
|
* the `pthread_mutex_t` objects are stored in static memory. cosmo.h
|
|
* also exposes a deadlock API that lets you incorporate your own lock
|
|
* object types into this error checking system, which we also use to
|
|
* verify the entire libc runtime itself. See libc/intrin/deadlock.c.
|
|
*
|
|
* Special care should be taken when using this interface in libraries.
|
|
* While it may seem tempting to use something like a `__constructor__`
|
|
* attribute to register your mutexes in a clean and abstracted way, it
|
|
* is only appropriate if your mutex is guarding pure memory operations
|
|
* and poses zero risk of nesting with locks outside your library. For
|
|
* example, calling open() or printf() while holding your lock will do
|
|
* just that, since the C runtime functions you may consider pure will
|
|
* actually use mutexes under the hood, which are also validated under
|
|
* `cosmocc -mdbg` builds. So if your locks can't be made unnestable
|
|
* pure memory operations, then you should consider revealing their
|
|
* existence to users of your library.
|
|
*
|
|
* Here's an example of how pthread_atfork() can be used:
|
|
*
|
|
* static struct {
|
|
* pthread_once_t once;
|
|
* pthread_mutex_t lock;
|
|
* // data structures...
|
|
* } g_lib;
|
|
*
|
|
* static void lib_lock(void) {
|
|
* pthread_mutex_lock(&g_lib.lock);
|
|
* }
|
|
*
|
|
* static void lib_unlock(void) {
|
|
* pthread_mutex_unlock(&g_lib.lock);
|
|
* }
|
|
*
|
|
* static void lib_wipe(void) {
|
|
* pthread_mutex_wipe_np(&g_lib.lock);
|
|
* }
|
|
*
|
|
* static void lib_setup(void) {
|
|
* pthread_mutex_init(&g_lib.lock, 0);
|
|
* pthread_atfork(lib_lock, lib_unlock, lib_wipe);
|
|
* }
|
|
*
|
|
* static void lib_init(void) {
|
|
* pthread_once(&g_lib.once, lib_setup);
|
|
* }
|
|
*
|
|
* void lib(void) {
|
|
* lib_init();
|
|
* lib_lock();
|
|
* // do stuff...
|
|
* lib_unlock();
|
|
* }
|
|
*
|
|
* @param prepare is run by fork() before forking happens
|
|
* @param parent is run by fork() after forking happens in parent process
|
|
* @param child is run by fork() after forking happens in childe process
|
|
* @return 0 on success, or errno on error
|
|
* @raise ENOMEM if we require more vespene gas
|
|
*/
|
|
int pthread_atfork(atfork_f prepare, atfork_f parent, atfork_f child) {
|
|
cosmo_once(&_atforks.once, pthread_atfork_init);
|
|
struct AtFork *a;
|
|
if (!(a = calloc(1, sizeof(struct AtFork))))
|
|
return ENOMEM;
|
|
a->f[0] = prepare;
|
|
a->f[1] = parent;
|
|
a->f[2] = child;
|
|
_pthread_mutex_lock(&_atforks.lock);
|
|
a->p[0] = 0;
|
|
a->p[1] = _atforks.list;
|
|
if (_atforks.list)
|
|
_atforks.list->p[0] = a;
|
|
_atforks.list = a;
|
|
_pthread_mutex_unlock(&_atforks.lock);
|
|
return 0;
|
|
}
|