/*- * Copyright (c) 2014-2020 MongoDB, Inc. * Copyright (c) 2008-2014 WiredTiger, Inc. * All rights reserved. * * See the file LICENSE for redistribution information. */ #include "wt_internal.h" /* * __wt_cond_alloc -- * Allocate and initialize a condition variable. */ int __wt_cond_alloc(WT_SESSION_IMPL *session, const char *name, WT_CONDVAR **condp) { WT_CONDVAR *cond; WT_DECL_RET; WT_RET(__wt_calloc_one(session, &cond)); WT_ERR(pthread_mutex_init(&cond->mtx, NULL)); #ifdef HAVE_PTHREAD_COND_MONOTONIC { pthread_condattr_t condattr; WT_ERR(pthread_condattr_init(&condattr)); ret = pthread_condattr_setclock(&condattr, CLOCK_MONOTONIC); if (ret == 0) ret = pthread_cond_init(&cond->cond, &condattr); WT_TRET(pthread_condattr_destroy(&condattr)); WT_ERR(ret); } #else WT_ERR(pthread_cond_init(&cond->cond, NULL)); #endif cond->name = name; cond->waiters = 0; *condp = cond; return (0); err: __wt_free(session, cond); return (ret); } /* * __wt_cond_wait_signal -- * Wait on a mutex, optionally timing out. If we get it before the time out period expires, let * the caller know. */ void __wt_cond_wait_signal(WT_SESSION_IMPL *session, WT_CONDVAR *cond, uint64_t usecs, bool (*run_func)(WT_SESSION_IMPL *), bool *signalled) { struct timespec ts; WT_DECL_RET; WT_TRACK_OP_DECL; bool locked; WT_TRACK_OP_INIT(session); locked = false; /* Fast path if already signalled. */ *signalled = true; if (__wt_atomic_addi32(&cond->waiters, 1) == 0) { WT_TRACK_OP_END(session); return; } __wt_verbose(session, WT_VERB_MUTEX, "wait %s", cond->name); WT_STAT_CONN_INCR(session, cond_wait); WT_ERR(pthread_mutex_lock(&cond->mtx)); locked = true; /* * It's possible to race with threads waking us up. That's not a problem if there are multiple * wakeups because the next wakeup will get us, or if we're only pausing for a short period. * It's a problem if there's only a single wakeup, our waker is likely waiting for us to exit. * After acquiring the mutex (so we're guaranteed to be awakened by any future wakeup call), * optionally check if we're OK to keep running. This won't ensure our caller won't just loop * and call us again, but at least it's not our fault. * * Assert we're not waiting longer than a second if not checking the run status. */ WT_ASSERT(session, run_func != NULL || usecs <= WT_MILLION); if (run_func != NULL && !run_func(session)) goto skipping; if (usecs > 0) { /* * Get the current time as the basis for calculating when the wait should end. Prefer a monotonic * clock source to avoid unexpectedly long sleeps when the system clock is adjusted. * * Failing that, query the time directly and don't attempt to correct for the clock moving * backwards, which would result in a sleep that is too long by however much the clock is updated. * This isn't as good as a monotonic clock source but makes the window of vulnerability smaller * (i.e., the calculated time is only incorrect if the system clock changes in between us querying * it and waiting). */ #ifdef HAVE_PTHREAD_COND_MONOTONIC WT_SYSCALL_RETRY(clock_gettime(CLOCK_MONOTONIC, &ts), ret); if (ret != 0) WT_IGNORE_RET(__wt_panic(session, ret, "clock_gettime")); #else __wt_epoch_raw(session, &ts); #endif ts.tv_sec += (time_t)(((uint64_t)ts.tv_nsec + WT_THOUSAND * usecs) / WT_BILLION); ts.tv_nsec = (long)(((uint64_t)ts.tv_nsec + WT_THOUSAND * usecs) % WT_BILLION); ret = pthread_cond_timedwait(&cond->cond, &cond->mtx, &ts); } else ret = pthread_cond_wait(&cond->cond, &cond->mtx); /* * Check pthread_cond_wait() return for EINTR, ETIME and ETIMEDOUT, some systems return these * errors. */ if (ret == EINTR || #ifdef ETIME ret == ETIME || #endif ret == ETIMEDOUT) { skipping: *signalled = false; ret = 0; } err: (void)__wt_atomic_subi32(&cond->waiters, 1); if (locked) WT_TRET(pthread_mutex_unlock(&cond->mtx)); WT_TRACK_OP_END(session); if (ret == 0) return; WT_IGNORE_RET(__wt_panic(session, ret, "pthread_cond_wait: %s", cond->name)); } /* * __wt_cond_signal -- * Signal a waiting thread. */ void __wt_cond_signal(WT_SESSION_IMPL *session, WT_CONDVAR *cond) { WT_DECL_RET; __wt_verbose(session, WT_VERB_MUTEX, "signal %s", cond->name); /* * Our callers often set flags to cause a thread to exit. Add a barrier to ensure exit flags are * seen by the sleeping threads, otherwise we can wake up a thread, it immediately goes back to * sleep, and we'll hang. Use a full barrier (we may not write before waiting on thread join). */ WT_FULL_BARRIER(); /* * Fast path if we are in (or can enter), a state where the next waiter will return immediately * as already signaled. */ if (cond->waiters == -1 || (cond->waiters == 0 && __wt_atomic_casi32(&cond->waiters, 0, -1))) return; WT_ERR(pthread_mutex_lock(&cond->mtx)); ret = pthread_cond_broadcast(&cond->cond); WT_TRET(pthread_mutex_unlock(&cond->mtx)); if (ret == 0) return; err: WT_IGNORE_RET(__wt_panic(session, ret, "pthread_cond_broadcast: %s", cond->name)); } /* * __wt_cond_destroy -- * Destroy a condition variable. */ void __wt_cond_destroy(WT_SESSION_IMPL *session, WT_CONDVAR **condp) { WT_CONDVAR *cond; WT_DECL_RET; cond = *condp; if (cond == NULL) return; if ((ret = pthread_cond_destroy(&cond->cond)) != 0) WT_IGNORE_RET(__wt_panic(session, ret, "pthread_cond_destroy: %s", cond->name)); if ((ret = pthread_mutex_destroy(&cond->mtx)) != 0) WT_IGNORE_RET(__wt_panic(session, ret, "pthread_mutex_destroy: %s", cond->name)); __wt_free(session, *condp); }