tl;dr: This fixes deadlocks that would occur in most applications in
Serenity when SMP was enabled.
As an optimization to `pthread_mutex_unlock()`, if only one thread
holds a mutex lock, it will avoid calling `futex()` to wake, since no
threads are actually waiting in `futex()`.
If a thread manages to synchronously unlock and relock the same mutex,
the state will be set to indicate that a wake is not needed, regardless
of whether any other threads are waiting for the lock. This should be
fine, as a thread that is waiting will set the mutex to needing a wake
and check if it is unlocked to unblock itself in the same atomic
operation.
However, when `pthread_mutex_lock()` was called with only one thread
holding the mutex lock, instead of telling `futex()` to atomically
sleep the thread if the state is set to indicate that a wake is
needed, the first wait would check if the state was set to indicate a
wake was _not_ needed. This means it is possible for the call to
`futex()` to wait when any subsequent call to `pthread_mutex_unlock()`
would not call `futex()` to wake, causing it to wait forever.
After that, any other thread that tries to take the lock will see that
the lock is taken and also `futex()` wait. Despite the fact that these
other threads would set the state to needing a wake, there will be no
unblocked thread holding the lock to actually wake them.
By making it wait only if the state indicates to other threads that a
wake is needed, heavily contended mutexes should no longer cause
deadlocks. Most applications would encounter these deadlocks due to the
mutex used by `malloc()`, some sooner than others. The worst offenders
(other than Ladybird) were most likely VideoPlayer and SoundPlayer.
This is a private function that locks the lock much like the regular
pthread_mutex_lock(), but causes the corresponding unlock operation to
always assume there may be other waiters. This is useful in case some
waiters are made to wait on the mutex's futex directly, without going
through pthread_mutex_lock(). This is going to be used by the condition
variable implementation in the next commit.
pthread_mutex is now an actual "sleeping" mutex, and not just a
spinlock! It still has a fast path that only uses atomics and (in the
successful case) returns immediately without sleeping. In case of
contention, it calls futex_wait(), which lets the kernel scheduler put
this thread to sleep, *and* lets it know exactly when to consider
scheduling it again.
This ensures the store to mutex->lock doesn't get re-ordered before
the store to mutex->owner which could otherwise result in a locked
owner-less mutex if another thread tries to acquire the lock at
the same time.
SPDX License Identifiers are a more compact / standardized
way of representing file license information.
See: https://spdx.dev/resources/use/#identifiers
This was done with the `ambr` search and replace tool.
ambr --no-parent-ignore --key-from-file --rep-from-file key.txt rep.txt *
GCC will insert various calls to pthread functions when compiling
C++ code with static initializers, even when the user doesn't link
their program against libpthread explicitly.
This is used to make static initializers thread-safe, e.g. when
building a library that does not itself use thread functionality
and thus does not link against libpthread - but is intended to
be used with other code that does use libpthread explicitly.
This makes these symbols available in libc.
This required a bit of rearchitecture, as pthread_atfork() required a
mutex, and duplicating a mutex impl for it was silly.
As such, this patch moves some standalone bits of pthread into LibC and
uses those to implement atfork().
It should be noted that for programs that don't use atfork(), this
mechanism only costs two atomic loads (as opposed to the normal mutex
lock+unlock) :^)