2022-09-11 18:02:07 +00:00
|
|
|
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│
|
|
|
|
│vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│
|
|
|
|
╞══════════════════════════════════════════════════════════════════════════════╡
|
|
|
|
│ Copyright 2016 Google Inc. │
|
|
|
|
│ │
|
|
|
|
│ Licensed under the Apache License, Version 2.0 (the "License"); │
|
|
|
|
│ you may not use this file except in compliance with the License. │
|
|
|
|
│ You may obtain a copy of the License at │
|
|
|
|
│ │
|
|
|
|
│ http://www.apache.org/licenses/LICENSE-2.0 │
|
|
|
|
│ │
|
|
|
|
│ Unless required by applicable law or agreed to in writing, software │
|
|
|
|
│ distributed under the License is distributed on an "AS IS" BASIS, │
|
|
|
|
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
|
|
|
|
│ See the License for the specific language governing permissions and │
|
|
|
|
│ limitations under the License. │
|
|
|
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
|
|
|
#include "libc/mem/mem.h"
|
|
|
|
#include "libc/runtime/runtime.h"
|
|
|
|
#include "libc/thread/thread.h"
|
2022-09-13 21:57:38 +00:00
|
|
|
#include "libc/thread/tls.h"
|
2022-09-11 18:02:07 +00:00
|
|
|
#include "third_party/nsync/atomic.h"
|
|
|
|
#include "third_party/nsync/atomic.internal.h"
|
|
|
|
#include "third_party/nsync/common.internal.h"
|
|
|
|
#include "third_party/nsync/dll.h"
|
|
|
|
#include "third_party/nsync/malloc.internal.h"
|
|
|
|
#include "third_party/nsync/mu_semaphore.h"
|
2022-09-12 01:53:52 +00:00
|
|
|
#include "third_party/nsync/races.internal.h"
|
2022-09-11 18:02:07 +00:00
|
|
|
#include "third_party/nsync/wait_s.internal.h"
|
|
|
|
|
|
|
|
asm(".ident\t\"\\n\\n\
|
|
|
|
*NSYNC (Apache 2.0)\\n\
|
|
|
|
Copyright 2016 Google, Inc.\\n\
|
|
|
|
https://github.com/google/nsync\"");
|
2022-09-10 23:11:26 +00:00
|
|
|
// clang-format off
|
|
|
|
|
2022-09-11 18:02:07 +00:00
|
|
|
/* This package provides a mutex nsync_mu and a Mesa-style condition
|
|
|
|
* variable nsync_cv. */
|
2022-09-10 23:11:26 +00:00
|
|
|
|
|
|
|
/* Implementation notes
|
|
|
|
|
|
|
|
The implementations of nsync_mu and nsync_cv both use spinlocks to protect
|
|
|
|
their waiter queues. The spinlocks are implemented with atomic operations
|
|
|
|
and a delay loop found below. They could use pthread_mutex_t, but I wished
|
|
|
|
to have an implementation independent of pthread mutexes and condition
|
|
|
|
variables.
|
|
|
|
|
|
|
|
nsync_mu and nsync_cv use the same type of doubly-linked list of waiters
|
|
|
|
(see waiter.c). This allows waiters to be transferred from the cv queue to
|
|
|
|
the mu queue when a thread is logically woken from the cv but would
|
|
|
|
immediately go to sleep on the mu. See the wake_waiters() call.
|
|
|
|
|
|
|
|
In mu, the "designated waker" is a thread that was waiting on mu, has been
|
|
|
|
woken up, but as yet has neither acquired nor gone back to waiting. The
|
|
|
|
presence of such a thread is indicated by the MU_DESIG_WAKER bit in the mu
|
|
|
|
word. This bit allows the nsync_mu_unlock() code to avoid waking a second
|
|
|
|
waiter when there's already one that will wake the next thread when the time
|
|
|
|
comes. This speeds things up when the lock is heavily contended, and the
|
|
|
|
critical sections are small.
|
|
|
|
|
|
|
|
The weasel words "with high probability" in the specification of
|
|
|
|
nsync_mu_trylock() and nsync_mu_rtrylock() prevent clients from believing
|
|
|
|
that they can determine with certainty whether another thread has given up a
|
|
|
|
lock yet. This, together with the requirement that a thread that acquired a
|
|
|
|
mutex must release it (rather than it being released by another thread),
|
|
|
|
prohibits clients from using mu as a sort of semaphore. The intent is that
|
|
|
|
it be used only for traditional mutual exclusion, and that clients that need
|
|
|
|
a semaphore should use one. This leaves room for certain future
|
|
|
|
optimizations, and make it easier to apply detection of potential races via
|
|
|
|
candidate lock-set algorithms, should that ever be desired.
|
|
|
|
|
|
|
|
The nsync_mu_wait_with_deadline() and nsync_mu_wait_with_deadline() calls use an
|
|
|
|
absolute rather than a relative timeout. This is less error prone, as
|
|
|
|
described in the comment on nsync_cv_wait_with_deadline(). Alas, relative
|
|
|
|
timeouts are seductive in trivial examples (such as tests). These are the
|
|
|
|
first things that people try, so they are likely to be requested. If enough
|
|
|
|
people complain we could give them that particular piece of rope.
|
|
|
|
|
|
|
|
Excessive evaluations of the same wait condition are avoided by maintaining
|
|
|
|
waiter.same_condition as a doubly-linked list of waiters with the same
|
|
|
|
non-NULL wait condition that are also adjacent in the waiter list. This does
|
|
|
|
well even with large numbers of threads if there is at most one
|
|
|
|
wait condition that can be false at any given time (such as in a
|
|
|
|
producer/consumer queue, which cannot be both empty and full
|
|
|
|
simultaneously). One could imagine a queueing mechanism that would
|
|
|
|
guarantee to evaluate each condition at most once per wakeup, but that would
|
|
|
|
be substantially more complex, and would still degrade if the number of
|
|
|
|
distinct wakeup conditions were high. So clients are advised to resort to
|
|
|
|
condition variables if they have many distinct wakeup conditions. */
|
|
|
|
|
|
|
|
/* Used in spinloops to delay resumption of the loop.
|
|
|
|
Usage:
|
|
|
|
unsigned attempts = 0;
|
|
|
|
while (try_something) {
|
|
|
|
attempts = nsync_spin_delay_ (attempts);
|
|
|
|
} */
|
|
|
|
unsigned nsync_spin_delay_ (unsigned attempts) {
|
|
|
|
if (attempts < 7) {
|
|
|
|
volatile int i;
|
|
|
|
for (i = 0; i != 1 << attempts; i++) {
|
|
|
|
}
|
|
|
|
attempts++;
|
|
|
|
} else {
|
2022-09-11 18:02:07 +00:00
|
|
|
nsync_yield_ ();
|
2022-09-10 23:11:26 +00:00
|
|
|
}
|
|
|
|
return (attempts);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Spin until (*w & test) == 0, then atomically perform *w = ((*w | set) &
|
|
|
|
~clear), perform an acquire barrier, and return the previous value of *w.
|
|
|
|
*/
|
|
|
|
uint32_t nsync_spin_test_and_set_ (nsync_atomic_uint32_ *w, uint32_t test,
|
|
|
|
uint32_t set, uint32_t clear) {
|
|
|
|
unsigned attempts = 0; /* CV_SPINLOCK retry count */
|
|
|
|
uint32_t old = ATM_LOAD (w);
|
|
|
|
while ((old & test) != 0 || !ATM_CAS_ACQ (w, old, (old | set) & ~clear)) {
|
|
|
|
attempts = nsync_spin_delay_ (attempts);
|
|
|
|
old = ATM_LOAD (w);
|
|
|
|
}
|
|
|
|
return (old);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ====================================================================================== */
|
|
|
|
|
|
|
|
struct nsync_waiter_s *nsync_dll_nsync_waiter_ (nsync_dll_element_ *e) {
|
|
|
|
struct nsync_waiter_s *nw = (struct nsync_waiter_s *) e->container;
|
|
|
|
ASSERT (nw->tag == NSYNC_WAITER_TAG);
|
|
|
|
ASSERT (e == &nw->q);
|
|
|
|
return (nw);
|
|
|
|
}
|
|
|
|
waiter *nsync_dll_waiter_ (nsync_dll_element_ *e) {
|
|
|
|
struct nsync_waiter_s *nw = DLL_NSYNC_WAITER (e);
|
|
|
|
waiter *w = CONTAINER (waiter, nw, nw);
|
|
|
|
ASSERT ((nw->flags & NSYNC_WAITER_FLAG_MUCV) != 0);
|
|
|
|
ASSERT (w->tag == WAITER_TAG);
|
|
|
|
ASSERT (e == &w->nw.q);
|
|
|
|
return (w);
|
|
|
|
}
|
|
|
|
|
|
|
|
waiter *nsync_dll_waiter_samecond_ (nsync_dll_element_ *e) {
|
|
|
|
waiter *w = (waiter *) e->container;
|
|
|
|
ASSERT (w->tag == WAITER_TAG);
|
|
|
|
ASSERT (e == &w->same_condition);
|
|
|
|
return (w);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------- */
|
|
|
|
|
|
|
|
static nsync_dll_list_ free_waiters = NULL;
|
|
|
|
|
|
|
|
/* free_waiters points to a doubly-linked list of free waiter structs. */
|
|
|
|
static nsync_atomic_uint32_ free_waiters_mu; /* spinlock; protects free_waiters */
|
|
|
|
|
2022-09-13 21:57:38 +00:00
|
|
|
#define waiter_for_thread __get_tls()->tib_nsync
|
2022-09-11 18:02:07 +00:00
|
|
|
|
2022-09-10 23:11:26 +00:00
|
|
|
static void waiter_destroy (void *v) {
|
|
|
|
waiter *w = (waiter *) v;
|
|
|
|
/* Reset waiter_for_thread in case another thread-local variable reuses
|
|
|
|
the waiter in its destructor while the waiter is taken by the other
|
|
|
|
thread from free_waiters. This can happen as the destruction order
|
|
|
|
of thread-local variables can be arbitrary in some platform e.g.
|
|
|
|
POSIX. */
|
|
|
|
waiter_for_thread = NULL;
|
|
|
|
IGNORE_RACES_START ();
|
|
|
|
ASSERT ((w->flags & (WAITER_RESERVED|WAITER_IN_USE)) == WAITER_RESERVED);
|
|
|
|
w->flags &= ~WAITER_RESERVED;
|
|
|
|
nsync_spin_test_and_set_ (&free_waiters_mu, 1, 1, 0);
|
|
|
|
free_waiters = nsync_dll_make_first_in_list_ (free_waiters, &w->nw.q);
|
|
|
|
ATM_STORE_REL (&free_waiters_mu, 0); /* release store */
|
|
|
|
IGNORE_RACES_END ();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Return a pointer to an unused waiter struct.
|
|
|
|
Ensures that the enclosed timer is stopped and its channel drained. */
|
|
|
|
waiter *nsync_waiter_new_ (void) {
|
|
|
|
nsync_dll_element_ *q;
|
|
|
|
waiter *tw;
|
|
|
|
waiter *w;
|
2022-09-11 18:02:07 +00:00
|
|
|
tw = waiter_for_thread;
|
2022-09-10 23:11:26 +00:00
|
|
|
w = tw;
|
|
|
|
if (w == NULL || (w->flags & (WAITER_RESERVED|WAITER_IN_USE)) != WAITER_RESERVED) {
|
|
|
|
w = NULL;
|
|
|
|
nsync_spin_test_and_set_ (&free_waiters_mu, 1, 1, 0);
|
|
|
|
q = nsync_dll_first_ (free_waiters);
|
|
|
|
if (q != NULL) { /* If free list is non-empty, dequeue an item. */
|
|
|
|
free_waiters = nsync_dll_remove_ (free_waiters, q);
|
|
|
|
w = DLL_WAITER (q);
|
|
|
|
}
|
|
|
|
ATM_STORE_REL (&free_waiters_mu, 0); /* release store */
|
|
|
|
if (w == NULL) { /* If free list was empty, allocate an item. */
|
2022-09-11 18:02:07 +00:00
|
|
|
w = (waiter *) nsync_malloc_ (sizeof (*w));
|
2022-09-10 23:11:26 +00:00
|
|
|
w->tag = WAITER_TAG;
|
|
|
|
w->nw.tag = NSYNC_WAITER_TAG;
|
|
|
|
nsync_mu_semaphore_init (&w->sem);
|
|
|
|
w->nw.sem = &w->sem;
|
|
|
|
nsync_dll_init_ (&w->nw.q, &w->nw);
|
|
|
|
NSYNC_ATOMIC_UINT32_STORE_ (&w->nw.waiting, 0);
|
|
|
|
w->nw.flags = NSYNC_WAITER_FLAG_MUCV;
|
|
|
|
ATM_STORE (&w->remove_count, 0);
|
|
|
|
nsync_dll_init_ (&w->same_condition, w);
|
|
|
|
w->flags = 0;
|
|
|
|
}
|
|
|
|
if (tw == NULL) {
|
|
|
|
w->flags |= WAITER_RESERVED;
|
|
|
|
nsync_set_per_thread_waiter_ (w, &waiter_destroy);
|
2022-09-11 18:02:07 +00:00
|
|
|
waiter_for_thread = w;
|
2022-09-10 23:11:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
w->flags |= WAITER_IN_USE;
|
|
|
|
return (w);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Return an unused waiter struct *w to the free pool. */
|
|
|
|
void nsync_waiter_free_ (waiter *w) {
|
|
|
|
ASSERT ((w->flags & WAITER_IN_USE) != 0);
|
|
|
|
w->flags &= ~WAITER_IN_USE;
|
|
|
|
if ((w->flags & WAITER_RESERVED) == 0) {
|
|
|
|
nsync_spin_test_and_set_ (&free_waiters_mu, 1, 1, 0);
|
|
|
|
free_waiters = nsync_dll_make_first_in_list_ (free_waiters, &w->nw.q);
|
|
|
|
ATM_STORE_REL (&free_waiters_mu, 0); /* release store */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ====================================================================================== */
|
|
|
|
|
|
|
|
/* writer_type points to a lock_type that describes how to manipulate a mu for a writer. */
|
|
|
|
static lock_type Xwriter_type = {
|
|
|
|
MU_WZERO_TO_ACQUIRE,
|
|
|
|
MU_WADD_TO_ACQUIRE,
|
|
|
|
MU_WHELD_IF_NON_ZERO,
|
|
|
|
MU_WSET_WHEN_WAITING,
|
|
|
|
MU_WCLEAR_ON_ACQUIRE,
|
|
|
|
MU_WCLEAR_ON_UNCONTENDED_RELEASE
|
|
|
|
};
|
|
|
|
lock_type *nsync_writer_type_ = &Xwriter_type;
|
|
|
|
|
|
|
|
|
|
|
|
/* reader_type points to a lock_type that describes how to manipulate a mu for a reader. */
|
|
|
|
static lock_type Xreader_type = {
|
|
|
|
MU_RZERO_TO_ACQUIRE,
|
|
|
|
MU_RADD_TO_ACQUIRE,
|
|
|
|
MU_RHELD_IF_NON_ZERO,
|
|
|
|
MU_RSET_WHEN_WAITING,
|
|
|
|
MU_RCLEAR_ON_ACQUIRE,
|
|
|
|
MU_RCLEAR_ON_UNCONTENDED_RELEASE
|
|
|
|
};
|
|
|
|
lock_type *nsync_reader_type_ = &Xreader_type;
|