Last active
October 27, 2018 15:33
-
-
Save pkhuong/d6ea6cab3c3e2364afcda4acb58317e2 to your computer and use it in GitHub Desktop.
Futex-backed event count for concurrency kit
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #define _GNU_SOURCE | |
| #include "ck_ec.h" | |
| #include <ck_limits.h> | |
| #include <ck_stdbool.h> | |
| #include <linux/futex.h> | |
| #include <sys/syscall.h> | |
| #include <time.h> | |
| #include <unistd.h> | |
| #define BUSY_LOOP_ITER 100U | |
| #define INITIAL_WAIT_TIME_NS 1000000L /* Start at 1 ms */ | |
| /* Once we've waited for >= 1 sec, go for the full deadline. */ | |
| #define FINAL_WAIT_TIME_SEC 1 | |
| #define WAIT_TIME_SCALE_FACTOR 1.5 /* Grow the wait time by 50%/iteration. */ | |
| #define TIME_MAX ((1ULL << ((sizeof(time_t) * CHAR_BIT) - 1)) - 1) | |
| struct ck_ec32_wait_state { | |
| struct ck_ec32 *ec; | |
| uint32_t flagged_word; | |
| }; | |
| struct ck_ec64_wait_state { | |
| struct ck_ec64 *ec; | |
| uint64_t flagged_word; | |
| }; | |
| void ck_ec32_wake(struct ck_ec32 *ec) | |
| { | |
| /* Spurious wake-ups are OK. Clear the flag before futexing. */ | |
| ck_pr_and_32(&ec->counter, (1U << 31) - 1); | |
| syscall(SYS_futex, (int *)&ec->counter, | |
| FUTEX_WAKE, INT_MAX, | |
| /* ignored arguments */NULL, NULL, 0); | |
| return; | |
| } | |
| void ck_ec64_wake(struct ck_ec64 *ec) | |
| { | |
| ck_pr_and_64(&ec->counter, ~1UL); | |
| syscall(SYS_futex, (int *)&ec->counter, | |
| FUTEX_WAKE, INT_MAX, | |
| /* ignored arguments */NULL, NULL, 0); | |
| return; | |
| } | |
| int ck_ec32_wait_slow(struct ck_ec32 *ec, uint32_t old_value, | |
| const struct timespec *deadline) | |
| { | |
| return ck_ec32_wait_pred_slow(ec, old_value, | |
| NULL, NULL, deadline); | |
| } | |
| int ck_ec64_wait_slow(struct ck_ec64 *ec, uint64_t old_value, | |
| const struct timespec *deadline) | |
| { | |
| return ck_ec64_wait_pred_slow(ec, old_value, | |
| NULL, NULL, deadline); | |
| } | |
| /* | |
| * Increments acc by scale * y. scale * y->tv_nsec must not exceed | |
| * LONG_MAX. | |
| */ | |
| static void timespec_inc(struct timespec *acc, | |
| double scale, | |
| const struct timespec *y) | |
| { | |
| const long nsec_per_sec = 1000 * 1000 * 1000L; | |
| const double delta_sec = y->tv_sec * scale; | |
| unsigned long nsec = acc->tv_nsec + scale * y->tv_nsec; | |
| if (delta_sec >= TIME_MAX - acc->tv_sec) { | |
| /* This is already infinitely in the future. */ | |
| acc->tv_sec = TIME_MAX; | |
| return; | |
| } | |
| acc->tv_sec += delta_sec; | |
| acc->tv_sec += nsec / nsec_per_sec; | |
| acc->tv_nsec = nsec % nsec_per_sec; | |
| return; | |
| } | |
| int ck_ec_deadline(struct timespec *new_deadline, | |
| const struct timespec *timeout) | |
| { | |
| struct timespec ret = { .tv_sec = TIME_MAX }; | |
| int r; | |
| if (timeout == NULL) { | |
| goto done; | |
| } | |
| r = clock_gettime(CLOCK_MONOTONIC, &ret); | |
| if (r != 0) { | |
| return -1; | |
| } | |
| timespec_inc(&ret, 1, timeout); | |
| done: | |
| *new_deadline = ret; | |
| return 0; | |
| } | |
| /* The rest of the file implements wait_pred_slow. */ | |
| /* | |
| * Returns a timespec value for deadline_ptr. If deadline_ptr is NULL, | |
| * returns a timespec far in the future. | |
| */ | |
| static struct timespec canonical_deadline(const struct timespec *deadline_ptr) | |
| { | |
| if (deadline_ptr == NULL) { | |
| return (struct timespec) { .tv_sec = TIME_MAX }; | |
| } | |
| return *deadline_ptr; | |
| } | |
| /* Compares two timespecs. Returns -1 if x < y, 0 if x == y, and 1 if x > y. */ | |
| static int timespec_cmp(const struct timespec *x, const struct timespec *y) | |
| { | |
| if (x->tv_sec != y->tv_sec) { | |
| return (x->tv_sec < y->tv_sec) ? -1 : 1; | |
| } | |
| if (x->tv_nsec != y->tv_nsec) { | |
| return (x->tv_nsec < y->tv_nsec) ? -1 : 1; | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Overwrites now with the current CLOCK_MONOTONIC time, and returns | |
| * true if the current time is greater than the deadline, or the clock | |
| * is somehow broken. | |
| */ | |
| static bool check_deadline(struct timespec *now, | |
| const struct timespec *deadline) | |
| { | |
| int r; | |
| r = clock_gettime(CLOCK_MONOTONIC, now); | |
| if (r != 0) { | |
| return true; | |
| } | |
| return timespec_cmp(now, deadline) > 0; | |
| } | |
| /* | |
| * Really slow (sleeping) path for ck_ec_wait. Drives the exponential | |
| * backoff scheme to sleep for longer and longer periods of time, | |
| * until either the sleep function returns true (the eventcount's | |
| * value has changed), or the predicate returns non-0 (something else | |
| * has changed). | |
| * | |
| * If deadline is ever reached, returns -1 (timeout). | |
| * | |
| * TODO: add some form of randomisation to the intermediate timeout | |
| * values. | |
| */ | |
| static int exponential_backoff( | |
| bool (*sleep)(const void *sleep_state, | |
| const struct timespec *partial_deadline), | |
| const void *sleep_state, | |
| int (*pred)(void *data, struct timespec *deadline, | |
| const struct timespec *now), | |
| void *data, | |
| const struct timespec *deadline) | |
| { | |
| struct timespec begin; | |
| struct timespec wait_time = { .tv_nsec = INITIAL_WAIT_TIME_NS }; | |
| bool first = true; | |
| for (;;) { | |
| struct timespec now; | |
| struct timespec partial_deadline; | |
| if (check_deadline(&now, deadline) == true) { | |
| /* Timeout. */ | |
| return -1; | |
| } | |
| if (first) { | |
| begin = now; | |
| first = false; | |
| } | |
| /* | |
| * If we've definitely waited more than | |
| * FINAL_WAIT_TIME_SEC, just wait for the full | |
| * deadline. Until then, stick to exponential backoff. | |
| */ | |
| if (now.tv_sec - begin.tv_sec > FINAL_WAIT_TIME_SEC) { | |
| partial_deadline = *deadline; | |
| } else { | |
| partial_deadline = now; | |
| timespec_inc(&partial_deadline, 1.0, &wait_time); | |
| /* | |
| * Add (SCALE_FACTOR - 1) * wait_time to | |
| * wait_time , i.e., multiply wait_time by | |
| * SCALE_FACTOR. | |
| */ | |
| timespec_inc(&wait_time, WAIT_TIME_SCALE_FACTOR - 1, | |
| &wait_time); | |
| } | |
| if (pred != NULL) { | |
| int r = pred(data, &partial_deadline, &now); | |
| if (r != 0) { | |
| return r; | |
| } | |
| } | |
| /* Canonicalize deadlines in the far future to NULL. */ | |
| if (sleep(sleep_state, | |
| ((partial_deadline.tv_sec == TIME_MAX) | |
| ? NULL : &partial_deadline)) == true) { | |
| return 0; | |
| } | |
| } | |
| } | |
| /* | |
| * Loops up to BUSY_LOOP_ITER times, or until ec's counter value | |
| * (including the flag) differs from old_value. | |
| * | |
| * Returns the new value in ec. | |
| */ | |
| #define DEF_WAIT_EASY(W) \ | |
| static uint##W##_t ck_ec##W##_wait_easy(struct ck_ec##W* ec, \ | |
| uint##W##_t expected) \ | |
| { \ | |
| uint##W##_t current = expected; \ | |
| \ | |
| for (size_t i = 0; \ | |
| i < BUSY_LOOP_ITER && current == expected; \ | |
| i++) { \ | |
| ck_pr_stall(); \ | |
| current = ck_pr_load_##W(&ec->counter); \ | |
| } \ | |
| \ | |
| return current; \ | |
| } | |
| DEF_WAIT_EASY(32) | |
| DEF_WAIT_EASY(64) | |
| #undef DEF_WAIT_EASY | |
| /* | |
| * Attempts to upgrade ec->counter from unflagged to flagged. | |
| * | |
| * Returns true if the event count has changed. Otherwise, ec's | |
| * counter word is equal to flagged on return, or has been at some | |
| * time before the return. | |
| */ | |
| #define DEF_UPGRADE(W) \ | |
| static bool ck_ec##W##_upgrade(struct ck_ec##W* ec, \ | |
| uint##W##_t current, \ | |
| uint##W##_t unflagged, \ | |
| uint##W##_t flagged) \ | |
| { \ | |
| uint##W##_t old_word; \ | |
| \ | |
| if (current == flagged) { \ | |
| /* Nothing to do, no change. */ \ | |
| return false; \ | |
| } \ | |
| \ | |
| if (current != unflagged) { \ | |
| /* We have a different counter value! */ \ | |
| return true; \ | |
| } \ | |
| \ | |
| /* \ | |
| * Flag the counter value. The CAS only fails if the \ | |
| * counter is already flagged, or has a new value. \ | |
| */ \ | |
| return (ck_pr_cas_##W##_value(&ec->counter, \ | |
| unflagged, flagged, \ | |
| &old_word) == false && \ | |
| old_word != flagged); \ | |
| } | |
| DEF_UPGRADE(32) | |
| DEF_UPGRADE(64) | |
| #undef DEF_UPGRADE | |
| /* | |
| * Blocks until partial_deadline on the ck_ec. Returns true if the | |
| * eventcount's value has changed. If partial_deadline is NULL, wait | |
| * forever. | |
| */ | |
| static bool ck_ec32_wait_slow_once(const void *vstate, | |
| const struct timespec *partial_deadline) | |
| { | |
| const struct ck_ec32_wait_state *state = vstate; | |
| struct ck_ec32 *ec = state->ec; | |
| const uint32_t flagged_word = state->flagged_word; | |
| (void)syscall(SYS_futex, (int *)&ec->counter, | |
| FUTEX_WAIT_BITSET, (int)flagged_word, | |
| partial_deadline, NULL, FUTEX_BITSET_MATCH_ANY); | |
| return ck_pr_load_32(&ec->counter) != flagged_word; | |
| } | |
| static bool ck_ec64_wait_slow_once(const void *vstate, | |
| const struct timespec *partial_deadline) | |
| { | |
| const struct ck_ec64_wait_state *state = vstate; | |
| struct ck_ec64 *ec = state->ec; | |
| const uint64_t flagged_word = state->flagged_word; | |
| /* futex_wait_bitset will only compare the low 32 | |
| * bits. Perform a full comparison here to maximise the | |
| * changes of catching an ABA in the low 32 bits. | |
| */ | |
| if (ck_pr_load_64(&ec->counter) != flagged_word) { | |
| return true; | |
| } | |
| /* | |
| * We're on x86, a little-endian platform. Use the low-order | |
| * bits as the futex int. | |
| */ | |
| (void)syscall(SYS_futex, (int *)&ec->counter, | |
| FUTEX_WAIT_BITSET, (int)flagged_word, | |
| partial_deadline, NULL, FUTEX_BITSET_MATCH_ANY); | |
| return ck_pr_load_64(&ec->counter) != flagged_word; | |
| } | |
| /* | |
| * The full wait logic is a lot of code (> 1KB). Encourage the | |
| * compiler to lay this all out linearly with LIKELY annotations on | |
| * every early exit. | |
| */ | |
| #define WAIT_SLOW_BODY(W, ec, pred, data, deadline_ptr, unflagged, flagged) \ | |
| do { \ | |
| const struct ck_ec##W##_wait_state state = { \ | |
| .ec = ec, \ | |
| .flagged_word = flagged \ | |
| }; \ | |
| const struct timespec deadline = \ | |
| canonical_deadline(deadline_ptr); \ | |
| uint##W##_t current; \ | |
| \ | |
| /* Detect infinite past deadlines. */ \ | |
| if (CK_CC_LIKELY(deadline.tv_sec <= 0)) { \ | |
| return -1; \ | |
| } \ | |
| \ | |
| current = ck_ec##W##_wait_easy(ec, unflagged); \ | |
| /* \ | |
| * We're about to wait harder (i.e., potentially with \ | |
| * futex). Make sure the counter word is flagged. \ | |
| */ \ | |
| if (CK_CC_LIKELY( \ | |
| ck_ec##W##_upgrade(ec, current, \ | |
| unflagged, flagged) == true)) { \ | |
| return 0; \ | |
| } \ | |
| \ | |
| /* \ | |
| * By now, ec->counter == flagged_word (at some point \ | |
| * in the past). Spin some more to heuristically let \ | |
| * any in-flight SP inc/add to retire. This does not \ | |
| * affect correctness, but practically eliminates lost \ | |
| * wake-ups. \ | |
| */ \ | |
| current = ck_ec##W##_wait_easy(ec, flagged); \ | |
| if (CK_CC_LIKELY(current != flagged_word)) { \ | |
| return 0; \ | |
| } \ | |
| \ | |
| return exponential_backoff(ck_ec##W##_wait_slow_once, \ | |
| &state, \ | |
| pred, data, &deadline); \ | |
| } while (0) | |
| int ck_ec32_wait_pred_slow(struct ck_ec32 *ec, uint32_t old_value, | |
| int (*pred)(void *data, | |
| struct timespec *deadline, | |
| const struct timespec *now), | |
| void *data, | |
| const struct timespec *deadline_ptr) | |
| { | |
| const uint32_t unflagged_word = old_value; | |
| const uint32_t flagged_word = old_value | (1UL << 31); | |
| WAIT_SLOW_BODY(32, ec, pred, data, deadline_ptr, | |
| unflagged_word, flagged_word); | |
| } | |
| int ck_ec64_wait_pred_slow(struct ck_ec64 *ec, uint64_t old_value, | |
| int (*pred)(void *data, | |
| struct timespec *deadline, | |
| const struct timespec *now), | |
| void *data, | |
| const struct timespec *deadline_ptr) | |
| { | |
| const uint64_t unflagged_word = old_value << 1; | |
| const uint64_t flagged_word = unflagged_word | 1; | |
| WAIT_SLOW_BODY(64, ec, pred, data, deadline_ptr, | |
| unflagged_word, flagged_word); | |
| } | |
| #undef WAIT_SLOW_BODY |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * Copyright 2018 Paul Khuong. | |
| * All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions | |
| * are met: | |
| * 1. Redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer. | |
| * 2. Redistributions in binary form must reproduce the above copyright | |
| * notice, this list of conditions and the following disclaimer in the | |
| * documentation and/or other materials provided with the distribution. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
| * SUCH DAMAGE. | |
| */ | |
| /* | |
| * Overview | |
| * ======== | |
| * | |
| * ck_ec implements 32- and 64- bit event counts. Event counts let us | |
| * easily integrate OS-level blocking (futexes) in lock-free | |
| * protocols. Waking up waiters only locks in the OS kernel, and does | |
| * not happen at all when no waiter is blocked. | |
| * | |
| * Waiters only block conditionally, if the event count's value is | |
| * still equal to some old value. | |
| * | |
| * Event counts come in four variants: 32 and 64 bit (with one bit | |
| * stolen for internal signaling, so 31 and 63 bit counters), and | |
| * single or multiple producers (wakers). Waiters are always multiple | |
| * consumers. The 32 bit variants are smaller, and more efficient, | |
| * especially in single producer mode. The 64 bit variants are larger, | |
| * but practically invulnerable to ABA. | |
| * | |
| * A typical usage pattern is: | |
| * | |
| * 1. On the producer side: | |
| * | |
| * - Make changes to some shared data structure, without involving | |
| * the event count at all. | |
| * - After each change, call ck_ec_inc on the event count. The call | |
| * acts as a write-write barrier, and wakes up any consumer blocked | |
| * on the event count (waiting for new changes). | |
| * | |
| * 2. On the consumer side: | |
| * | |
| * - Snapshot ck_ec_value of the event count. The call acts as a | |
| * read barrier. | |
| * - Read and process the shared data structure. | |
| * - Wait for new changes by calling ck_ec_wait with the snapshot value. | |
| * | |
| * Some data structures may opt for tighter integration with their | |
| * event count. For example, an SPMC ring buffer or disruptor might | |
| * use the event count's value as the write pointer. If the buffer is | |
| * regularly full, it might also make sense to store the read pointer | |
| * in an MP event count. | |
| * | |
| * This event count implementation supports tighter integration in two | |
| * ways. | |
| * | |
| * Producers may opt to increment by an arbitrary value (less than | |
| * INT32_MAX / INT64_MAX), in order to encode, e.g., byte | |
| * offsets. Larger increment values make wraparound more likely, so | |
| * the increments should still be relatively small. | |
| * | |
| * Consumers may pass a predicate to ck_ec_wait_pred. This predicate | |
| * can make `ck_ec_wait_pred` return early, before the event count's | |
| * value changes, and can override the deadline passed to futex_wait. | |
| * This lets consumer block on one eventcount, while optimistically | |
| * looking at other waking conditions. | |
| * | |
| * API Reference | |
| * ============= | |
| * | |
| * When compiled as C11 or later, this header defines type-generic | |
| * macros for ck_ec32 and ck_ec64; the reference describes this | |
| * type-generic API. | |
| * | |
| * ec is a struct ck_ec32*, or a struct ck_ec64*. | |
| * | |
| * value is an uint32_t for ck_ec32, and an uint64_t for ck_ec64. It | |
| * never exceeds INT32_MAX and INT64_MAX respectively. | |
| * | |
| * mode is CK_EC_SP for single producer mode, or CK_EC_MP for | |
| * multipler producer mode. | |
| * | |
| * deadline is either NULL, or a `const struct timespec *` that will | |
| * be compared against CLOCK_MONOTONIC. | |
| * | |
| * `void ck_ec_init(ec)`: zero-initializes the event count. | |
| * | |
| * `value ck_ec_value(ec)`: returns the current value of the event | |
| * counter. This read acts as an read (acquire) barrier. | |
| * | |
| * `void ck_ec_inc(ec, mode)`: increments the value of the event | |
| * counter by one. This writes acts as a write (release) | |
| * barrier. Wakes up any waiting thread. | |
| * | |
| * `value ck_ec_add(ec, mode, value)`: increments the event counter by | |
| * `value`, and returns the event counter's previous value. This | |
| * write acts as a write (release) barrier. Wakes up any waiting | |
| * thread. | |
| * | |
| * `int ck_ec_deadline(struct timespec *new_deadline, | |
| * const struct timespec *timeout)`: | |
| * computes a deadline `timeout` away from the current time. If | |
| * timeout is NULL, computes a deadline in the infinite future. The | |
| * resulting deadline is written to `new_deadline`. Returns 0 on | |
| * success, and -1 if clock_gettime failed (without touching errno). | |
| * | |
| * `int ck_ec_wait(ec, value, deadline?)`: waits until the event | |
| * counter's value differs from `value`, or, if `deadline` is | |
| * provided and non-NULL, until the current time is after that | |
| * deadline. Use a deadline with tv_sec = 0 for a non-blocking | |
| * execution. Returns 0 if the event counter has changed, and -1 on | |
| * timeout. This function may spuriously return with a timeout if | |
| * clock_gettime fails. | |
| * | |
| * `int ck_ec_wait_pred(ec, value, pred, data, deadline?)`: waits | |
| * until the event counter's value differs from `value`, or until | |
| * `pred` returns non-zero, or, if `deadline` is provided and | |
| * non-NULL, until the current time is after that deadline. Use a | |
| * deadline with tv_sec = 0 for a non-blocking execution. Returns 0 if | |
| * the event counter has changed, `pred`'s return value if non-zero, | |
| * and -1 on timeout. This function may spuriously return with a | |
| * timeout if clock_gettime fails. | |
| * | |
| * `pred` is always called as `pred(data, iteration_deadline, now)`, | |
| * where `iteration_deadline` is a timespec of the deadline for this | |
| * exponential backoff iteration, and `now` is the current time. If | |
| * `pred` returns a non-zero value, that value is immediately returned | |
| * to the waiter. Otherwise, `pred` is free to modify | |
| * `iteration_deadline` (moving it further in the future is a bad | |
| * idea). | |
| * | |
| * Implementation notes | |
| * ==================== | |
| * | |
| * This event count implementation is heavily tied to | |
| * [x86-TSO](https://www.cl.cam.ac.uk/~pes20/weakmemory/cacm.pdf), to | |
| * futexes, and to x86's non-atomic read-modify-write instructions | |
| * (e.g., `add m, r`). Porting it to another architecture or OS will | |
| * likely require a complete redesign, or sacrificing single-producer | |
| * performance. The multiple producer case is trivial: every write to | |
| * the `counter` word is LOCKed. Only the single producer case is | |
| * interesting, due to the way it uses both atomic and non-atomic | |
| * writes to the same memory location. | |
| * | |
| * The reason we can mix atomic and non-atomic writes to the `counter` | |
| * word is that every non-atomic write obviates the need for the | |
| * atomically flipped flag bit: we only use non-atomic writes to | |
| * update the event count, and the atomic flag only informs the | |
| * producer that we would like a futex_wake, because of the update. | |
| * We only require the non-atomic RMW counter update to prevent | |
| * preemption from introducing arbitrarily long worst case delays. | |
| * | |
| * Correctness does not rely on the usual ordering argument: in the | |
| * absence of fences, there is no strict ordering between atomic and | |
| * non-atomic writes. | |
| * | |
| * The key is instead x86-TSO's guarantee that a read is satisfied | |
| * from the most recent buffered write in the local store queue if | |
| * there is one, or from memory if there is no write to that address | |
| * in the store queue. | |
| * | |
| * x86-TSO's constraint on reads suffices to guarantee that the | |
| * producer will never forget about a counter update. If the last | |
| * update is still queued, the new update will be based on the queued | |
| * value. Otherwise, the new update will be based on the value in | |
| * memory, which may or may not have had its flag flipped. In either | |
| * case, the value of the counter (modulo flag) is correct. | |
| * | |
| * When the producer forwards the counter's value from its store | |
| * queue, the new update might not preserve a flag flip. Any waiter | |
| * thus has to check from time to time to determine if it wasn't | |
| * woken up because the flag bit was silently cleared. | |
| * | |
| * In reality, the store queue in x86-TSO stands for in-flight | |
| * instructions in the chip's out-of-order backend. In the vast | |
| * majority of cases, instructions will only remain in flight for a | |
| * few hundred or thousand of cycles. That's why ck_ec_wait spins on | |
| * the `counter` word for 100 iterations after flipping its flag bit: | |
| * if the counter hasn't changed after that many iterations, it is | |
| * very likely that the producer's next counter update will observe | |
| * the flag flip. | |
| * | |
| * That's still not a hard guarantee of correctness. Conservatively, | |
| * we can expect that no instruction will remain in flight for more | |
| * than 1 second... if only because some interrupt will have forced | |
| * the chip to store its architectural state in memory, at which point | |
| * an instruction is either fully retired or rolled back. Interrupts, | |
| * particularly the pre-emption timer, are why single-producer updates | |
| * must happen in a single non-atomic read-modify-write instruction. | |
| * Having a single instruction as the critical section means we only | |
| * have to consider the worst-case execution time for that | |
| * instruction. That's easier than doing the same for a pair of | |
| * instructions, which an unlucky pre-emption could delay for | |
| * arbitrarily long. | |
| * | |
| * Thus, after a few iterations of a spin loop, ck_ec_wait enters an | |
| * exponential backoff loop, where each "sleep" is instead a | |
| * futex_wait. The backoff is only necessary to handle rare cases | |
| * where the flag flip was overwritten after the spin | |
| * loop. Eventually, more than one second will have elapsed since the | |
| * flag flip, and the sleep timeout becomes infinite: since the flag | |
| * bit has been set for much longer than the time for which an | |
| * instruction may remain in flight, the flag will definitely be | |
| * observed at the next counter update. | |
| * | |
| * The 64 bit ck_ec_wait pulls another trick: futexes only handle 32 | |
| * bit ints, so we must treat the 64 bit counter's low 32 bits as an | |
| * int in futex_wait. That's a bit dodgy, but fine in practice, given | |
| * that the OS's futex code will always read whatever value is | |
| * currently in memory: even if the producer thread were to wait on | |
| * its own event count, the syscall and ring transition would empty | |
| * the store queue (the out-of-order execution backend). | |
| * | |
| * Finally, what happens when the producer is migrated to another core | |
| * or otherwise pre-empted? Migration must already incur a barrier, so | |
| * that thread always sees its own writes, so that's safe. As for | |
| * pre-emption, that requires storing the architectural state, which | |
| * means every instruction must either be executed fully or not at | |
| * all when pre-emption happens. | |
| */ | |
| #ifndef CK_EC_H | |
| #define CK_EC_H | |
| #include <ck_cc.h> | |
| #include <ck_pr.h> | |
| #include <ck_limits.h> | |
| #include <ck_stdint.h> | |
| #include <ck_stddef.h> | |
| #include <sys/time.h> | |
| enum ck_ec_mode { | |
| CK_EC_SP = 0, /* Single producer. */ | |
| CK_EC_MP = 1 /* Multiple producers. */ | |
| }; | |
| #define CK_EC_INITIALIZER { .counter = 0 } | |
| struct ck_ec32 { | |
| /* Flag is "sign" bit, value in bits 0:30. */ | |
| uint32_t counter; | |
| }; | |
| struct ck_ec64 { | |
| /* | |
| * Flag is bottom bit, value in bits 1:63. Eventcount only | |
| * works on x86-64 (i.e., little endian), so the futex int | |
| * lies in the first 4 (bottom) bytes. | |
| */ | |
| uint64_t counter; | |
| }; | |
| static void ck_ec32_init(struct ck_ec32 *ec); | |
| static void ck_ec64_init(struct ck_ec64 *ec); | |
| #if __STDC_VERSION__ >= 201112L | |
| #define ck_ec_init(EC) \ | |
| (_Generic(*(EC), \ | |
| struct ck_ec32 : ck_ec32_init, \ | |
| struct ck_ec64 : ck_ec64_init)((EC))) | |
| #endif | |
| /* | |
| * Returns the counter value in the event count. The value is at most | |
| * INT32_MAX. | |
| */ | |
| static uint32_t ck_ec32_value(const struct ck_ec32* ec); | |
| /* | |
| * Returns the counter value in the event count. The value is at most | |
| * INT64_MAX. | |
| */ | |
| static uint64_t ck_ec64_value(const struct ck_ec64* ec); | |
| #if __STDC_VERSION__ >= 201112L | |
| #define ck_ec_value(EC) \ | |
| (_Generic(*(EC), \ | |
| struct ck_ec32 : ck_ec32_value, \ | |
| struct ck_ec64 : ck_ec64_value)((EC))) | |
| #endif | |
| /* | |
| * Increments the counter value in the event count by one, and wakes | |
| * up any waiter. | |
| */ | |
| static void ck_ec32_inc(struct ck_ec32 *ec, enum ck_ec_mode mode); | |
| static void ck_ec64_inc(struct ck_ec64 *ec, enum ck_ec_mode mode); | |
| #if __STDC_VERSION__ >= 201112L | |
| #define ck_ec_inc(EC, MODE) \ | |
| (_Generic(*(EC), \ | |
| struct ck_ec32 : ck_ec32_inc, \ | |
| struct ck_ec64 : ck_ec64_inc)((EC), (MODE))) | |
| #endif | |
| /* | |
| * Increments the counter value in the event count by delta, wakes | |
| * up any waiter, and returns the previous counter value. | |
| */ | |
| static uint32_t ck_ec32_add(struct ck_ec32 *ec, enum ck_ec_mode mode, | |
| uint32_t delta); | |
| static uint64_t ck_ec64_add(struct ck_ec64 *ec, enum ck_ec_mode mode, | |
| uint64_t delta); | |
| #if __STDC_VERSION__ >= 201112L | |
| #define ck_ec_add(EC, MODE, DELTA) \ | |
| (_Generic(*(EC), \ | |
| struct ck_ec32 : ck_ec32_add, \ | |
| struct ck_ec64 : ck_ec64_add)((EC), (MODE), (DELTA))) | |
| #endif | |
| /* | |
| * Populates `new_deadline` with a deadline `timeout` in the future. | |
| * Returns 0 on success, and -1 if clock_gettime failed, in which | |
| * case errno is left as is. | |
| */ | |
| int ck_ec_deadline(struct timespec *new_deadline, | |
| const struct timespec *timeout); | |
| /* | |
| * Waits until the counter value in the event count differs from | |
| * old_value, or, if deadline is non-NULL, until CLOCK_MONOTONIC is | |
| * past the deadline. | |
| * | |
| * Returns 0 on success, and -1 on timeout. | |
| */ | |
| static int ck_ec32_wait(struct ck_ec32 *ec, uint32_t old_value, | |
| const struct timespec *deadline); | |
| static int ck_ec64_wait(struct ck_ec64 *ec, uint64_t old_value, | |
| const struct timespec *deadline); | |
| #if __STDC_VERSION__ >= 201112L | |
| #define ck_ec_wait(EC, OLD_VALUE, ...) \ | |
| ck_ec_wait_impl((EC), (OLD_VALUE), ##__VA_ARGS__, NULL) | |
| #define ck_ec_wait_impl(EC, OLD_VALUE, DEADLINE, ...) \ | |
| (_Generic(*EC, \ | |
| struct ck_ec32 : ck_ec32_wait, \ | |
| struct ck_ec64 : ck_ec64_wait)(EC, OLD_VALUE, (DEADLINE))) | |
| #endif | |
| /* | |
| * Waits until the counter value in the event count differs from | |
| * old_value, pred returns non-zero, or, if deadline is non-NULL, | |
| * until CLOCK_MONOTONIC is past the deadline. | |
| * | |
| * Returns 0 on success, -1 on timeout, and the return value of pred | |
| * if it returns non-zero. | |
| * | |
| * A NULL pred represents a function that always returns 0. | |
| */ | |
| static int ck_ec32_wait_pred(struct ck_ec32 *ec, uint32_t old_value, | |
| int (*pred)(void *data, | |
| struct timespec *deadline, | |
| const struct timespec *now), | |
| void *data, | |
| const struct timespec *deadline); | |
| static int ck_ec64_wait_pred(struct ck_ec64 *ec, uint64_t old_value, | |
| int (*pred)(void *data, | |
| struct timespec *deadline, | |
| const struct timespec *now), | |
| void *data, | |
| const struct timespec *deadline); | |
| #if __STDC_VERSION__ >= 201112L | |
| #define ck_ec_wait_pred(EC, OLD_VALUE, PRED, DATA, ...) \ | |
| ck_ec_wait_pred_impl((EC), (OLD_VALUE), (PRED), (DATA), \ | |
| ##__VA_ARGS__, NULL) | |
| #define ck_ec_wait_pred_impl(EC, OLD_VALUE, PRED, DATA, DEADLINE, ...) \ | |
| (_Generic(*EC, \ | |
| struct ck_ec32 : ck_ec32_wait_pred, \ | |
| struct ck_ec64 : ck_ec64_wait_pred) \ | |
| (EC, OLD_VALUE, PRED, DATA, (DEADLINE))) | |
| #endif | |
| /* | |
| * Inline implementation details. | |
| */ | |
| CK_CC_INLINE void ck_ec32_init(struct ck_ec32 *ec) | |
| { | |
| ec->counter = 0; | |
| return; | |
| } | |
| CK_CC_INLINE void ck_ec64_init(struct ck_ec64 *ec) | |
| { | |
| ec->counter = 0; | |
| return; | |
| } | |
| CK_CC_INLINE uint32_t ck_ec32_value(const struct ck_ec32* ec) | |
| { | |
| return ck_pr_load_32(&ec->counter) & ~(1UL << 31); | |
| } | |
| CK_CC_INLINE uint64_t ck_ec64_value(const struct ck_ec64 *ec) | |
| { | |
| return ck_pr_load_64(&ec->counter) >> 1; | |
| } | |
| /* Slow path for ck_ec{32,64}_{inc,add} */ | |
| void ck_ec32_wake(struct ck_ec32 *ec); | |
| void ck_ec64_wake(struct ck_ec64 *ec); | |
| CK_CC_INLINE void ck_ec32_inc(struct ck_ec32 *ec, enum ck_ec_mode mode) | |
| { | |
| char flagged; | |
| #if !defined(__GNUC__) | |
| # error "ck_ec requires GCC inline assembly extensions." | |
| #elif __GNUC__ >= 6 | |
| /* | |
| * We don't want to wake if the sign bit is 0. We do want to | |
| * wake if the sign bit just flipped from 1 to 0. We don't | |
| * care what happens when our increment caused the sign bit to | |
| * flip from 0 to 1 (that's once per 2^31 increment). | |
| * | |
| * This leaves us with four cases: | |
| * | |
| * old sign bit | new sign bit | SF | OF | ZF | |
| * ------------------------------------------- | |
| * 0 | 0 | 0 | 0 | ? | |
| * 0 | 1 | 1 | 0 | ? | |
| * 1 | 1 | 1 | 0 | ? | |
| * 1 | 0 | 0 | 0 | 1 | |
| * | |
| * In the first case, we don't want to hit ck_ec32_wake. In | |
| * the last two cases, we do want to call ck_ec32_wake. In the | |
| * second case, we don't care, so we arbitrarily choose to | |
| * call ck_ec32_wake. | |
| * | |
| * The "le" condition checks if SF != OF, or ZF == 1, which | |
| * meets our requirements. | |
| */ | |
| #define CK_EC32_INC_ASM(PREFIX) \ | |
| __asm__ volatile(PREFIX " addl $1, %0" \ | |
| : "+m"(ec->counter), "=@ccle"(flagged) \ | |
| :: "cc", "memory") | |
| #else | |
| #define CK_EC32_INC_ASM(PREFIX) \ | |
| __asm__ volatile(PREFIX " addl $1, %0; setle %1" \ | |
| : "+m"(ec->counter), "=r"(flagged) \ | |
| :: "cc", "memory") | |
| #endif | |
| switch (mode) { | |
| case CK_EC_SP: | |
| CK_EC32_INC_ASM(""); | |
| break; | |
| case CK_EC_MP: | |
| default: | |
| CK_EC32_INC_ASM("lock"); | |
| break; | |
| } | |
| #undef CK_EC32_INC_ASM | |
| if (CK_CC_UNLIKELY(flagged)) { | |
| ck_ec32_wake(ec); | |
| } | |
| return; | |
| } | |
| CK_CC_FORCE_INLINE void ck_ec64_inc(struct ck_ec64 *ec, enum ck_ec_mode mode) | |
| { | |
| /* We always xadd, so there's no special optimization here. */ | |
| (void)ck_ec64_add(ec, mode, 1); | |
| return; | |
| } | |
| CK_CC_INLINE uint32_t ck_ec32_add(struct ck_ec32 *ec, enum ck_ec_mode mode, | |
| uint32_t delta) | |
| { | |
| const uint32_t flag_mask = 1U << 31; | |
| uint32_t old = delta; | |
| uint32_t ret; | |
| #define CK_EC32_ADD_ASM(PREFIX) \ | |
| __asm__ volatile(PREFIX " xaddl %1, %0" \ | |
| : "+m"(ec->counter), "+r"(old) \ | |
| :: "cc", "memory") | |
| switch (mode) { | |
| case CK_EC_SP: | |
| CK_EC32_ADD_ASM(""); | |
| break; | |
| case CK_EC_MP: | |
| CK_EC32_ADD_ASM("lock"); | |
| break; | |
| } | |
| #undef CK_EC32_ADD_ASM | |
| ret = old & ~flag_mask; | |
| /* These two only differ if the flag bit is set. */ | |
| if (CK_CC_UNLIKELY(old != ret)) { | |
| ck_ec32_wake(ec); | |
| } | |
| return ret; | |
| } | |
| CK_CC_INLINE uint64_t ck_ec64_add(struct ck_ec64 *ec, enum ck_ec_mode mode, | |
| uint64_t delta) | |
| { | |
| uint64_t old = 2 * delta; /* The low bit is the flag bit. */ | |
| uint64_t ret; | |
| #define CK_EC64_ADD_ASM(PREFIX) \ | |
| __asm__ volatile(PREFIX " xaddq %1, %0" \ | |
| : "+m"(ec->counter), "+r"(old) \ | |
| :: "cc", "memory") | |
| switch (mode) { | |
| case CK_EC_SP: | |
| CK_EC64_ADD_ASM(""); | |
| break; | |
| case CK_EC_MP: | |
| CK_EC64_ADD_ASM("lock"); | |
| break; | |
| } | |
| #undef CK_EC64_ADD_ASM | |
| ret = old >> 1; | |
| if (CK_CC_UNLIKELY(old & 1)) { | |
| ck_ec64_wake(ec); | |
| } | |
| return ret; | |
| } | |
| int ck_ec32_wait_slow(struct ck_ec32 *ec, uint32_t old_value, | |
| const struct timespec *deadline); | |
| int ck_ec64_wait_slow(struct ck_ec64 *ec, uint64_t old_value, | |
| const struct timespec *deadline); | |
| CK_CC_INLINE int ck_ec32_wait(struct ck_ec32 *ec, uint32_t old_value, | |
| const struct timespec *deadline) | |
| { | |
| if (ck_ec32_value(ec) != old_value) { | |
| return 0; | |
| } | |
| return ck_ec32_wait_slow(ec, old_value, deadline); | |
| } | |
| CK_CC_INLINE int ck_ec64_wait(struct ck_ec64 *ec, uint64_t old_value, | |
| const struct timespec *deadline) | |
| { | |
| if (ck_ec64_value(ec) != old_value) { | |
| return 0; | |
| } | |
| return ck_ec64_wait_slow(ec, old_value, deadline); | |
| } | |
| int ck_ec32_wait_pred_slow(struct ck_ec32 *ec, uint32_t old_value, | |
| int (*pred)(void *data, | |
| struct timespec *deadline, | |
| const struct timespec *now), | |
| void *data, | |
| const struct timespec *deadline); | |
| int ck_ec64_wait_pred_slow(struct ck_ec64 *ec, uint64_t old_value, | |
| int (*pred)(void *data, | |
| struct timespec *deadline, | |
| const struct timespec *now), | |
| void *data, | |
| const struct timespec *deadline); | |
| CK_CC_INLINE int ck_ec32_wait_pred(struct ck_ec32 *ec, uint32_t old_value, | |
| int (*pred)(void *data, | |
| struct timespec *deadline, | |
| const struct timespec *now), | |
| void *data, | |
| const struct timespec *deadline) | |
| { | |
| if (ck_ec32_value(ec) != old_value) { | |
| return 0; | |
| } | |
| return ck_ec32_wait_pred_slow(ec, old_value, pred, data, deadline); | |
| } | |
| CK_CC_INLINE int ck_ec64_wait_pred(struct ck_ec64 *ec, uint64_t old_value, | |
| int (*pred)(void *data, | |
| struct timespec *deadline, | |
| const struct timespec *now), | |
| void *data, | |
| const struct timespec *deadline) | |
| { | |
| if (ck_ec64_value(ec) != old_value) { | |
| return 0; | |
| } | |
| return ck_ec64_wait_pred_slow(ec, old_value, pred, data, deadline); | |
| } | |
| #endif /* !CK_EC_H */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include "ck_ec.h" | |
| #include <assert.h> | |
| #include <pthread.h> | |
| #include <stdio.h> | |
| #include <unistd.h> | |
| #define TIME_MAX (((unsigned long long)((time_t)-1)) / 2) | |
| static void test_update_counter_32(enum ck_ec_mode mode) | |
| { | |
| struct ck_ec32 ec = CK_EC_INITIALIZER; | |
| assert(ck_ec_value(&ec) == 0); | |
| ck_ec_inc(&ec, mode); | |
| assert(ck_ec_value(&ec) == 1); | |
| uint32_t old = ck_ec_add(&ec, mode, 42); | |
| assert(old == 1); | |
| assert(ck_ec_value(&ec) == 43); | |
| return; | |
| } | |
| static void test_update_counter_64(enum ck_ec_mode mode) | |
| { | |
| struct ck_ec64 ec = CK_EC_INITIALIZER; | |
| assert(ck_ec_value(&ec) == 0); | |
| ck_ec_inc(&ec, mode); | |
| assert(ck_ec_value(&ec) == 1); | |
| uint64_t old = ck_ec_add(&ec, mode, 42); | |
| assert(old == 1); | |
| assert(ck_ec_value(&ec) == 43); | |
| return; | |
| } | |
| static void test_deadline(void) | |
| { | |
| struct timespec deadline; | |
| assert(ck_ec_deadline(&deadline, NULL) == 0); | |
| assert(deadline.tv_sec == TIME_MAX); | |
| { | |
| const struct timespec timeout = { | |
| .tv_sec = 1, | |
| .tv_nsec = 1000 | |
| }; | |
| const struct timespec no_timeout = { | |
| .tv_sec = 0 | |
| }; | |
| struct timespec now; | |
| assert(ck_ec_deadline(&deadline, &timeout) == 0); | |
| assert(ck_ec_deadline(&now, &no_timeout) == 0); | |
| double now_sec = now.tv_sec + 1e-9 * now.tv_nsec; | |
| double deadline_sec = deadline.tv_sec + 1e-9 * deadline.tv_nsec; | |
| assert(now_sec < deadline_sec); | |
| assert(deadline_sec <= now_sec + 1 + 1000e-9); | |
| } | |
| { | |
| const struct timespec timeout = { | |
| .tv_sec = TIME_MAX - 1, | |
| .tv_nsec = 1000 | |
| }; | |
| assert(ck_ec_deadline(&deadline, &timeout) == 0); | |
| assert(deadline.tv_sec == TIME_MAX); | |
| } | |
| return; | |
| } | |
| static void test_wait_32(void) | |
| { | |
| struct timespec deadline = { .tv_sec = 0 }; | |
| struct ck_ec32 ec; | |
| ck_ec_init(&ec); | |
| assert(ck_ec_value(&ec) == 0); | |
| assert(ck_ec_wait(&ec, 1) == 0); | |
| assert(ck_ec_wait(&ec, 1, NULL) == 0); | |
| assert(ck_ec_wait(&ec, 0, &deadline) == -1); | |
| { | |
| const struct timespec timeout = { .tv_sec = 0 }; | |
| assert(ck_ec_deadline(&deadline, &timeout) == 0); | |
| assert(ck_ec_wait(&ec, 0, &deadline) == -1); | |
| } | |
| return; | |
| } | |
| static void test_wait_64(void) | |
| { | |
| struct timespec deadline = { .tv_sec = 0 }; | |
| struct ck_ec64 ec; | |
| ck_ec_init(&ec); | |
| assert(ck_ec_value(&ec) == 0); | |
| assert(ck_ec_wait(&ec, 1) == 0); | |
| assert(ck_ec_wait(&ec, 1, NULL) == 0); | |
| assert(ck_ec_wait(&ec, 0, &deadline) == -1); | |
| { | |
| const struct timespec timeout = { .tv_sec = 0 }; | |
| assert(ck_ec_deadline(&deadline, &timeout) == 0); | |
| assert(ck_ec_wait(&ec, 0, &deadline) == -1); | |
| } | |
| return; | |
| } | |
| static int pred(void *data, | |
| struct timespec *deadline, | |
| const struct timespec *now) | |
| { | |
| int *count = data; | |
| (void)deadline; | |
| (void)now; | |
| if ((*count)++ < 3) { | |
| return 0; | |
| } | |
| return (*count)++; | |
| } | |
| /* Check that pred's return value is correctly bubbled up. */ | |
| static void test_wait_pred_32(void) | |
| { | |
| struct ck_ec32 ec = CK_EC_INITIALIZER; | |
| int count = 0; | |
| assert(ck_ec_wait_pred(&ec, 0, pred, &count) == 4); | |
| assert(count == 5); | |
| return; | |
| } | |
| static int pred2(void *data, | |
| struct timespec *deadline, | |
| const struct timespec *now) | |
| { | |
| int *count = data; | |
| *deadline = *now; | |
| deadline->tv_sec++; | |
| (*count)++; | |
| return 0; | |
| } | |
| /* | |
| * wait_pred_64 is nearly identical to _32. Now check that deadline | |
| * overriding works. | |
| */ | |
| static void test_wait_pred_64(void) | |
| { | |
| const struct timespec timeout = { .tv_sec = 5 }; | |
| struct timespec deadline; | |
| struct ck_ec64 ec = CK_EC_INITIALIZER; | |
| int count = 0; | |
| assert(ck_ec_deadline(&deadline, &timeout) == 0); | |
| assert(ck_ec_wait_pred(&ec, 0, pred2, &count, &deadline) == -1); | |
| assert(count == 5); | |
| return; | |
| } | |
| static int woken = 0; | |
| static void *test_threaded_32_waiter(void *data) | |
| { | |
| struct ck_ec32 *ec = data; | |
| ck_ec_wait(ec, 0); | |
| ck_pr_store_int(&woken, 1); | |
| return NULL; | |
| } | |
| static void test_threaded_inc_32(enum ck_ec_mode mode) | |
| { | |
| struct ck_ec32 ec = CK_EC_INITIALIZER; | |
| pthread_t waiter; | |
| ck_pr_store_int(&woken, 0); | |
| pthread_create(&waiter, NULL, test_threaded_32_waiter, &ec); | |
| usleep(10000); | |
| assert(ck_pr_load_int(&woken) == 0); | |
| ck_ec_inc(&ec, mode); | |
| pthread_join(waiter, NULL); | |
| assert(ck_pr_load_int(&woken) == 1); | |
| return; | |
| } | |
| static void test_threaded_add_32(enum ck_ec_mode mode) | |
| { | |
| struct ck_ec32 ec = CK_EC_INITIALIZER; | |
| pthread_t waiter; | |
| ck_pr_store_int(&woken, 0); | |
| pthread_create(&waiter, NULL, test_threaded_32_waiter, &ec); | |
| usleep(10000); | |
| assert(ck_pr_load_int(&woken) == 0); | |
| ck_ec_add(&ec, mode, 4); | |
| pthread_join(waiter, NULL); | |
| assert(ck_pr_load_int(&woken) == 1); | |
| return; | |
| } | |
| static void *test_threaded_64_waiter(void *data) | |
| { | |
| struct ck_ec64 *ec = data; | |
| ck_ec_wait(ec, 0); | |
| ck_pr_store_int(&woken, 1); | |
| return NULL; | |
| } | |
| static void test_threaded_inc_64(enum ck_ec_mode mode) | |
| { | |
| struct ck_ec64 ec = CK_EC_INITIALIZER; | |
| pthread_t waiter; | |
| ck_pr_store_int(&woken, 0); | |
| pthread_create(&waiter, NULL, test_threaded_64_waiter, &ec); | |
| usleep(10000); | |
| assert(ck_pr_load_int(&woken) == 0); | |
| ck_ec_inc(&ec, mode); | |
| pthread_join(waiter, NULL); | |
| assert(ck_pr_load_int(&woken) == 1); | |
| return; | |
| } | |
| static void test_threaded_add_64(enum ck_ec_mode mode) | |
| { | |
| struct ck_ec64 ec = CK_EC_INITIALIZER; | |
| pthread_t waiter; | |
| ck_pr_store_int(&woken, 0); | |
| pthread_create(&waiter, NULL, test_threaded_64_waiter, &ec); | |
| usleep(10000); | |
| assert(ck_pr_load_int(&woken) == 0); | |
| ck_ec_add(&ec, mode, 4); | |
| pthread_join(waiter, NULL); | |
| assert(ck_pr_load_int(&woken) == 1); | |
| return; | |
| } | |
| int main () | |
| { | |
| test_update_counter_32(CK_EC_SP); | |
| test_update_counter_64(CK_EC_SP); | |
| printf("test_update_counter SP passed.\n"); | |
| test_update_counter_32(CK_EC_MP); | |
| test_update_counter_64(CK_EC_MP); | |
| printf("test_update_counter MP passed.\n"); | |
| test_deadline(); | |
| printf("test_deadline passed.\n"); | |
| test_wait_32(); | |
| test_wait_64(); | |
| printf("test_wait passed.\n"); | |
| test_wait_pred_32(); | |
| test_wait_pred_64(); | |
| printf("test_wait_pred passed.\n"); | |
| test_threaded_inc_32(CK_EC_SP); | |
| test_threaded_add_32(CK_EC_SP); | |
| test_threaded_inc_64(CK_EC_SP); | |
| test_threaded_add_64(CK_EC_SP); | |
| printf("test_threaded SP passed.\n"); | |
| test_threaded_inc_32(CK_EC_MP); | |
| test_threaded_add_32(CK_EC_MP); | |
| test_threaded_inc_64(CK_EC_MP); | |
| test_threaded_add_64(CK_EC_MP); | |
| printf("test_threaded MP passed.\n"); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment