├── .clang-format ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── common.mk ├── include ├── logger.h ├── skinny_mutex.h ├── tasklet.h ├── thread.h ├── threadpool.h └── threadtracer.h ├── scripts ├── aspell-pws ├── commit-msg.hook ├── install-git-hooks ├── pre-commit.hook └── pre-push.hook ├── src ├── skinny_mutex.c ├── tasklet.c ├── thread.c ├── threadpool.c └── threadtracer.c └── tests ├── test-heavy.c ├── test-shutdown.c ├── test-skinny-mutex.c ├── test-tasklet.c └── test-threadpool.c /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Chromium 2 | Language: Cpp 3 | MaxEmptyLinesToKeep: 3 4 | IndentCaseLabels: false 5 | AllowShortIfStatementsOnASingleLine: false 6 | AllowShortCaseLabelsOnASingleLine: false 7 | AllowShortLoopsOnASingleLine: false 8 | DerivePointerAlignment: false 9 | PointerAlignment: Right 10 | SpaceAfterCStyleCast: true 11 | TabWidth: 4 12 | UseTab: Never 13 | IndentWidth: 4 14 | BreakBeforeBraces: Linux 15 | AccessModifierOffset: -4 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.o.d 3 | 4 | # Object files 5 | *.o 6 | 7 | # Executables 8 | tests/test-threadpool 9 | tests/test-heavy 10 | tests/test-shutdown 11 | tests/test-skinny-mutex 12 | 13 | # Others 14 | threadtracer.*.json 15 | *.ok 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2020 National Cheng Kung University, Taiwan. 4 | Copyright (C) 2017 Abraham Stolk 5 | Copyright (c) 2013 David Wragg 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TESTS = \ 2 | skinny-mutex \ 3 | tasklet \ 4 | threadpool \ 5 | heavy \ 6 | shutdown 7 | TESTS := $(addprefix tests/test-,$(TESTS)) 8 | deps := $(TESTS:%=%.o.d) 9 | 10 | .PHONY: all check clean 11 | GIT_HOOKS := .git/hooks/applied 12 | all: $(GIT_HOOKS) $(TESTS) 13 | 14 | $(GIT_HOOKS): 15 | @scripts/install-git-hooks 16 | @echo 17 | 18 | include common.mk 19 | 20 | CFLAGS = -I./include 21 | CFLAGS += -std=gnu11 -Wall -W 22 | CFLAGS += -D_GNU_SOURCE 23 | CFLAGS += -DUNUSED="__attribute__((unused))" 24 | LDFLAGS = -lpthread 25 | 26 | TESTS_OK = $(TESTS:=.ok) 27 | check: $(TESTS_OK) 28 | 29 | $(TESTS_OK): %.ok: % 30 | $(Q)$(PRINTF) "*** Validating $< ***\n" 31 | $(Q)./$< && $(PRINTF) "\t$(PASS_COLOR)[ Verified ]$(NO_COLOR)\n" 32 | @touch $@ 33 | 34 | # standard build rules 35 | .SUFFIXES: .o .c 36 | .c.o: 37 | $(VECHO) " CC\t$@\n" 38 | $(Q)$(CC) -o $@ $(CFLAGS) -c -MMD -MF $@.d $< 39 | 40 | OBJS = \ 41 | src/skinny_mutex.o \ 42 | src/thread.o \ 43 | src/tasklet.o \ 44 | src/threadpool.o \ 45 | src/threadtracer.o 46 | deps += $(OBJS:%.o=%.o.d) 47 | 48 | $(TESTS): %: %.o $(OBJS) 49 | $(VECHO) " LD\t$@\n" 50 | $(Q)$(CC) -o $@ $^ $(LDFLAGS) 51 | 52 | clean: 53 | $(VECHO) " Cleaning...\n" 54 | $(Q)$(RM) $(TESTS) $(TESTS_OK) $(TESTS:=.o) $(OBJS) threadtracer*.json $(deps) 55 | 56 | -include $(deps) 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ThreadKit: A collection of lightweight threading utilities 2 | 3 | This package `ThreadKit` contains the following threading utilities: 4 | 1. Thread pool: simple and usable thread pool. 5 | 2. Thread tracer: Lightweight inline thread profiler. 6 | 3. Skinny mutex: Low-memory-footprint mutexes for POSIX Threads. 7 | 4. Tasklet: Very lightweight thread without its own stack. 8 | 9 | ## Thread pool 10 | 11 | Currently, this thread pool implementation 12 | * Works with pthreads only, but API is intentionally opaque to allow 13 | other implementations 14 | * Starts all threads on creation of the thread pool. 15 | * Reserves one task for signaling the queue is full. 16 | * Stops and joins all worker threads on destroy. 17 | 18 | ### Possible enhancements 19 | 20 | Allow some additional options: 21 | * Lazy creation of threads 22 | * Reduce number of threads automatically 23 | * Unlimited queue size 24 | * Kill worker threads on destroy 25 | * Reduce locking contention 26 | 27 | ## ThreadTracer 28 | 29 | `ThreadTracer` is a lightweight inline profiler that measures wall-time, 30 | cpu-time and premptive context switches for threads. 31 | 32 | ### Features 33 | 34 | ThreadTracer is an inline profiler that is special in the following ways: 35 | * Fully supports multi threaded applications. 36 | * Will never cause your thread to go to sleep because of profiling. 37 | * Will not miss events. 38 | * Will detect if threads were context-switched by scheduler, preemptively or voluntarily. 39 | * Computes duty-cycle for each scope: not just how long it ran, but also how much of that time, it was scheduled on a core. 40 | * Small light weight system, written in C. Just one header and one small implementation file. 41 | * Zero dependencies. 42 | 43 | ### Limitations 44 | * Doesn't show a live profile, but creates a report after the run, [viewable with Google Chrome](https://www.gamasutra.com/view/news/176420/Indepth_Using_Chrometracing_to_view_your_inline_profiling_data.php). 45 | * Currently does not support asynchronous events that start on one thread, and finish on another. 46 | 47 | ### Usage 48 | 49 | ```c 50 | #include "threadtracer.h" 51 | 52 | // Each thread that will be generating profiling events needs to be made known to the system. 53 | TT_ENTRY(); 54 | 55 | // C Programs need to wrap sections of code with a begin and end macro. 56 | TT_BEGIN("simulation"); 57 | simulate( dt ); 58 | TT_END("simulation"); 59 | 60 | // When you are done profiling, typically at program end, or earlier, you can generate the profile report. 61 | TT_REPORT(); 62 | ``` 63 | 64 | ### Viewing the report 65 | 66 | Start the Google Chrome browser, and in the URL bar, type `chrome://tracing` and 67 | then load the genererated `threadtracer*.json` file. 68 | 69 | ![screenshot](https://pbs.twimg.com/media/DNZe7tRVwAAm2_-.png) 70 | 71 | Note that for the highlighted task, the detail view shows that the thread got 72 | interrupted once preemptively, which causes it to run on a CPU core for only 73 | 81% of the time that the task took to complete. 74 | 75 | The shading of the time slices shows the duty cycle: how much of the time was 76 | spend running on a core. 77 | 78 | ### Skipping samples at launch. 79 | 80 | To avoid recording samples right after launch, you can skip the first seconds 81 | of recording with an environment variable. To skip the first five seconds, do: 82 | 83 | ```shell 84 | $ THREADTRACERSKIP=5 ./foo 85 | ThreadTracer: clock resolution: 1 nsec. 86 | ThreadTracer: skipping the first 5 seconds before recording. 87 | ThreadTracer: Wrote 51780 events (6 discarded) to threadtracer.json 88 | ``` 89 | 90 | ### Reference 91 | 92 | * [chrome://tracing](https://www.chromium.org/developers/how-tos/trace-event-profiling-tool) 93 | for their excellent in-browser visualization. 94 | 95 | ## Skinny mutex 96 | 97 | The main kind of lock provided by the pthreads API is the mutex 98 | (`pthread_mutex_t`). These have a lot of features (enabled though the 99 | attributes set in `pthread_mutexattr_t`), integrate with condition variables, 100 | and handle contention gracefully. 101 | 102 | But a drawback is their size. On Linux, a `pthread_mutex_t` occupies 64 bytes 103 | on 64-bit machines. If the mutex is protecting a small data structure, this 104 | can lead to unwelcome overheads in memory usage, and reduce the effectiveness 105 | of caches. 106 | 107 | Some pthreads implementations also have spinlocks (`pthread_spinlock_t`). 108 | These are smaller (4 bytes on Linux). But they don't handle contention 109 | gracefully, so they are best used for critical sections containing small 110 | amounts of code that can be verified to have a short bounded running time. 111 | 112 | Hence skinny mutexes provide mutexes that occupy one pointer-sized word. 113 | Like pthreads mutexes, they integrate with condition variables and handle 114 | contention gracefully, so code using pthreads mutexes can be easily converted 115 | to use skinny mutexes instead. 116 | 117 | Skinny mutexes use atomic operations to when possible (e.g. when locking or 118 | unlocking an uncontended skinny mutex), and fall back to the pthreads 119 | primitives when necessary (e.g. when a lock is contended causing a thread to 120 | block). So you will still need to compile with `-pthread`. Performance should 121 | generally be similar to pthreads mutexes, and it might even be better in some 122 | cases. 123 | 124 | Pthread | Skinny mutex 125 | ----------------------------|----------------- 126 | `pthread_mutex_t` | `skinny_mutex_t` 127 | `pthread_mutex_init` | `skinny_mutex_init` 128 | `pthread_mutex_destroy` | `skinny_mutex_destroy` 129 | `pthread_mutex_lock` | `skinny_mutex_lock` 130 | `pthread_mutex_unlock` | `skinny_mutex_unlock` 131 | `pthread_mutex_trylock` | `skinny_mutex_trylock` 132 | `pthread_cond_wait` | `skinny_mutex_cond_wait` 133 | `pthread_cond_timedwait` | `skinny_mutex_cond_timedwait` 134 | `PTHREAD_MUTEX_INITIALIZER` | `SKINNY_MUTEX_INITIALIZER` 135 | 136 | Note that `skinny_mutex_init` does not take an attributes argument (see below 137 | for more details). Other than that, all the arguments of the functions 138 | mentioned correspond to the pthreads ones, and their specifications and 139 | return values are intended to correspond exactly. 140 | 141 | In particular, `skinny_mutex_lock` is not a thread cancellation point, and 142 | `skinny_mutex_cond_wait` is. 143 | 144 | ### Limitations compared to `pthread_mutex` 145 | 146 | Unlike pthreads mutexes, skinny mutexes do not currently support any mutex 147 | attributes. Their behavior corresponds to the default pthread mutex 148 | attributes (i.e. with `NULL` passed as the second argument to 149 | `pthread_mutex_init`). 150 | 151 | It is possible to add support for error checking corresponding to the 152 | `PTHREAD_MUTEX_ERRORCHECK` type attribute (from `pthread_mutexattr_settype`). 153 | This will probably be a compile-time option. 154 | 155 | It seems feasible to add support for the protocol attribute 156 | (`PTHREAD_PRIO_INHERIT` and `PTHREAD_PRIO_PROTECT` from 157 | `pthread_mutexattr_setprotocol`). There might be room for improvements. 158 | 159 | The `PTHREAD_MUTEX_RECURSIVE` type attribute will not be supported, as 160 | it would require `skinny_mutex_t` to grow, and you can rewrite code to 161 | avoid the need for recursive mutexes. 162 | 163 | Support for the process-shared and priority ceiling attributes 164 | (`pthread_mutexattr_setpshared` and 165 | `pthread_mutexattr_setprioceiling`) is also unlikely, as they seem to 166 | be of marginal usefulness and/or hard to implement. 167 | 168 | ## Tasklet 169 | 170 | A tasklet is a sequential context of execution. Like a thread, a tasklet can 171 | wait for events (such as data arriving on a socket). Unlike a thread, 172 | a tasklet does not have its own stack, so tasklet code has to be follow 173 | certain idioms. But those idioms are less cumbersome than trying to write 174 | callback-based code in C, particularly in a multithreaded context. 175 | 176 | Tasklets are very lightweight; many millions of tasklets could fit in the 177 | memory of a modern machine. A scalable service can schedule runnable tasklets 178 | onto a much smaller number of threads. 179 | -------------------------------------------------------------------------------- /common.mk: -------------------------------------------------------------------------------- 1 | UNAME_S := $(shell uname -s) 2 | ifeq ($(UNAME_S),Darwin) 3 | PRINTF = printf 4 | else 5 | PRINTF = env printf 6 | endif 7 | 8 | # Control the build verbosity 9 | ifeq ("$(VERBOSE)","1") 10 | Q := 11 | VECHO = @true 12 | else 13 | Q := @ 14 | VECHO = @$(PRINTF) 15 | endif 16 | 17 | PASS_COLOR = \e[32;01m 18 | NO_COLOR = \e[0m 19 | -------------------------------------------------------------------------------- /include/logger.h: -------------------------------------------------------------------------------- 1 | #ifndef DBG_H 2 | #define DBG_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define clean_errno() (errno == 0 ? "None" : strerror(errno)) 10 | 11 | #define log_err(M, ...) \ 12 | fprintf(stderr, "[ERROR] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, \ 13 | clean_errno(), ##__VA_ARGS__) 14 | 15 | #define log_info(M, ...) \ 16 | fprintf(stderr, "[INFO] (%s:%d) " M "\n", __FILE__, __LINE__, ##__VA_ARGS__) 17 | 18 | #define check(A, M, ...) \ 19 | if (!(A)) { \ 20 | log_err(M "\n", ##__VA_ARGS__); /* exit(1); */ \ 21 | } 22 | 23 | #define check_exit(A, M, ...) \ 24 | if (!(A)) { \ 25 | log_err(M "\n", ##__VA_ARGS__); \ 26 | exit(1); \ 27 | } 28 | 29 | #define check_debug(A, M, ...) \ 30 | if (!(A)) { \ 31 | debug(M "\n", ##__VA_ARGS__); /* exit(1); */ \ 32 | } 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /include/skinny_mutex.h: -------------------------------------------------------------------------------- 1 | #ifndef SKINNY_MUTEX_H 2 | #define SKINNY_MUTEX_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct { 9 | void *val; 10 | } skinny_mutex_t; 11 | 12 | static inline int skinny_mutex_init(skinny_mutex_t *m) 13 | { 14 | m->val = 0; 15 | return 0; 16 | } 17 | 18 | static inline int skinny_mutex_destroy(skinny_mutex_t *m) 19 | { 20 | return !m->val ? 0 : EBUSY; 21 | } 22 | 23 | #define SKINNY_MUTEX_INITIALIZER \ 24 | { \ 25 | (void *) 0 \ 26 | } 27 | 28 | int skinny_mutex_lock_slow(skinny_mutex_t *m); 29 | 30 | static inline int skinny_mutex_lock(skinny_mutex_t *m) 31 | { 32 | if (__builtin_expect( 33 | __sync_bool_compare_and_swap(&m->val, (void *) 0, (void *) 1), 1)) 34 | return 0; 35 | return skinny_mutex_lock_slow(m); 36 | } 37 | 38 | int skinny_mutex_unlock_slow(skinny_mutex_t *m); 39 | 40 | static inline int skinny_mutex_unlock(skinny_mutex_t *m) 41 | { 42 | if (__builtin_expect( 43 | __sync_bool_compare_and_swap(&m->val, (void *) 1, (void *) 0), 1)) 44 | return 0; 45 | return skinny_mutex_unlock_slow(m); 46 | } 47 | 48 | int skinny_mutex_trylock(skinny_mutex_t *m); 49 | int skinny_mutex_cond_wait(pthread_cond_t *cond, skinny_mutex_t *m); 50 | int skinny_mutex_cond_timedwait(pthread_cond_t *cond, 51 | skinny_mutex_t *m, 52 | const struct timespec *abstime); 53 | 54 | int skinny_mutex_transfer(skinny_mutex_t *a, skinny_mutex_t *b); 55 | int skinny_mutex_veto_transfer(skinny_mutex_t *m); 56 | 57 | #endif /* SKINNY_MUTEX_H */ 58 | -------------------------------------------------------------------------------- /include/tasklet.h: -------------------------------------------------------------------------------- 1 | #ifndef TASKLET_H 2 | #define TASKLET_H 3 | 4 | #include 5 | 6 | #include "thread.h" 7 | 8 | struct tasklet { 9 | struct mutex *mutex; 10 | 11 | void (*handler)(void *); 12 | void *data; 13 | 14 | struct mutex wait_mutex; 15 | struct wait_list *wait; /* Covered by wait_mutex */ 16 | int unwaiting; /* Covered by wait_mutex */ 17 | bool waited; 18 | struct tasklet *wait_next; /* Covered by wait's mutex */ 19 | struct tasklet *wait_prev; /* Ditto */ 20 | 21 | struct run_queue *runq; /* Set using atomic ops */ 22 | struct tasklet *runq_next; /* Covered by runq's mutex */ 23 | struct tasklet *runq_prev; /* Ditto */ 24 | }; 25 | 26 | struct wait_list { 27 | struct mutex mutex; 28 | void *head; 29 | int unwaiting; 30 | int up_count; 31 | }; 32 | 33 | struct run_queue *run_queue_create(void); 34 | 35 | /* Set the preferred run queue for this thread. */ 36 | void run_queue_target(struct run_queue *runq); 37 | 38 | /* Serve a run queue. Returns once the run queue is drained. 39 | * If 'wait' is set, waits until tasklets arrive. 40 | */ 41 | void run_queue_run(struct run_queue *runq, int wait); 42 | 43 | void tasklet_init(struct tasklet *tasklet, struct mutex *mutex, void *data); 44 | void tasklet_fini(struct tasklet *t); 45 | void tasklet_stop(struct tasklet *t); 46 | void tasklet_run(struct tasklet *t); 47 | 48 | static inline void tasklet_set_handler(struct tasklet *t, 49 | void (*handler)(void *)) 50 | { 51 | mutex_assert_held(t->mutex); 52 | t->handler = handler; 53 | } 54 | 55 | static inline void tasklet_goto(struct tasklet *t, void (*handler)(void *)) 56 | { 57 | tasklet_set_handler(t, handler); 58 | handler(t->data); 59 | } 60 | 61 | static inline void tasklet_later(struct tasklet *t, void (*handler)(void *)) 62 | { 63 | t->handler = handler; 64 | tasklet_run(t); 65 | } 66 | 67 | void wait_list_init(struct wait_list *w, int up_count); 68 | void wait_list_fini(struct wait_list *w); 69 | void wait_list_up(struct wait_list *w, int n); 70 | bool wait_list_down(struct wait_list *w, int n, struct tasklet *t); 71 | void wait_list_set(struct wait_list *w, int n, bool broadcast); 72 | bool wait_list_nonempty(struct wait_list *w); 73 | 74 | void wait_list_wait(struct wait_list *w, struct tasklet *t); 75 | void wait_list_broadcast(struct wait_list *w); 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /include/thread.h: -------------------------------------------------------------------------------- 1 | #ifndef THREAD_H 2 | #define THREAD_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "skinny_mutex.h" 9 | 10 | typedef pthread_t thread_handle_t; 11 | static inline thread_handle_t thread_handle_current(void) 12 | { 13 | return pthread_self(); 14 | } 15 | 16 | struct thread { 17 | thread_handle_t handle; 18 | void *init; 19 | }; 20 | 21 | void thread_init(struct thread *thr, void (*func)(void *data), void *data); 22 | void thread_fini(struct thread *thr); 23 | 24 | static inline thread_handle_t thread_get_handle(struct thread *thr) 25 | { 26 | return thr->handle; 27 | } 28 | 29 | void thread_signal(thread_handle_t thr, int sig); 30 | 31 | struct mutex { 32 | skinny_mutex_t mutex; 33 | bool held; 34 | void *init; 35 | }; 36 | 37 | struct cond { 38 | pthread_cond_t cond; 39 | void *init; 40 | }; 41 | 42 | #define MUTEX_INITIALIZER \ 43 | { \ 44 | SKINNY_MUTEX_INITIALIZER, FALSE, NULL \ 45 | } 46 | 47 | void mutex_init(struct mutex *m); 48 | void mutex_fini(struct mutex *m); 49 | void mutex_lock(struct mutex *m); 50 | void mutex_unlock(struct mutex *m); 51 | bool mutex_transfer(struct mutex *a, struct mutex *b); 52 | void mutex_veto_transfer(struct mutex *m); 53 | 54 | static inline void mutex_unlock_fini(struct mutex *m) 55 | { 56 | mutex_unlock(m); 57 | mutex_fini(m); 58 | } 59 | 60 | static inline void mutex_assert_held(struct mutex *m) 61 | { 62 | assert(m->held); 63 | } 64 | 65 | void cond_init(struct cond *c); 66 | void cond_fini(struct cond *c); 67 | void cond_wait(struct cond *c, struct mutex *m); 68 | void cond_signal(struct cond *c); 69 | void cond_broadcast(struct cond *c); 70 | 71 | struct tls_var { 72 | pthread_once_t once; 73 | pthread_key_t key; 74 | }; 75 | 76 | #define TLS_VAR_DECLARE_STATIC(name) \ 77 | static pthread_once_t name##_once = PTHREAD_ONCE_INIT; \ 78 | static pthread_key_t name##_key; \ 79 | \ 80 | static void name##_once_func(void) \ 81 | { \ 82 | pthread_key_create(&name##_key, NULL); \ 83 | } 84 | 85 | #define TLS_VAR_GET(name) \ 86 | (pthread_once(&name##_once, name##_once_func), \ 87 | pthread_getspecific(name##_key)) 88 | 89 | #define TLS_VAR_SET(name, val) \ 90 | (pthread_once(&name##_once, name##_once_func), \ 91 | pthread_setspecific(name##_key, val)) 92 | 93 | #endif 94 | -------------------------------------------------------------------------------- /include/threadpool.h: -------------------------------------------------------------------------------- 1 | #ifndef THREADPOOL_H 2 | #define THREADPOOL_H 3 | 4 | #include 5 | 6 | typedef struct threadpool_internal threadpool_t; 7 | 8 | typedef enum { 9 | tp_invalid = -1, 10 | tp_lock_fail = -2, 11 | tp_already_shutdown = -3, 12 | tp_cond_broadcast = -4, 13 | tp_thread_fail = -5, 14 | } threadpool_error_t; 15 | 16 | /** 17 | * @brief Creates a threadpool_t object. 18 | * @param thread_num Number of worker threads. 19 | */ 20 | threadpool_t *threadpool_init(int thread_num); 21 | 22 | /** 23 | * @brief add a new task in the queue of a thread pool. 24 | * @param pool Thread pool to which add the task. 25 | * @param func Pointer to the function that will perform the task. 26 | * @param arg Argument to be passed to the function. 27 | * @return 0 if all goes well, negative values in case of error (@see 28 | * threadpool_error_t for codes). 29 | */ 30 | int threadpool_add(threadpool_t *pool, void (*func)(void *), void *arg); 31 | 32 | /** 33 | * @brief Stops and destroys a thread pool. 34 | * @param pool Thread pool to destroy. 35 | * @param gracegul The thread pool does not accept any new tasks but 36 | * processes all pending tasks before shutdown. 37 | */ 38 | int threadpool_destroy(threadpool_t *pool, bool gracegul); 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /include/threadtracer.h: -------------------------------------------------------------------------------- 1 | #ifndef THREAD_TRACER_H 2 | #define THREAD_TRACER_H 3 | 4 | #include 5 | 6 | #define TT_ENTRY(S) tt_signin(S) 7 | int tt_signin(const char *threadname); 8 | 9 | #define TT_BEGIN(S) tt_stamp("generic", S, "B") 10 | #define TT_END(S) tt_stamp("generic", S, "E") 11 | int tt_stamp(const char *cat, const char *tag, const char *phase); 12 | 13 | #define TT_REPORT() tt_report(NULL) 14 | int tt_report(const char *oname); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /scripts/aspell-pws: -------------------------------------------------------------------------------- 1 | personal_ws-1.1 en 500 2 | usr 3 | lib 4 | sbin 5 | env 6 | bash 7 | etc 8 | var 9 | dudect 10 | runtime 11 | todo 12 | fixme 13 | hotfix 14 | qtest 15 | vscode 16 | sanitizer 17 | unix 18 | linux 19 | valgrind 20 | ubuntu 21 | gdb 22 | sdk 23 | aspell 24 | cppcheck 25 | glibc 26 | git 27 | pre 28 | gcc 29 | clang 30 | enqueue 31 | dequeue 32 | fifo 33 | lifo 34 | stdin 35 | stdout 36 | stderr 37 | strdup 38 | strcmp 39 | strcasecmp 40 | snprintf 41 | sprintf 42 | strcat 43 | strchr 44 | strcmp 45 | strcoll 46 | strcpy 47 | strcspn 48 | strerror 49 | strlen 50 | strncasecmp 51 | strncat 52 | strncmp 53 | strncpy 54 | strpbrk 55 | strrchr 56 | strspn 57 | strstr 58 | strtod 59 | strtof 60 | strtok 61 | strtol 62 | strtold 63 | strtoul 64 | atexit 65 | atof 66 | atoi 67 | atol 68 | bsearch 69 | calloc 70 | fclose 71 | fdopen 72 | feof 73 | ferror 74 | fflush 75 | fgetc 76 | fgetpos 77 | fgets 78 | fileno 79 | fopen 80 | fprintf 81 | fputc 82 | fputs 83 | fread 84 | freopen 85 | fscanf 86 | fseek 87 | fsetpos 88 | ftell 89 | fwrite 90 | getc 91 | getchar 92 | getenv 93 | gets 94 | isalnum 95 | isalpha 96 | isascii 97 | iscntrl 98 | isdigit 99 | isgraph 100 | islower 101 | isprint 102 | ispunct 103 | isspace 104 | isupper 105 | longjmp 106 | memchr 107 | memcmp 108 | memcpy 109 | memmove 110 | memset 111 | printf 112 | putc 113 | putchar 114 | putenv 115 | puts 116 | qsort 117 | rand 118 | realloc 119 | regcomp 120 | regerror 121 | regexec 122 | regfree 123 | rewind 124 | scanf 125 | setbuf 126 | setjmp 127 | signal 128 | srand 129 | sscanf 130 | macOS 131 | Fibonacci 132 | fib 133 | pow 134 | Binet 135 | Vorobev 136 | GMP 137 | MPFR 138 | mutex 139 | trylock 140 | unlock 141 | lseek 142 | llseek 143 | cdev 144 | inode 145 | sysfs 146 | printk 147 | clz 148 | ctz 149 | popcount 150 | fops 151 | init 152 | alloc 153 | ktime 154 | getres 155 | gettime 156 | settime 157 | ns 158 | timespec 159 | timeval 160 | NaN 161 | livepatch 162 | MathEx 163 | vec 164 | expr 165 | httpd 166 | daemon 167 | bench 168 | benchmark 169 | htstress 170 | workqueue 171 | percpu 172 | cmwq 173 | wq 174 | epoll 175 | kthread 176 | sock 177 | tcp 178 | udp 179 | msg 180 | http 181 | kecho 182 | khttpd 183 | KLT 184 | ULT 185 | mutex 186 | sem 187 | cond 188 | condvar 189 | threadpool 190 | skinny 191 | tasklet 192 | -------------------------------------------------------------------------------- /scripts/commit-msg.hook: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # git-good-commit(1) - Git hook to help you write good commit messages. 4 | # Released under the MIT License. 5 | # 6 | # https://github.com/tommarshall/git-good-commit 7 | 8 | COMMIT_MSG_FILE="$1" 9 | COMMIT_MSG_LINES= 10 | HOOK_EDITOR= 11 | SKIP_DISPLAY_WARNINGS=0 12 | WARNINGS= 13 | 14 | RED= 15 | YELLOW= 16 | BLUE= 17 | WHITE= 18 | CYAN= 19 | NC= 20 | 21 | # 22 | # Set colour variables if the output should be coloured. 23 | # 24 | 25 | set_colors() { 26 | local default_color=$(git config --get hooks.goodcommit.color || git config --get color.ui || echo 'auto') 27 | if [[ $default_color == 'always' ]] || [[ $default_color == 'auto' && -t 1 ]]; then 28 | RED='\033[1;31m' 29 | YELLOW='\033[1;33m' 30 | BLUE='\033[1;34m' 31 | WHITE='\033[1;37m' 32 | CYAN='\033[1;36m' 33 | NC='\033[0m' # No Color 34 | fi 35 | } 36 | 37 | # 38 | # Set the hook editor, using the same approach as git. 39 | # 40 | 41 | set_editor() { 42 | # $GIT_EDITOR appears to always be set to `:` when the hook is executed by Git? 43 | # ref: http://stackoverflow.com/q/41468839/885540 44 | # ref: https://github.com/tommarshall/git-good-commit/issues/11 45 | # HOOK_EDITOR=$GIT_EDITOR 46 | test -z "${HOOK_EDITOR}" && HOOK_EDITOR=$(git config --get core.editor) 47 | test -z "${HOOK_EDITOR}" && HOOK_EDITOR=$VISUAL 48 | test -z "${HOOK_EDITOR}" && HOOK_EDITOR=$EDITOR 49 | test -z "${HOOK_EDITOR}" && HOOK_EDITOR='vi' 50 | } 51 | 52 | # 53 | # Output prompt help information. 54 | # 55 | 56 | prompt_help() { 57 | echo -e "${RED}$(cat <<-EOF 58 | e - edit commit message 59 | n - abort commit 60 | ? - print help 61 | EOF 62 | )${NC}" 63 | } 64 | 65 | # 66 | # Add a warning with and . 67 | # 68 | 69 | add_warning() { 70 | local line_number=$1 71 | local warning=$2 72 | WARNINGS[$line_number]="${WARNINGS[$line_number]}$warning;" 73 | } 74 | 75 | # 76 | # Output warnings. 77 | # 78 | 79 | display_warnings() { 80 | if [ $SKIP_DISPLAY_WARNINGS -eq 1 ]; then 81 | # if the warnings were skipped then they should be displayed next time 82 | SKIP_DISPLAY_WARNINGS=0 83 | return 84 | fi 85 | 86 | for i in "${!WARNINGS[@]}"; do 87 | printf "%-74s ${WHITE}%s${NC}\n" "${COMMIT_MSG_LINES[$(($i-1))]}" "[line ${i}]" 88 | IFS=';' read -ra WARNINGS_ARRAY <<< "${WARNINGS[$i]}" 89 | for ERROR in "${WARNINGS_ARRAY[@]}"; do 90 | echo -e " ${YELLOW}- ${ERROR}${NC}" 91 | done 92 | done 93 | 94 | echo 95 | echo -e "${RED}$(cat <<-EOF 96 | How to Write a Git Commit Message: https://chris.beams.io/posts/git-commit/ 97 | EOF 98 | )${NC}" 99 | } 100 | 101 | # 102 | # Read the contents of the commit msg into an array of lines. 103 | # 104 | 105 | read_commit_message() { 106 | # reset commit_msg_lines 107 | COMMIT_MSG_LINES=() 108 | 109 | # read commit message into lines array 110 | while IFS= read -r; do 111 | 112 | # trim trailing spaces from commit lines 113 | shopt -s extglob 114 | REPLY="${REPLY%%*( )}" 115 | shopt -u extglob 116 | 117 | # ignore comments 118 | [[ $REPLY =~ ^# ]] 119 | test $? -eq 0 || COMMIT_MSG_LINES+=("$REPLY") 120 | 121 | done < $COMMIT_MSG_FILE 122 | } 123 | 124 | # 125 | # Validate the contents of the commmit msg agains the good commit guidelines. 126 | # 127 | 128 | validate_commit_message() { 129 | # reset warnings 130 | WARNINGS=() 131 | 132 | # capture the subject, and remove the 'squash! ' prefix if present 133 | COMMIT_SUBJECT=${COMMIT_MSG_LINES[0]/#squash! /} 134 | 135 | # if the commit is empty there's nothing to validate, we can return here 136 | COMMIT_MSG_STR="${COMMIT_MSG_LINES[*]}" 137 | test -z "${COMMIT_MSG_STR[*]// }" && return; 138 | 139 | # if the commit subject starts with 'fixup! ' there's nothing to validate, we can return here 140 | [[ $COMMIT_SUBJECT == 'fixup! '* ]] && return; 141 | 142 | # skip first token in subject (e.g. issue ID from bugtracker which is validated otherwise) 143 | skipfirsttokeninsubject=$(git config --get hooks.goodcommit.subjectskipfirsttoken || echo 'false') 144 | if [ "$skipfirsttokeninsubject" == "true" ]; then 145 | COMMIT_SUBJECT_TO_PROCESS=${COMMIT_SUBJECT#* } 146 | else 147 | COMMIT_SUBJECT_TO_PROCESS=$COMMIT_SUBJECT 148 | fi 149 | 150 | # 0. Check spelling 151 | # ------------------------------------------------------------------------------ 152 | ASPELL=$(which aspell) 153 | if [ $? -ne 0 ]; then 154 | echo "Aspell not installed - unable to check spelling" 155 | else 156 | LINE_NUMBER=1 157 | MISSPELLED_WORDS=`echo "$COMMIT_MSG_LINES[LINE_NUMBER]" | $ASPELL --lang=en --list --home-dir=scripts --personal=aspell-pws` 158 | if [ -n "$MISSPELLED_WORDS" ]; then 159 | add_warning LINE_NUMBER "Possible misspelled word(s): $MISSPELLED_WORDS" 160 | fi 161 | fi 162 | 163 | # 1. Separate subject from body with a blank line 164 | # ------------------------------------------------------------------------------ 165 | 166 | test ${#COMMIT_MSG_LINES[@]} -lt 1 || test -z "${COMMIT_MSG_LINES[1]}" 167 | test $? -eq 0 || add_warning 2 "Separate subject from body with a blank line" 168 | 169 | # 2. Limit the subject line to configured number of characters 170 | # ------------------------------------------------------------------------------ 171 | 172 | subject_max_length=$(git config --get hooks.goodcommit.subjectmaxlength || echo '60') 173 | test "${#COMMIT_SUBJECT}" -le $subject_max_length 174 | test $? -eq 0 || add_warning 1 "Limit the subject line to $subject_max_length characters (${#COMMIT_SUBJECT} chars)" 175 | 176 | # 3. Capitalize the subject line 177 | # ------------------------------------------------------------------------------ 178 | 179 | [[ ${COMMIT_SUBJECT_TO_PROCESS} =~ ^[[:blank:]]*([[:upper:]]{1}[[:lower:]]*|[[:digit:]]+)([[:blank:]]|[[:punct:]]|$) ]] 180 | test $? -eq 0 || add_warning 1 "Capitalize the subject line" 181 | 182 | # 4. Do not end the subject line with a period 183 | # ------------------------------------------------------------------------------ 184 | 185 | [[ ${COMMIT_SUBJECT} =~ [^\.]$ ]] 186 | test $? -eq 0 || add_warning 1 "Do not end the subject line with a period" 187 | 188 | # 5. Use the imperative mood in the subject line 189 | # ------------------------------------------------------------------------------ 190 | 191 | IMPERATIVE_MOOD_BLACKLIST=( 192 | added adds adding 193 | adjusted adjusts adjusting 194 | amended amends amending 195 | avoided avoids avoiding 196 | bumped bumps bumping 197 | changed changes changing 198 | checked checks checking 199 | committed commits committing 200 | copied copies copying 201 | corrected corrects correcting 202 | created creates creating 203 | decreased decreases decreasing 204 | deleted deletes deleting 205 | disabled disables disabling 206 | dropped drops dropping 207 | duplicated duplicates duplicating 208 | enabled enables enabling 209 | excluded excludes excluding 210 | fixed fixes fixing 211 | handled handles handling 212 | implemented implements implementing 213 | improved improves improving 214 | included includes including 215 | increased increases increasing 216 | installed installs installing 217 | introduced introduces introducing 218 | merged merges merging 219 | moved moves moving 220 | pruned prunes pruning 221 | refactored refactors refactoring 222 | released releases releasing 223 | removed removes removing 224 | renamed renames renaming 225 | replaced replaces replacing 226 | resolved resolves resolving 227 | reverted reverts reverting 228 | showed shows showing 229 | tested tests testing 230 | tidied tidies tidying 231 | updated updates updating 232 | used uses using 233 | ) 234 | 235 | # enable case insensitive match 236 | shopt -s nocasematch 237 | 238 | for BLACKLISTED_WORD in "${IMPERATIVE_MOOD_BLACKLIST[@]}"; do 239 | [[ ${COMMIT_SUBJECT_TO_PROCESS} =~ ^[[:blank:]]*$BLACKLISTED_WORD ]] 240 | test $? -eq 0 && add_warning 1 "Use the imperative mood in the subject line, e.g 'fix' not 'fixes'" && break 241 | done 242 | 243 | # disable case insensitive match 244 | shopt -u nocasematch 245 | 246 | # 6. Wrap the body at 72 characters 247 | # ------------------------------------------------------------------------------ 248 | 249 | URL_REGEX='^[[:blank:]]*(https?|ftp|file)://[-A-Za-z0-9\+&@#/%?=~_|!:,.;]*[-A-Za-z0-9\+&@#/%=~_|]' 250 | 251 | for i in "${!COMMIT_MSG_LINES[@]}"; do 252 | LINE_NUMBER=$((i+1)) 253 | test "${#COMMIT_MSG_LINES[$i]}" -le 72 || [[ ${COMMIT_MSG_LINES[$i]} =~ $URL_REGEX ]] 254 | test $? -eq 0 || add_warning $LINE_NUMBER "Wrap the body at 72 characters (${#COMMIT_MSG_LINES[$i]} chars)" 255 | done 256 | 257 | # 7. Use the body to explain what and why vs. how 258 | # ------------------------------------------------------------------------------ 259 | 260 | # ? 261 | 262 | # 8. Do no write single worded commits 263 | # ------------------------------------------------------------------------------ 264 | 265 | COMMIT_SUBJECT_WORDS=(${COMMIT_SUBJECT_TO_PROCESS}) 266 | test "${#COMMIT_SUBJECT_WORDS[@]}" -gt 1 267 | test $? -eq 0 || add_warning 1 "Do no write single worded commits" 268 | 269 | # 9. Do not start the subject line with whitespace 270 | # ------------------------------------------------------------------------------ 271 | 272 | [[ ${COMMIT_SUBJECT_TO_PROCESS} =~ ^[[:blank:]]+ ]] 273 | test $? -eq 1 || add_warning 1 "Do not start the subject line with whitespace" 274 | } 275 | 276 | # 277 | # It's showtime. 278 | # 279 | 280 | set_colors 281 | 282 | set_editor 283 | 284 | if tty >/dev/null 2>&1; then 285 | TTY=$(tty) 286 | else 287 | TTY=/dev/tty 288 | fi 289 | 290 | while true; do 291 | 292 | read_commit_message 293 | 294 | validate_commit_message 295 | 296 | # if there are no WARNINGS are empty then we're good to break out of here 297 | test ${#WARNINGS[@]} -eq 0 && exit 0; 298 | 299 | display_warnings 300 | 301 | # Ask the question (not using "read -p" as it uses stderr not stdout) 302 | echo -en "${CYAN}Proceed with commit? [e/n/?] ${NC}" 303 | 304 | # Read the answer 305 | read REPLY < "$TTY" 306 | 307 | # Check if the reply is valid 308 | case "$REPLY" in 309 | E*|e*) $HOOK_EDITOR "$COMMIT_MSG_FILE" < $TTY; continue ;; 310 | N*|n*) exit 1 ;; 311 | *) SKIP_DISPLAY_WARNINGS=1; prompt_help; continue ;; 312 | esac 313 | 314 | done 315 | -------------------------------------------------------------------------------- /scripts/install-git-hooks: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if ! test -d .git; then 4 | echo "Execute scripts/install-git-hooks in the top-level directory." 5 | exit 1 6 | fi 7 | 8 | ln -sf ../../scripts/pre-commit.hook .git/hooks/pre-commit || exit 1 9 | chmod +x .git/hooks/pre-commit 10 | 11 | ln -sf ../../scripts/commit-msg.hook .git/hooks/commit-msg || exit 1 12 | chmod +x .git/hooks/commit-msg 13 | 14 | ln -sf ../../scripts/pre-push.hook .git/hooks/pre-push || exit 1 15 | chmod +x .git/hooks/pre-push 16 | 17 | touch .git/hooks/applied || exit 1 18 | 19 | echo 20 | echo "Git hooks are installed successfully." 21 | -------------------------------------------------------------------------------- /scripts/pre-commit.hook: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CPPCHECK_suppress="--suppress=missingInclude \ 4 | --suppress=unusedFunction:src/threadtracer.c \ 5 | --suppress=redundantAssignment:src/skinny_mutex.c \ 6 | --suppress=unusedFunction:src/tasklet.c \ 7 | --suppress=unusedFunction:src/thread.c \ 8 | --suppress=unusedFunction:src/skinny_mutex.c \ 9 | --inline-suppr" 10 | CPPCHECK_OPTS="-I. --enable=all --error-exitcode=1 --force $CPPCHECK_suppress ." 11 | 12 | RETURN=0 13 | CLANG_FORMAT=$(which clang-format) 14 | if [ $? -ne 0 ]; then 15 | echo "[!] clang-format not installed. Unable to check source file format policy." >&2 16 | exit 1 17 | fi 18 | 19 | CPPCHECK=$(which cppcheck) 20 | if [ $? -ne 0 ]; then 21 | echo "[!] cppcheck not installed. Unable to perform static analysis." >&2 22 | exit 1 23 | fi 24 | 25 | ASPELL=$(which aspell) 26 | if [ $? -ne 0 ]; then 27 | echo "[!] aspell not installed. Unable to do spelling check." >&2 28 | exit 1 29 | fi 30 | 31 | DIFF=$(which colordiff) 32 | if [ $? -ne 0 ]; then 33 | DIFF=diff 34 | fi 35 | 36 | FILES=`git diff --cached --name-only --diff-filter=ACMR | grep -E "\.(c|cpp|h)$"` 37 | for FILE in $FILES; do 38 | nf=`git checkout-index --temp $FILE | cut -f 1` 39 | tempdir=`mktemp -d` || exit 1 40 | newfile=`mktemp ${tempdir}/${nf}.XXXXXX` || exit 1 41 | basename=`basename $FILE` 42 | 43 | source="${tempdir}/${basename}" 44 | mv $nf $source 45 | cp .clang-format $tempdir 46 | $CLANG_FORMAT $source > $newfile 2>> /dev/null 47 | $DIFF -u -p -B --label="modified $FILE" --label="expected coding style" \ 48 | "${source}" "${newfile}" 49 | r=$? 50 | rm -rf "${tempdir}" 51 | if [ $r != 0 ] ; then 52 | echo "[!] $FILE does not follow the consistent coding style." >&2 53 | RETURN=1 54 | fi 55 | if [ $RETURN -eq 1 ]; then 56 | echo "" >&2 57 | echo "Make sure you indent as the following:" >&2 58 | echo " clang-format -i $FILE" >&2 59 | echo 60 | fi 61 | done 62 | 63 | # Prevent unsafe functions 64 | root=$(git rev-parse --show-toplevel) 65 | banned="([^f]gets\()|(sprintf\()|(strcpy\()" 66 | status=0 67 | for file in $(git diff --staged --name-only | grep -E "\.(c|cc|cpp|h|hh|hpp)\$") 68 | do 69 | filepath="${root}/${file}" 70 | output=$(grep -nrE "${banned}" "${filepath}") 71 | if [ ! -z "${output}" ]; then 72 | echo "Dangerous function detected in ${filepath}" 73 | echo "${output}" 74 | echo 75 | echo "Read 'Common vulnerabilities guide for C programmers' carefully." 76 | echo " https://security.web.cern.ch/security/recommendations/en/codetools/c.shtml" 77 | RETURN=1 78 | fi 79 | done 80 | 81 | # static analysis 82 | $CPPCHECK $CPPCHECK_OPTS >/dev/null 83 | 84 | exit $RETURN 85 | -------------------------------------------------------------------------------- /scripts/pre-push.hook: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | protected_branch='master' 4 | current_branch=$(git symbolic-ref HEAD | sed -e 's,.*/\(.*\),\1,') 5 | RED='\033[0;31m' 6 | GREEN='\033[1;32m' 7 | YELLOW='\033[1;33m' 8 | NC='\033[0m' # No Color 9 | 10 | # Show hints 11 | echo -e "${YELLOW}Hint${NC}: You might want to know why Git is always ${GREEN}asking for my password${NC}." 12 | echo -e " https://help.github.com/en/github/using-git/why-is-git-always-asking-for-my-password" 13 | echo "" 14 | 15 | # only run this if you are pushing to master 16 | if [[ $current_branch = $protected_branch ]] ; then 17 | echo -e "${YELLOW}Running pre push to master check...${NC}" 18 | 19 | echo -e "${YELLOW}Trying to build tests project...${NC}" 20 | 21 | # build the project 22 | make 23 | 24 | # $? is a shell variable which stores the return code from what we just ran 25 | rc=$? 26 | if [[ $rc != 0 ]] ; then 27 | echo -e "${RED}Failed to build the project, please fix this and push again${NC}" 28 | echo "" 29 | exit $rc 30 | fi 31 | 32 | # Everything went OK so we can exit with a zero 33 | echo -e "${GREEN}Pre-push check passed!${NC}" 34 | echo "" 35 | fi 36 | 37 | exit 0 38 | -------------------------------------------------------------------------------- /src/skinny_mutex.c: -------------------------------------------------------------------------------- 1 | #ifndef _GNU_SOURCE 2 | #define _GNU_SOURCE 3 | #endif 4 | 5 | #include "skinny_mutex.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "logger.h" 14 | 15 | #define CAS(p, a, b) __sync_bool_compare_and_swap(p, a, b) 16 | 17 | /* Atomically exchange the value of a pointer in memory. 18 | * 19 | * This is absent from GCC's builtin atomics, but we can simulate it with CAS. 20 | */ 21 | static inline void *atomic_xchg(void **ptr, void *new) 22 | { 23 | void *old; 24 | do { 25 | old = *ptr; 26 | } while (!CAS(ptr, old, new)); 27 | return old; 28 | } 29 | 30 | /* Atomically subtract from a byte in memory, and test the subsequent value, 31 | * returning zero if it reached zero, and non-zero otherwise. 32 | */ 33 | static inline int atomic_sub_and_test(uint8_t *ptr, uint8_t x) 34 | { 35 | return __sync_sub_and_fetch(ptr, x); 36 | } 37 | 38 | /* The function says how to behave when we encounter an error while recovering 39 | * from another error. 40 | * 41 | * It is not clear what the right thing to do in general is. Here we assume 42 | * it is better to blow up than to discard an error code (which might lead to 43 | * blowing up later on anyway). 44 | */ 45 | static int recover(int res1, int res2) 46 | { 47 | if (res2 == 0) 48 | return res1; 49 | 50 | if (res1 == 0) 51 | return res2; 52 | 53 | log_err("got error %d while recovering from %d\n", res2, res1); 54 | abort(); 55 | } 56 | 57 | /* The common header for the fat_mutex and peg structs */ 58 | struct common { 59 | uint8_t peg; 60 | }; 61 | 62 | /* 63 | * A skinny_mutex_t contains a pointer-sized word. The non-contended cases 64 | * is simple: If the mutex is not held, it contains 0. If the mutex is held 65 | * but not contended, it contains 1. A compare-and-swap is used to acquire 66 | * an unheld skinny_mutex, or to release it when held. 67 | * 68 | * When a lock becomes contended - when a thread tries to lock a skinny_mutex 69 | * that is already held - we fall back to standard pthreads synchronization 70 | * primitives (so that the thread can block and be woken again when it has 71 | * a chance to acquire the lock). The fat_mutex struct holds all the state 72 | * necessary to handle contention cases (that is, a normal pthreads mutex 73 | * and condition variable, and a flag to indicate whether the skinny_mutex 74 | * is held or not). 75 | */ 76 | struct fat_mutex { 77 | struct common common; 78 | 79 | /* Is the lock held? */ 80 | bool held; 81 | 82 | /* How many threads are waiting to acquire the associated skinny_mutex. */ 83 | long waiters; 84 | 85 | /* References that prevent the fat_mutex being freed. This includes: 86 | * 87 | * - References from threads waiting to acquire the mutex. 88 | * 89 | * - References from pegs (see below) not on the primary chain (another 90 | * way of looking at it is that we do include the reference from the 91 | * primary chain, which could be the one from the skinny_mutex, but we 92 | * offset the refcount value by -1, so a refcount of 0 means we only 93 | * have the primary chain). 94 | * 95 | * - A pseudo-reference from the thread holding the skinny_mutex (this 96 | * might not correspond to an explicit reference, but keeps the fat_mutex 97 | * pinned while the mutex is held). 98 | * 99 | * - References from threads waiting on condition variables associated 100 | * with the skinny_mutex. 101 | */ 102 | long refcount; 103 | 104 | /* The pthreads mutex guarding the other fields. */ 105 | pthread_mutex_t mutex; 106 | 107 | /* Conv var signalled when the mutex is released and there are waiters */ 108 | pthread_cond_t cond; 109 | 110 | /* Transfer generation. */ 111 | long transfer_gen; 112 | long transfers; 113 | }; 114 | 115 | /* 116 | * If the skinny_mutex points to a fat_mutex, a thread cannot simply 117 | * obtain the pointer and dereference it, as another thread might free 118 | * the fat_mutex between those two points. There needs to be some way 119 | * for a thread to communicate its intent to access the fat_mutex. 120 | * 121 | * Many lock-free algoithms solve this problem using hazard pointers. 122 | * But hazard pointers require tracking the set of all threads 123 | * involved. Furthermore, for efficiency, hazard pointer 124 | * implementations batch deallocations, and process a batch using a 125 | * data structure that allows efficient comparison of a candidate 126 | * pointer with the set of hazard pointers. Implementing all this 127 | * involves a substantial amount of code. 128 | * 129 | * We use a simpler approach: Pegging. This approach has higher 130 | * per-access costs than hazard pointers, but we only access the 131 | * fat_mutex when other significant costs are involved (e.g. blocking 132 | * the thread on a pthreds mutex), so the cost of this part is likely 133 | * to be marginal. 134 | 135 | * A thread indicates its intent to access the fat_mutex by allocating 136 | * a peg struct and storing a pointer to it into the skinny_mutex, 137 | * replacing the pointer to the fat_mutex (see fat_mutex_peg). The 138 | * skinny_mutex is updated with CAS so that installing a peg is 139 | * atomic. A fat_mutex can only be freed if the skinny_mutex points 140 | * directly to it, so the presence of the peg prevents it being freed, 141 | * hence the name (see fat_mutex_release). 142 | * 143 | * The peg struct has a "next" pointer in it, pointing to the previous 144 | * value of the skinny_mutex. This might be a fat_mutex, but it an 145 | * also be another peg. So chains of pegs can be built up, starting 146 | * with the skinny_mutex, followed by zero or more pegs, and 147 | * terminating with the fat_mutex, e.g.: 148 | * 149 | * +--------------+ +--------+ +--------+ +-----------+ 150 | * | skinny_mutex | | peg | | peg | | fat_mutex | 151 | * +--------------+ +--------+ +--------| +-----------+ 152 | * | val *---------->| next *---->| next *---->| ... | 153 | * +--------------+ | ... | | ... | +-----------+ 154 | * +--------+ +--------+ 155 | * 156 | * During the process of releasing a peg (in the second half of 157 | * fat_mutex_peg), the skinny_mutex is set to point to the fat_mutex 158 | * again, possibly leaving chains which of pegs which do not originate 159 | * at the skinny_mutex (these are accounted for in the fat_mutex's 160 | * refcount, so the pegs on these chains still prevent the fat_mutex 161 | * being freed). We refer to the chain connecting the skinny_mutex to 162 | * the fat_mutex as the primary chain, and the others as secondary 163 | * chains, e.g.: 164 | * 165 | * +--------+ +--------+ 166 | * | peg | | peg | 167 | * Secondary chain: +--------+ +--------| 168 | * | next *---->| next *-------\ 169 | * | ... | | ... | \ 170 | * +--------+ +--------+ | 171 | * | 172 | * Primary chain: v 173 | * +--------------+ +--------+ +--------+ +-----------+ 174 | * | skinny_mutex | | peg | | peg | | fat_mutex | 175 | * +--------------+ +--------+ +--------| +-----------+ 176 | * | val *---------->| next *---->| next *---->| ... | 177 | * +--------------+ | ... | | ... | +-----------+ 178 | * +--------+ +--------+ ^ 179 | * | 180 | * +--------+ | 181 | * | peg | | 182 | * +--------| / 183 | * Secondary chain: | next *-------/ 184 | * | ... | 185 | * +--------+ 186 | */ 187 | struct peg { 188 | struct common common; 189 | 190 | /* The refcount on this peg. The peg can be freed when this falls to 0. 191 | * This never exceeds 2, so we only need a byte. 192 | */ 193 | uint8_t refcount; 194 | 195 | /* The next peg in the chain, or the fat_mutex at the end of the chain. */ 196 | struct common *next; 197 | }; 198 | 199 | 200 | /* Given a skinny_mutex containing a pointer, find the associated 201 | * fat_mutex and lock its mutex. 202 | * 203 | * "skinny" points to the skinny_mutex. 204 | * 205 | * "p" is the pointer previously obtained from the skinny_mutex. 206 | * 207 | * "fatp" is used to return the pointer to the locked fat_mutex. 208 | * 209 | * Returns 0 on success, a positive error code, or <0 if the 210 | * skinny_mutex was found to no longer contain a pointer. 211 | */ 212 | static int fat_mutex_peg(skinny_mutex_t *skinny, 213 | struct common *p, 214 | struct fat_mutex **fatp) 215 | { 216 | int res; 217 | volatile unsigned int peg_refcount_decr; 218 | struct fat_mutex *fat; 219 | struct peg *peg = malloc(sizeof *peg); 220 | if (!peg) 221 | return ENOMEM; 222 | 223 | /* Install our peg. The initial ref count is two: One for the 224 | * reference from this thread, and one that will be from the 225 | * skinny_mutex. 226 | */ 227 | peg->common.peg = 1; 228 | peg->refcount = 2; 229 | peg->next = p; 230 | 231 | while (!CAS(&skinny->val, p, peg)) { 232 | /* value in the skinny_mutex has changed from what we saw earlier. */ 233 | 234 | p = skinny->val; 235 | if ((uintptr_t) p <= 1) { 236 | /* There is no longer a fat_mutex to peg, so backtrack. */ 237 | free(peg); 238 | return -1; 239 | } 240 | 241 | /* There is a new fat_mutex, so try again to install our peg. */ 242 | peg->next = p; 243 | } 244 | 245 | /* Our peg is now installed. Now we know the rest of the 246 | * chain won't disappear under us, so we can walk it to find 247 | * the fat_mutex and lock it. */ 248 | while (p->peg) 249 | p = ((struct peg *) p)->next; 250 | 251 | *fatp = fat = (struct fat_mutex *) p; 252 | res = pthread_mutex_lock(&fat->mutex); 253 | 254 | /* The fat_mutex is locked, and we know it won't go away while we hold 255 | * its lock. So we can release our peg. 256 | * 257 | * To do this, we set the skinny_mutex to point to the fat_mutex, turning 258 | * the primary chain into a secondary chain. Note that we don't know 259 | * whether this thread's peg is still on the primary chain when we do 260 | * this. Handling the various cases correctly hinges on the refcounts. 261 | * By the end of this function, the fat_mutex refcount can be incremented, 262 | * decremented, or returned to its original value. 263 | */ 264 | p = atomic_xchg(&skinny->val, fat); 265 | 266 | /* By setting the skinny_mutex to point to the fat_mutex, we have 267 | * heoretically created a new reference to it. This might be a real 268 | * reference (e.g. from a new secondary chain) or not. If not, we will 269 | * decrement the fat_mutex refcount below. 270 | */ 271 | fat->refcount++; 272 | 273 | /* In this loop, walk peg chain starting with old value of skinny_mutex. */ 274 | for (;;) { 275 | struct peg *chain_peg; 276 | 277 | peg_refcount_decr = 2; 278 | if (p == &peg->common) 279 | /* We have reached our peg, so fall through to the loop below. */ 280 | break; 281 | 282 | peg_refcount_decr = 1; 283 | if (p == &fat->common) { 284 | /* We have reached the fat_mutex at the end of the chain, 285 | * eliminating a reference to it. 286 | */ 287 | fat->refcount--; 288 | break; 289 | } 290 | 291 | /* Decrement refcount of peg, and see whether we can free it yet. */ 292 | chain_peg = (struct peg *) p; 293 | if (atomic_sub_and_test(&chain_peg->refcount, 1)) 294 | /* We can't free this peg yet, so leave a 295 | * secondary chain in place. */ 296 | break; 297 | 298 | /* Free the peg, and proceed to the next peg in the chain. */ 299 | p = chain_peg->next; 300 | free(chain_peg); 301 | } 302 | 303 | for (;;) { 304 | if (atomic_sub_and_test(&peg->refcount, peg_refcount_decr)) 305 | /* We can not free peg yet, so leave a secondary chain in place. */ 306 | break; 307 | 308 | /* No references to the peg remain, so free it. */ 309 | p = peg->next; 310 | free(peg); 311 | 312 | if (p == &fat->common) { 313 | /* We have reached the fat_mutex at the end of the chain, 314 | * eliminating a reference to it. 315 | */ 316 | fat->refcount--; 317 | break; 318 | } 319 | 320 | /* Proceed to the next peg in the chain. */ 321 | peg = (struct peg *) p; 322 | peg_refcount_decr = 1; 323 | } 324 | 325 | return res; 326 | } 327 | 328 | /* Allocate a fat_mutex and associate it with a skinny_mutex. 329 | * 330 | * "skinny" points to the skinny_mutex. 331 | * 332 | * "head" is the value previously obtained from the skinny_mutex. 333 | * 334 | * "fatp" is used to return the pointer to the locked fat_mutex. 335 | * 336 | * Returns 0 on success, a positive error code, or <0 if the 337 | * skinny_mutex was found to no longer contain "head". 338 | */ 339 | static int skinny_mutex_promote(skinny_mutex_t *skinny, 340 | void *head, 341 | struct fat_mutex **fatp) 342 | { 343 | int res = ENOMEM; 344 | struct fat_mutex *fat = malloc(sizeof *fat); 345 | *fatp = fat; 346 | if (!fat) 347 | goto err; 348 | 349 | fat->common.peg = 0; 350 | fat->held = !!head; 351 | /* If the skinny_mutex is held, then refcount needs to account for the 352 | * pseudo-reference from the holding thread. 353 | */ 354 | fat->refcount = fat->held; 355 | fat->waiters = 0; 356 | fat->transfer_gen = 0; 357 | fat->transfers = 0; 358 | 359 | res = pthread_mutex_init(&fat->mutex, NULL); 360 | if (res) 361 | goto err_mutex_init; 362 | 363 | res = pthread_cond_init(&fat->cond, NULL); 364 | if (res) 365 | goto err_cond_init; 366 | 367 | res = pthread_mutex_lock(&fat->mutex); 368 | if (res) 369 | goto err_mutex_lock; 370 | 371 | /* fat_mutex is now ready, so try to make the skinny_mutex point to it. */ 372 | if (CAS(&skinny->val, head, fat)) 373 | return 0; 374 | 375 | res = -1; 376 | pthread_mutex_unlock(&fat->mutex); 377 | err_mutex_lock: 378 | pthread_cond_destroy(&fat->cond); 379 | err_cond_init: 380 | pthread_mutex_destroy(&fat->mutex); 381 | err_mutex_init: 382 | free(fat); 383 | err: 384 | return res; 385 | } 386 | 387 | /* Get and lock the fat_mutex associated with a skinny_mutex, 388 | * allocating it if necessary. 389 | * 390 | * "skinny" points to the skinny_mutex. 391 | * 392 | * "head" is the value that previously seen in the skinny_mutex. 393 | * 394 | * "fatp" is used to return the pointer to the locked fat_mutex. 395 | * 396 | * Returns 0 on success, a positive error code, or <0 if the 397 | * skinny_mutex value changed so that the operation should be retried. 398 | */ 399 | static int fat_mutex_get(skinny_mutex_t *skinny, 400 | struct common *head, 401 | struct fat_mutex **fatp) 402 | { 403 | if ((uintptr_t) head <= 1) 404 | return skinny_mutex_promote(skinny, head, fatp); 405 | else 406 | return fat_mutex_peg(skinny, head, fatp); 407 | } 408 | 409 | /* Decrement the refcount on a fat_mutex, unlock it, and free it 410 | * if the conditions are right. 411 | */ 412 | static int fat_mutex_release(skinny_mutex_t *skinny, struct fat_mutex *fat) 413 | { 414 | int keep, res; 415 | 416 | /* If the decremented refcount reaches zero, then we know there are no 417 | * secondary peg chains or other threads pinning the fat_mutex. And if 418 | * the skinny_mutex points to the fat_mutex, then we know that there are 419 | * no pegs on the primary chain either. So if the CAS succeeds in nulling 420 | * out the skinny_mutex, we can free the fat_mutex. 421 | */ 422 | keep = (--fat->refcount || !CAS(&skinny->val, fat, NULL)); 423 | 424 | res = pthread_mutex_unlock(&fat->mutex); 425 | if (keep || res) 426 | return res; 427 | 428 | res = pthread_mutex_destroy(&fat->mutex); 429 | if (res) 430 | return res; 431 | 432 | res = pthread_cond_destroy(&fat->cond); 433 | if (res) 434 | return res; 435 | 436 | free(fat); 437 | return 0; 438 | } 439 | 440 | /* Try to acquire a skinny_mutex with an associated fat_mutex. 441 | * 442 | * The fat_mutex's mutex will be released, so the calling thread 443 | * should already be accounted for in the fat_mutex's refcount. 444 | */ 445 | static int fat_mutex_lock(skinny_mutex_t *skinny, struct fat_mutex *fat) 446 | { 447 | if (fat->held) { 448 | /* The mutex is already held, so we have to wait for it. */ 449 | fat->waiters++; 450 | 451 | do { 452 | int res, old_state, old_state2; 453 | 454 | /* skinny_mutex_lock is not a cancellation 455 | point, but pthread_cond_wait is, so we need 456 | to defer cancellation around it. */ 457 | assert(!pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state)); 458 | res = pthread_cond_wait(&fat->cond, &fat->mutex); 459 | assert(!pthread_setcancelstate(old_state, &old_state2)); 460 | 461 | if (res) { 462 | fat->waiters--; 463 | return recover(res, fat_mutex_release(skinny, fat)); 464 | } 465 | } while (fat->held); 466 | 467 | fat->waiters--; 468 | } 469 | 470 | fat->held = true; 471 | return pthread_mutex_unlock(&fat->mutex); 472 | } 473 | 474 | /* Called from skinny_mutex_lock when the fast path fails. */ 475 | int skinny_mutex_lock_slow(skinny_mutex_t *skinny) 476 | { 477 | for (;;) { 478 | struct common *head = skinny->val; 479 | if (head) { 480 | struct fat_mutex *fat; 481 | int res = fat_mutex_get(skinny, head, &fat); 482 | if (!res) { 483 | fat->refcount++; 484 | res = fat_mutex_lock(skinny, fat); 485 | } 486 | 487 | if (res >= 0) 488 | return res; 489 | 490 | /* skinny_mutex value changed under us, try again. */ 491 | } else { 492 | /* Recapitulate skinny_mutex_lock */ 493 | if (CAS(&skinny->val, head, (void *) 1)) 494 | return 0; 495 | } 496 | } 497 | } 498 | 499 | int skinny_mutex_trylock(skinny_mutex_t *skinny) 500 | { 501 | for (;;) { 502 | struct common *head = skinny->val; 503 | struct fat_mutex *fat; 504 | int res; 505 | 506 | switch ((uintptr_t) head) { 507 | case 0: 508 | if (CAS(&skinny->val, head, (void *) 1)) 509 | return 0; 510 | 511 | break; 512 | 513 | case 1: 514 | return EBUSY; 515 | 516 | default: 517 | res = fat_mutex_peg(skinny, head, &fat); 518 | if (res > 0) 519 | return res; 520 | else if (res < 0) 521 | /* skinny_mutex value changed under us, try again. */ 522 | break; 523 | 524 | res = EBUSY; 525 | if (!fat->held) { 526 | fat->held = true; 527 | fat->refcount++; 528 | res = 0; 529 | } 530 | 531 | return recover(res, pthread_mutex_unlock(&fat->mutex)); 532 | } 533 | } 534 | } 535 | 536 | /* Get and lock the fat_mutex associated with a skinny_mutex, when this thread 537 | * is expected to already hold the mutex. 538 | */ 539 | static int fat_mutex_get_held(skinny_mutex_t *skinny, struct fat_mutex **fatp) 540 | { 541 | for (;;) { 542 | int res; 543 | struct common *head = skinny->val; 544 | if (!head) 545 | return EPERM; 546 | 547 | res = fat_mutex_get(skinny, head, fatp); 548 | if (res == 0) { 549 | if ((*fatp)->held) 550 | return 0; 551 | 552 | res = pthread_mutex_unlock(&(*fatp)->mutex); 553 | if (res) 554 | return res; 555 | 556 | return EPERM; 557 | } 558 | 559 | if (res >= 0) 560 | return res; 561 | } 562 | } 563 | 564 | /* Called from skinny_mutex_unlock when the fast path fails. */ 565 | int skinny_mutex_unlock_slow(skinny_mutex_t *skinny) 566 | { 567 | struct fat_mutex *fat; 568 | int res = fat_mutex_get_held(skinny, &fat); 569 | 570 | if (res) 571 | return res; 572 | 573 | fat->held = false; 574 | res = 0; 575 | if (fat->waiters) 576 | /* Wake a single waiter. */ 577 | res = pthread_cond_signal(&fat->cond); 578 | 579 | return recover(res, fat_mutex_release(skinny, fat)); 580 | } 581 | 582 | struct cond_wait_cleanup { 583 | skinny_mutex_t *skinny; 584 | struct fat_mutex *fat; 585 | int lock_res; 586 | }; 587 | 588 | /* Thread cancallation cleanup handler when waiting for the condition variable 589 | * below. 590 | */ 591 | static void cond_wait_cleanup(void *v_c) 592 | { 593 | struct cond_wait_cleanup *c = v_c; 594 | 595 | /* Cancellation of pthread_cond_wait should re-acquire the mutex. */ 596 | c->lock_res = fat_mutex_lock(c->skinny, c->fat); 597 | } 598 | 599 | int skinny_mutex_cond_timedwait(pthread_cond_t *cond, 600 | skinny_mutex_t *skinny, 601 | const struct timespec *abstime) 602 | { 603 | struct cond_wait_cleanup c; 604 | int res = fat_mutex_get_held(skinny, &c.fat); 605 | if (res) 606 | return res; 607 | 608 | /* We will release the lock, so wake a waiter */ 609 | if (c.fat->waiters) { 610 | res = pthread_cond_signal(&c.fat->cond); 611 | if (res) { 612 | pthread_mutex_unlock(&c.fat->mutex); 613 | return res; 614 | } 615 | } 616 | 617 | /* Relinquish the mutex. But we leave our reference accounted for in 618 | * fat->refcount in place, in order to pin the fat_mutex. 619 | */ 620 | c.fat->held = false; 621 | 622 | /* pthread_cond_wait is a cancellation point */ 623 | pthread_cleanup_push(cond_wait_cleanup, &c); 624 | 625 | if (!abstime) 626 | res = pthread_cond_wait(cond, &c.fat->mutex); 627 | else 628 | res = pthread_cond_timedwait(cond, &c.fat->mutex, abstime); 629 | 630 | pthread_cleanup_pop(1); 631 | return recover(res, c.lock_res); 632 | } 633 | 634 | int skinny_mutex_cond_wait(pthread_cond_t *cond, skinny_mutex_t *skinny) 635 | { 636 | return skinny_mutex_cond_timedwait(cond, skinny, NULL); 637 | } 638 | 639 | int skinny_mutex_transfer(skinny_mutex_t *a, skinny_mutex_t *b) 640 | { 641 | struct fat_mutex *fat_b; 642 | int res; 643 | long transfer_gen; 644 | 645 | for (;;) { 646 | struct common *b_head = b->val; 647 | 648 | if (!b_head) { 649 | /* b is neither held nor contended, the simple case. */ 650 | if (!CAS(&b->val, b_head, (void *) 1)) 651 | /* skinny mutex value changed under us, try 652 | again. */ 653 | continue; 654 | 655 | res = skinny_mutex_unlock(a); 656 | if (res) 657 | /* if we fail to unlock a, we need to unlock b to recover to 658 | * the original state. 659 | */ 660 | return recover(res, skinny_mutex_unlock(b)); 661 | 662 | /* All done. That was easy. */ 663 | return 0; 664 | } 665 | 666 | /* b is held or contended, we might have work to do. */ 667 | res = fat_mutex_get(b, b_head, &fat_b); 668 | if (!res) 669 | break; 670 | 671 | if (res >= 0) 672 | return res; 673 | } 674 | 675 | fat_b->refcount++; 676 | transfer_gen = fat_b->transfer_gen; 677 | 678 | /* We are going to wait to acquire b, so we need to unlock a. 679 | * Try the easy way first. 680 | */ 681 | if (!CAS(&a->val, (void *) 1, (void *) 0)) { 682 | /* We can't acquire a's fat lock while holding b's fat lock, because 683 | * that would risk deadlock. So we have to drop b first. We have 684 | * bumped the refcount, so it won't go away. 685 | */ 686 | pthread_mutex_unlock(&fat_b->mutex); 687 | res = skinny_mutex_unlock_slow(a); 688 | pthread_mutex_lock(&fat_b->mutex); 689 | if (res) 690 | return recover(res, fat_mutex_release(b, fat_b)); 691 | } 692 | 693 | fat_b->transfers++; 694 | fat_b->waiters++; 695 | 696 | for (;;) { 697 | int old_state, old_state2; 698 | 699 | if (!fat_b->held) { 700 | /* We can acquire the lock */ 701 | fat_b->transfers--; 702 | fat_b->waiters--; 703 | fat_b->held = true; 704 | return pthread_mutex_unlock(&fat_b->mutex); 705 | } 706 | 707 | if (fat_b->transfer_gen != transfer_gen) { 708 | /* There was a veto_transfer */ 709 | res = EAGAIN; 710 | break; 711 | } 712 | 713 | /* Not a cancellation point, but pthread_cond_wait is, so we need to 714 | * defer cancellation around it. 715 | */ 716 | assert(!pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state)); 717 | res = pthread_cond_wait(&fat_b->cond, &fat_b->mutex); 718 | assert(!pthread_setcancelstate(old_state, &old_state2)); 719 | 720 | if (res) 721 | break; 722 | } 723 | 724 | fat_b->transfers--; 725 | fat_b->waiters--; 726 | res = recover(res, fat_mutex_release(b, fat_b)); 727 | return recover(res, skinny_mutex_lock(a)); 728 | } 729 | 730 | int skinny_mutex_veto_transfer(skinny_mutex_t *skinny) 731 | { 732 | int res; 733 | struct fat_mutex *fat; 734 | 735 | for (;;) { 736 | struct common *head = skinny->val; 737 | if (head == (void *) 1) 738 | /* Mutex held, but no fat mutex, so there can't be any waiting 739 | * transfers. 740 | */ 741 | return 0; 742 | 743 | if (head == (void *) 0) 744 | /* Mutex not held */ 745 | return EPERM; 746 | 747 | res = fat_mutex_peg(skinny, head, &fat); 748 | if (res == 0) 749 | break; 750 | 751 | if (res > 0) 752 | return res; 753 | 754 | /* skinny mutex value changed under us, try again. */ 755 | } 756 | 757 | res = EPERM; 758 | 759 | if (fat->held) { 760 | /* notify any waiting transfers */ 761 | res = 0; 762 | fat->transfer_gen++; 763 | if (fat->transfers) 764 | res = pthread_cond_broadcast(&fat->cond); 765 | } 766 | 767 | return recover(res, pthread_mutex_unlock(&fat->mutex)); 768 | } 769 | -------------------------------------------------------------------------------- /src/tasklet.c: -------------------------------------------------------------------------------- 1 | #include "tasklet.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define pointer_bits(p) ((uintptr_t)(p) &3) 9 | #define pointer_clear_bits(p) ((void *) ((uintptr_t)(p) & -4)) 10 | #define pointer_set_bits(p, bits) ((void *) ((uintptr_t)(p) | (bits))) 11 | 12 | void tasklet_init(struct tasklet *tasklet, struct mutex *mutex, void *data) 13 | { 14 | tasklet->mutex = mutex; 15 | tasklet->handler = NULL; 16 | tasklet->data = data; 17 | mutex_init(&tasklet->wait_mutex); 18 | tasklet->wait = NULL; 19 | tasklet->unwaiting = 0; 20 | tasklet->runq = NULL; 21 | } 22 | 23 | struct run_queue { 24 | /* Linked list of all run queues. */ 25 | struct run_queue *next; 26 | 27 | struct mutex mutex; 28 | struct tasklet *head; 29 | struct tasklet *current; 30 | enum { CURRENT_STARTED, CURRENT_STOPPED, CURRENT_REQUEUE } current_state; 31 | 32 | bool stop_waiting; 33 | bool worker_waiting; 34 | thread_handle_t thread; 35 | struct cond cond; 36 | }; 37 | 38 | /* Threads can hold a run_queue reference without holding any locks. 39 | So run_queues cannot simply be freed. At some stage it might be 40 | worth introducing RCU-like grace periods to determine when 41 | run_queues can be freed. But for now, we don't expect many of them 42 | to get allocated, so we simply clean them up on exit to keep 43 | valgrind happy. */ 44 | 45 | static struct run_queue *run_queues; 46 | 47 | static void run_queue_destroy(struct run_queue *runq) 48 | { 49 | assert(!runq->head); 50 | assert(!runq->current); 51 | mutex_fini(&runq->mutex); 52 | cond_fini(&runq->cond); 53 | free(runq); 54 | } 55 | 56 | static void cleanup_run_queues(void) 57 | { 58 | struct run_queue *runq = run_queues; 59 | 60 | while (runq) { 61 | struct run_queue *next = runq->next; 62 | run_queue_destroy(runq); 63 | runq = next; 64 | } 65 | } 66 | 67 | static struct run_queue *run_queue_create_unlinked(void) 68 | { 69 | struct run_queue *runq = malloc(sizeof *runq); 70 | 71 | mutex_init(&runq->mutex); 72 | runq->head = runq->current = NULL; 73 | runq->stop_waiting = false; 74 | runq->worker_waiting = false; 75 | cond_init(&runq->cond); 76 | 77 | return runq; 78 | } 79 | 80 | static void add_to_run_queues(struct run_queue *runq) 81 | { 82 | do { 83 | runq->next = run_queues; 84 | } while (!__sync_bool_compare_and_swap(&run_queues, runq->next, runq)); 85 | 86 | if (!runq->next) 87 | atexit(cleanup_run_queues); 88 | } 89 | 90 | struct run_queue *run_queue_create(void) 91 | { 92 | struct run_queue *runq = run_queue_create_unlinked(); 93 | add_to_run_queues(runq); 94 | return runq; 95 | } 96 | 97 | /* A dedicated run_queue worker thread */ 98 | struct worker { 99 | struct run_queue *runq; 100 | struct thread thread; 101 | 102 | /* Only used when we are stopping the worker */ 103 | struct mutex mutex; 104 | struct tasklet tasklet; 105 | }; 106 | 107 | static void worker_thread(void *v_worker); 108 | 109 | static struct worker *worker_create(struct run_queue *runq) 110 | { 111 | struct worker *w = malloc(sizeof *w); 112 | w->runq = runq; 113 | thread_init(&w->thread, worker_thread, w); 114 | return w; 115 | } 116 | 117 | static void stop_worker(void *v_worker) 118 | { 119 | struct worker *w = v_worker; 120 | w->runq = NULL; 121 | tasklet_stop(&w->tasklet); 122 | } 123 | 124 | static void worker_destroy(struct worker *w) 125 | { 126 | mutex_init(&w->mutex); 127 | tasklet_init(&w->tasklet, &w->mutex, w); 128 | tasklet_later(&w->tasklet, stop_worker); 129 | thread_fini(&w->thread); 130 | mutex_lock(&w->mutex); 131 | tasklet_fini(&w->tasklet); 132 | mutex_unlock_fini(&w->mutex); 133 | free(w); 134 | } 135 | 136 | static struct run_queue *default_run_queue; 137 | static struct worker *default_worker; 138 | 139 | static void cleanup_default_worker(void) 140 | { 141 | worker_destroy(default_worker); 142 | } 143 | 144 | TLS_VAR_DECLARE_STATIC(tls_run_queue); 145 | 146 | void run_queue_target(struct run_queue *runq) 147 | { 148 | TLS_VAR_SET(tls_run_queue, runq); 149 | } 150 | 151 | static struct run_queue *thread_run_queue(void) 152 | { 153 | struct run_queue *runq = TLS_VAR_GET(tls_run_queue); 154 | if (runq) 155 | return runq; 156 | 157 | for (;;) { 158 | runq = default_run_queue; 159 | if (runq) 160 | return runq; 161 | 162 | runq = run_queue_create_unlinked(); 163 | if (__sync_bool_compare_and_swap(&default_run_queue, NULL, runq)) 164 | break; 165 | 166 | run_queue_destroy(runq); 167 | } 168 | 169 | /* The order is important here - we want cleanup_run_queues to 170 | come after cleanup_default_worker. */ 171 | add_to_run_queues(runq); 172 | 173 | default_worker = worker_create(runq); 174 | atexit(cleanup_default_worker); 175 | 176 | return runq; 177 | } 178 | 179 | static void run_queue_enqueue(struct run_queue *runq, struct tasklet *t) 180 | { 181 | struct tasklet *head; 182 | 183 | mutex_assert_held(&runq->mutex); 184 | assert(t->runq == runq); 185 | 186 | head = runq->head; 187 | if (!head) { 188 | runq->head = t->runq_next = t->runq_prev = t; 189 | 190 | if (runq->worker_waiting) 191 | cond_signal(&runq->cond); 192 | } else { 193 | struct tasklet *prev = head->runq_prev; 194 | t->runq_next = head; 195 | t->runq_prev = prev; 196 | prev->runq_next = head->runq_prev = t; 197 | } 198 | } 199 | 200 | static void run_queue_remove(struct run_queue *runq, struct tasklet *t) 201 | { 202 | struct tasklet *next, *prev; 203 | 204 | mutex_assert_held(&runq->mutex); 205 | assert(t->runq == runq); 206 | 207 | next = t->runq_next; 208 | prev = t->runq_prev; 209 | next->runq_prev = prev; 210 | prev->runq_next = next; 211 | 212 | if (runq->head == t) 213 | runq->head = (next == t ? NULL : next); 214 | } 215 | 216 | /* The tasklet lock does not need to be held for this. */ 217 | void tasklet_run(struct tasklet *t) 218 | { 219 | bool done = false; 220 | 221 | do { 222 | struct run_queue *runq = t->runq; 223 | if (!runq) { 224 | runq = thread_run_queue(); 225 | mutex_lock(&runq->mutex); 226 | 227 | if (__sync_bool_compare_and_swap(&t->runq, NULL, runq)) { 228 | run_queue_enqueue(runq, t); 229 | done = true; 230 | } 231 | } else { 232 | mutex_lock(&runq->mutex); 233 | 234 | if (t->runq == runq) { 235 | if (runq->current == t) 236 | runq->current_state = CURRENT_REQUEUE; 237 | 238 | done = true; 239 | } 240 | } 241 | 242 | mutex_unlock(&runq->mutex); 243 | } while (!done); 244 | } 245 | 246 | 247 | void wait_list_init(struct wait_list *w, int up_count) 248 | { 249 | mutex_init(&w->mutex); 250 | w->head = NULL; 251 | w->unwaiting = 0; 252 | w->up_count = up_count; 253 | } 254 | 255 | void wait_list_fini(struct wait_list *w) 256 | { 257 | struct tasklet *head; 258 | 259 | mutex_lock(&w->mutex); 260 | 261 | head = w->head; 262 | if (head) { 263 | struct tasklet *t = head; 264 | 265 | /* Remove all tasklets from the linked list */ 266 | do { 267 | struct tasklet *next = t->wait_next; 268 | 269 | tasklet_run(t); 270 | 271 | mutex_lock(&t->wait_mutex); 272 | w->unwaiting += t->unwaiting; 273 | t->wait = NULL; 274 | t->unwaiting = 0; 275 | mutex_unlock(&t->wait_mutex); 276 | 277 | t = next; 278 | } while (t != head); 279 | 280 | /* If other threads are waiting on the wait_list mutex 281 | to remove themselves, allow them to proceed and 282 | wait until they are done. */ 283 | if (w->unwaiting) { 284 | struct cond cond; 285 | 286 | cond_init(&cond); 287 | w->head = pointer_set_bits(&cond, 1); 288 | 289 | do { 290 | cond_wait(&cond, &w->mutex); 291 | } while (w->unwaiting); 292 | 293 | cond_fini(&cond); 294 | } 295 | 296 | w->head = NULL; 297 | } 298 | 299 | mutex_unlock_fini(&w->mutex); 300 | } 301 | 302 | static void tasklet_unwait(struct tasklet *t) 303 | { 304 | struct wait_list *w; 305 | struct tasklet *next; 306 | 307 | mutex_lock(&t->wait_mutex); 308 | 309 | for (;;) { 310 | w = t->wait; 311 | if (!w) { 312 | /* Tasklet is not on a wait_list */ 313 | mutex_unlock(&t->wait_mutex); 314 | return; 315 | } 316 | 317 | t->unwaiting++; 318 | mutex_unlock(&t->wait_mutex); 319 | mutex_lock(&w->mutex); 320 | mutex_lock(&t->wait_mutex); 321 | 322 | /* The tasklet could have been removed from the 323 | waitlist, or even be on a different waitlist by 324 | now. If so, we need to start again. */ 325 | if (t->wait == w) 326 | break; 327 | 328 | if (!--w->unwaiting && pointer_bits(w->head)) { 329 | /* Dropping the last reference to the 330 | wait_list, so wake up the wait_list_fini 331 | caller. */ 332 | struct cond *cond = pointer_clear_bits(w->head); 333 | cond_signal(cond); 334 | } 335 | 336 | mutex_unlock(&w->mutex); 337 | } 338 | 339 | /* Remove t from the waitlist */ 340 | t->wait = NULL; 341 | 342 | /* Other threads may be accounted for in t->unwaiting. 343 | We need to record them in the wait_list. */ 344 | w->unwaiting += t->unwaiting - 1; 345 | t->unwaiting = 0; 346 | 347 | next = t->wait_next; 348 | t->wait_prev->wait_next = next; 349 | next->wait_prev = t->wait_prev; 350 | 351 | if (w->head == t) { 352 | if (next == t) { 353 | w->head = NULL; 354 | } else { 355 | w->head = next; 356 | if (w->up_count) 357 | tasklet_run(next); 358 | } 359 | } 360 | 361 | mutex_unlock(&t->wait_mutex); 362 | mutex_unlock(&w->mutex); 363 | } 364 | 365 | static void wait_list_broadcast_locked(struct wait_list *w) 366 | { 367 | struct tasklet *head; 368 | 369 | mutex_assert_held(&w->mutex); 370 | 371 | head = w->head; 372 | if (head) { 373 | struct tasklet *t = head; 374 | do { 375 | tasklet_run(t); 376 | t = t->wait_next; 377 | } while (t != head); 378 | } 379 | } 380 | 381 | void wait_list_broadcast(struct wait_list *w) 382 | { 383 | mutex_lock(&w->mutex); 384 | wait_list_broadcast_locked(w); 385 | mutex_unlock(&w->mutex); 386 | } 387 | 388 | void wait_list_set(struct wait_list *w, int n, bool broadcast) 389 | { 390 | mutex_lock(&w->mutex); 391 | w->up_count = n; 392 | if (broadcast) 393 | wait_list_broadcast_locked(w); 394 | mutex_unlock(&w->mutex); 395 | } 396 | 397 | static void wait_list_add(struct wait_list *w, struct tasklet *t) 398 | { 399 | t->wait = w; 400 | 401 | if (!w->head) { 402 | w->head = t->wait_next = t->wait_prev = t; 403 | if (w->up_count) 404 | tasklet_run(t); 405 | } else { 406 | struct tasklet *head = w->head; 407 | struct tasklet *prev = head->wait_prev; 408 | t->wait_next = head; 409 | t->wait_prev = prev; 410 | head->wait_prev = prev->wait_next = t; 411 | } 412 | } 413 | 414 | void wait_list_wait(struct wait_list *w, struct tasklet *t) 415 | { 416 | for (;;) { 417 | int done = false; 418 | 419 | mutex_lock(&w->mutex); 420 | mutex_lock(&t->wait_mutex); 421 | 422 | if (!t->wait) { 423 | wait_list_add(w, t); 424 | done = true; 425 | } else if (t->wait == w) { 426 | done = true; 427 | } 428 | 429 | mutex_unlock(&t->wait_mutex); 430 | mutex_unlock(&w->mutex); 431 | 432 | if (done) 433 | break; 434 | 435 | tasklet_unwait(t); 436 | } 437 | 438 | t->waited = true; 439 | } 440 | 441 | bool wait_list_down(struct wait_list *w, int n, struct tasklet *t) 442 | { 443 | int res; 444 | 445 | for (;;) { 446 | int done = false; 447 | 448 | mutex_lock(&w->mutex); 449 | mutex_lock(&t->wait_mutex); 450 | 451 | if (!t->wait || t->wait == w) { 452 | if (w->up_count >= n) { 453 | w->up_count -= n; 454 | res = true; 455 | } else { 456 | if (t->wait != w) 457 | wait_list_add(w, t); 458 | 459 | t->waited = true; 460 | res = false; 461 | } 462 | 463 | done = true; 464 | } 465 | 466 | mutex_unlock(&t->wait_mutex); 467 | mutex_unlock(&w->mutex); 468 | 469 | if (done) 470 | break; 471 | 472 | tasklet_unwait(t); 473 | } 474 | 475 | return res; 476 | } 477 | 478 | void wait_list_up(struct wait_list *w, int n) 479 | { 480 | mutex_lock(&w->mutex); 481 | 482 | w->up_count += n; 483 | if (w->head) 484 | tasklet_run(w->head); 485 | 486 | mutex_unlock(&w->mutex); 487 | } 488 | 489 | bool wait_list_nonempty(struct wait_list *w) 490 | { 491 | return !!w->head; 492 | } 493 | 494 | void run_queue_run(struct run_queue *runq, int wait) 495 | { 496 | struct tasklet *t; 497 | 498 | mutex_lock(&runq->mutex); 499 | 500 | t = runq->head; 501 | if (!t) { 502 | if (!wait) 503 | goto out; 504 | 505 | runq->worker_waiting = true; 506 | do { 507 | cond_wait(&runq->cond, &runq->mutex); 508 | t = runq->head; 509 | } while (!t); 510 | runq->worker_waiting = false; 511 | } 512 | 513 | runq->thread = thread_handle_current(); 514 | 515 | do { 516 | run_queue_remove(runq, t); 517 | runq->current = t; 518 | t->waited = false; 519 | 520 | for (;;) { 521 | runq->current_state = CURRENT_STARTED; 522 | if (mutex_transfer(&runq->mutex, t->mutex)) 523 | break; 524 | 525 | /* mutex_transfer can fail because of a veto 526 | aimed at a tasket on another run queue but 527 | using the same mutex. So we have to check 528 | that the current tasklet was really 529 | stopped. */ 530 | if (runq->current_state == CURRENT_STOPPED) 531 | goto next; 532 | } 533 | 534 | t->handler(t->data); 535 | 536 | mutex_lock(&runq->mutex); 537 | if (runq->current != t) 538 | /* tasklet was destroyed */ 539 | goto next; 540 | 541 | switch (runq->current_state) { 542 | case CURRENT_STARTED: 543 | /* Detect dangling tasklets that are not on a 544 | waitlist and were not explicitly 545 | stopped. */ 546 | assert(t->waited); 547 | assert(t->wait); 548 | 549 | /* fall through */ 550 | case CURRENT_STOPPED: 551 | t->runq = NULL; 552 | break; 553 | 554 | case CURRENT_REQUEUE: 555 | run_queue_enqueue(runq, t); 556 | } 557 | 558 | mutex_unlock(t->mutex); 559 | 560 | next: 561 | if (runq->stop_waiting) { 562 | runq->stop_waiting = false; 563 | cond_broadcast(&runq->cond); 564 | } 565 | 566 | t = runq->head; 567 | } while (t); 568 | 569 | runq->current = NULL; 570 | 571 | out: 572 | mutex_unlock(&runq->mutex); 573 | } 574 | 575 | void tasklet_stop(struct tasklet *t) 576 | { 577 | mutex_assert_held(t->mutex); 578 | tasklet_unwait(t); 579 | 580 | for (;;) { 581 | struct run_queue *runq = t->runq; 582 | if (!runq) 583 | break; 584 | 585 | mutex_lock(&runq->mutex); 586 | 587 | if (t->runq == runq) { 588 | if (runq->current != t) { 589 | run_queue_remove(runq, t); 590 | t->runq = NULL; 591 | } else { 592 | runq->current_state = CURRENT_STOPPED; 593 | 594 | if (thread_handle_current() != runq->thread) { 595 | mutex_veto_transfer(t->mutex); 596 | 597 | /* Wait until the tasklet is done */ 598 | runq->stop_waiting = true; 599 | 600 | do 601 | cond_wait(&runq->cond, &runq->mutex); 602 | while (runq->current == t); 603 | } 604 | } 605 | 606 | mutex_unlock(&runq->mutex); 607 | break; 608 | } 609 | 610 | mutex_unlock(&runq->mutex); 611 | } 612 | } 613 | 614 | void tasklet_fini(struct tasklet *t) 615 | { 616 | mutex_assert_held(t->mutex); 617 | tasklet_unwait(t); 618 | 619 | for (;;) { 620 | struct run_queue *runq = t->runq; 621 | if (!runq) 622 | break; 623 | 624 | mutex_lock(&runq->mutex); 625 | 626 | if (t->runq == runq) { 627 | if (runq->current != t) { 628 | run_queue_remove(runq, t); 629 | t->runq = NULL; 630 | } else { 631 | runq->current_state = CURRENT_STOPPED; 632 | 633 | if (thread_handle_current() == runq->thread) { 634 | runq->current = NULL; 635 | } else { 636 | mutex_veto_transfer(t->mutex); 637 | 638 | /* Wait until the tasklet is done */ 639 | runq->stop_waiting = true; 640 | 641 | do 642 | cond_wait(&runq->cond, &runq->mutex); 643 | while (runq->current == t); 644 | } 645 | } 646 | 647 | mutex_unlock(&runq->mutex); 648 | break; 649 | } 650 | 651 | mutex_unlock(&runq->mutex); 652 | } 653 | 654 | t->mutex = NULL; 655 | t->handler = NULL; 656 | t->data = NULL; 657 | mutex_fini(&t->wait_mutex); 658 | } 659 | 660 | static void worker_thread(void *v_worker) 661 | { 662 | struct worker *w = v_worker; 663 | 664 | for (;;) { 665 | struct run_queue *runq = w->runq; 666 | if (!runq) 667 | break; 668 | 669 | run_queue_run(runq, true); 670 | } 671 | } 672 | -------------------------------------------------------------------------------- /src/thread.c: -------------------------------------------------------------------------------- 1 | #include "thread.h" 2 | 3 | #include 4 | #include 5 | 6 | struct thread_params { 7 | void (*func)(void *data); 8 | void *data; 9 | }; 10 | 11 | static void *thread_trampoline(void *v_params) 12 | { 13 | struct thread_params params = *(struct thread_params *) v_params; 14 | free(v_params); 15 | params.func(params.data); 16 | return NULL; 17 | } 18 | 19 | void thread_init(struct thread *thr, void (*func)(void *data), void *data) 20 | { 21 | struct thread_params *params = malloc(sizeof *params); 22 | params->func = func; 23 | params->data = data; 24 | pthread_create(&thr->handle, NULL, thread_trampoline, params); 25 | thr->init = malloc(1); 26 | } 27 | 28 | void thread_fini(struct thread *thr) 29 | { 30 | pthread_join(thr->handle, NULL); 31 | free(thr->init); 32 | } 33 | 34 | void thread_signal(thread_handle_t thr, int sig) 35 | { 36 | pthread_kill(thr, sig); 37 | } 38 | 39 | void mutex_init(struct mutex *m) 40 | { 41 | skinny_mutex_init(&m->mutex); 42 | m->init = malloc(1); 43 | m->held = false; 44 | } 45 | 46 | void mutex_fini(struct mutex *m) 47 | { 48 | assert(!m->held); 49 | free(m->init); 50 | skinny_mutex_destroy(&m->mutex); 51 | } 52 | 53 | void mutex_lock(struct mutex *m) 54 | { 55 | skinny_mutex_lock(&m->mutex); 56 | m->held = true; 57 | } 58 | 59 | void mutex_unlock(struct mutex *m) 60 | { 61 | assert(m->held); 62 | m->held = false; 63 | skinny_mutex_unlock(&m->mutex); 64 | } 65 | 66 | bool mutex_transfer(struct mutex *a, struct mutex *b) 67 | { 68 | assert(a->held); 69 | a->held = false; 70 | int res = skinny_mutex_transfer(&a->mutex, &b->mutex); 71 | if (res != EAGAIN) { 72 | b->held = true; 73 | return true; 74 | } 75 | a->held = true; 76 | return false; 77 | } 78 | 79 | void mutex_veto_transfer(struct mutex *m) 80 | { 81 | assert(m->held); 82 | skinny_mutex_veto_transfer(&m->mutex); 83 | } 84 | 85 | void cond_init(struct cond *c) 86 | { 87 | pthread_cond_init(&c->cond, NULL); 88 | c->init = malloc(1); 89 | } 90 | 91 | void cond_fini(struct cond *c) 92 | { 93 | free(c->init); 94 | pthread_cond_destroy(&c->cond); 95 | } 96 | 97 | void cond_wait(struct cond *c, struct mutex *m) 98 | { 99 | mutex_assert_held(m); 100 | m->held = false; 101 | skinny_mutex_cond_wait(&c->cond, &m->mutex); 102 | m->held = true; 103 | } 104 | 105 | void cond_signal(struct cond *c) 106 | { 107 | pthread_cond_signal(&c->cond); 108 | } 109 | 110 | void cond_broadcast(struct cond *c) 111 | { 112 | pthread_cond_broadcast(&c->cond); 113 | } 114 | -------------------------------------------------------------------------------- /src/threadpool.c: -------------------------------------------------------------------------------- 1 | #include "threadpool.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "logger.h" 7 | 8 | typedef struct task_s { 9 | void (*func)(void *); 10 | void *arg; 11 | struct task_s *next; 12 | } task_t; 13 | 14 | struct threadpool_internal { 15 | pthread_mutex_t lock; 16 | pthread_cond_t cond; 17 | pthread_t *threads; 18 | task_t *head; 19 | int thread_count; 20 | int queue_size; 21 | int shutdown; 22 | int started; 23 | }; 24 | 25 | typedef enum { immediate_shutdown = 1, graceful_shutdown = 2 } threadpool_sd_t; 26 | 27 | static int threadpool_free(threadpool_t *pool) 28 | { 29 | if (!pool || pool->started > 0) 30 | return -1; 31 | 32 | if (pool->threads) 33 | free(pool->threads); 34 | 35 | while (pool->head->next) { 36 | task_t *old = pool->head->next; 37 | pool->head->next = pool->head->next->next; 38 | free(old); 39 | } 40 | 41 | return 0; 42 | } 43 | 44 | static void *worker(void *arg) 45 | { 46 | if (!arg) { 47 | log_err("arg should be type threadpool_t*"); 48 | return NULL; 49 | } 50 | 51 | threadpool_t *pool = (threadpool_t *) arg; 52 | 53 | while (1) { 54 | pthread_mutex_lock(&(pool->lock)); 55 | 56 | /* Wait on condition variable, check for spurious wakeups. */ 57 | while ((pool->queue_size == 0) && !(pool->shutdown)) { 58 | pthread_cond_wait(&(pool->cond), &(pool->lock)); 59 | } 60 | 61 | if ((pool->shutdown == immediate_shutdown) || 62 | ((pool->shutdown == graceful_shutdown) && pool->queue_size == 0)) 63 | break; 64 | 65 | task_t *task = pool->head->next; 66 | if (!task) { 67 | pthread_mutex_unlock(&(pool->lock)); 68 | continue; 69 | } 70 | 71 | pool->head->next = task->next; 72 | pool->queue_size--; 73 | 74 | pthread_mutex_unlock(&(pool->lock)); 75 | 76 | (*(task->func))(task->arg); 77 | /* TODO: memory pool */ 78 | free(task); 79 | } 80 | 81 | pool->started--; 82 | pthread_mutex_unlock(&(pool->lock)); 83 | pthread_exit(NULL); 84 | 85 | return NULL; 86 | } 87 | threadpool_t *threadpool_init(int thread_num) 88 | { 89 | if (thread_num <= 0) { 90 | log_err("the arg of threadpool_init must greater than 0"); 91 | return NULL; 92 | } 93 | 94 | threadpool_t *pool; 95 | if (!(pool = (threadpool_t *) malloc(sizeof(threadpool_t)))) 96 | goto err; 97 | 98 | pool->thread_count = 0; 99 | pool->queue_size = 0; 100 | pool->shutdown = 0; 101 | pool->started = 0; 102 | pool->threads = (pthread_t *) malloc(sizeof(pthread_t) * thread_num); 103 | pool->head = (task_t *) malloc(sizeof(task_t)); /* dummy head */ 104 | 105 | if (!pool->threads || !pool->head) 106 | goto err; 107 | 108 | pool->head->func = NULL; 109 | pool->head->arg = NULL; 110 | pool->head->next = NULL; 111 | 112 | if (pthread_mutex_init(&(pool->lock), NULL)) 113 | goto err; 114 | 115 | if (pthread_cond_init(&(pool->cond), NULL)) { 116 | pthread_mutex_destroy(&(pool->lock)); 117 | goto err; 118 | } 119 | 120 | for (int i = 0; i < thread_num; ++i) { 121 | if (pthread_create(&(pool->threads[i]), NULL, worker, pool)) { 122 | threadpool_destroy(pool, 0); 123 | return NULL; 124 | } 125 | log_info("thread: %08x started", (uint32_t) pool->threads[i]); 126 | 127 | pool->thread_count++; 128 | pool->started++; 129 | } 130 | 131 | return pool; 132 | 133 | err: 134 | if (pool) 135 | threadpool_free(pool); 136 | 137 | return NULL; 138 | } 139 | 140 | int threadpool_add(threadpool_t *pool, void (*func)(void *), void *arg) 141 | { 142 | int rc, err = 0; 143 | if (!pool || !func) 144 | return -1; 145 | 146 | if (pthread_mutex_lock(&(pool->lock)) != 0) 147 | return -1; 148 | 149 | if (pool->shutdown) { 150 | err = tp_already_shutdown; 151 | goto out; 152 | } 153 | 154 | // TODO: use a memory pool 155 | task_t *task = (task_t *) malloc(sizeof(task_t)); 156 | if (!task) { 157 | log_err("malloc task fail"); 158 | goto out; 159 | } 160 | 161 | // TODO: use a memory pool 162 | task->func = func; 163 | task->arg = arg; 164 | task->next = pool->head->next; 165 | pool->head->next = task; 166 | 167 | pool->queue_size++; 168 | 169 | rc = pthread_cond_signal(&(pool->cond)); 170 | check(rc == 0, "pthread_cond_signal"); 171 | 172 | out: 173 | if (pthread_mutex_unlock(&pool->lock) != 0) { 174 | log_err("pthread_mutex_unlock"); 175 | return -1; 176 | } 177 | 178 | return err; 179 | } 180 | 181 | int threadpool_destroy(threadpool_t *pool, bool graceful) 182 | { 183 | int err = 0; 184 | 185 | if (!pool) 186 | return tp_invalid; 187 | 188 | if (pthread_mutex_lock(&(pool->lock))) 189 | return tp_lock_fail; 190 | 191 | do { 192 | // set the showdown flag of pool and wake up all thread 193 | if (pool->shutdown) { 194 | err = tp_already_shutdown; 195 | break; 196 | } 197 | 198 | pool->shutdown = (graceful) ? graceful_shutdown : immediate_shutdown; 199 | 200 | if (pthread_cond_broadcast(&(pool->cond))) { 201 | err = tp_cond_broadcast; 202 | break; 203 | } 204 | 205 | if (pthread_mutex_unlock(&(pool->lock))) { 206 | err = tp_lock_fail; 207 | break; 208 | } 209 | 210 | for (int i = 0; i < pool->thread_count; i++) { 211 | if (pthread_join(pool->threads[i], NULL)) 212 | err = tp_thread_fail; 213 | log_info("thread %08x exit", (uint32_t) pool->threads[i]); 214 | } 215 | } while (0); 216 | 217 | if (!err) { 218 | pthread_mutex_destroy(&(pool->lock)); 219 | pthread_cond_destroy(&(pool->cond)); 220 | threadpool_free(pool); 221 | } 222 | 223 | return err; 224 | } 225 | -------------------------------------------------------------------------------- /src/threadtracer.c: -------------------------------------------------------------------------------- 1 | #include "threadtracer.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define MAXTHREADS 12 //!< How many threads can we support? 14 | #define MAXSAMPLES 64 * 1024 //!< How many samples can we record for a thread? 15 | 16 | //! How many threads are we currently tracing? 17 | static _Atomic int numthreads = 0; 18 | 19 | //! When (in wallclock time) did we start tracing? 20 | static int64_t walloffset = 0; 21 | 22 | //! Optionally, we can delay the recording until this timestamp using 23 | //! THREADTRACERSKIP env var. 24 | static int64_t wallcutoff = 0; 25 | 26 | //! Are we currently recording events? 27 | static int isrecording = 0; 28 | 29 | //! The information we record for a trace event. 30 | typedef struct { 31 | const char *cat; //!< category 32 | const char *tag; //!< tag 33 | const char *phase; //!< "B" or "E" 34 | int64_t wall_time; //!< timestamp on wall clock 35 | int64_t cpu_time; //!< timestamp on thread's cpu clock 36 | int64_t num_preemptive_switch, //!< number of context switches (premptive) 37 | num_voluntary_switch; //!< number of context switches (cooperative) 38 | } sample_t; 39 | 40 | //! The samples recorded, per thread. 41 | static sample_t samples[MAXTHREADS][MAXSAMPLES]; 42 | 43 | //! The number of samples recorded, per thread. 44 | static int samplecounts[MAXTHREADS]; 45 | 46 | //! The names for the threads. 47 | static const char *threadnames[MAXTHREADS]; 48 | 49 | //! The thread-ids. 50 | static pthread_t threadids[MAXTHREADS]; 51 | 52 | static __thread int tidx; 53 | 54 | //! Before tracing, a thread should make itself known to ThreadTracer. 55 | int tt_signin(const char *threadname) 56 | { 57 | int slot = numthreads++; 58 | if (slot == 0) { 59 | struct timespec wt, ct; 60 | clock_gettime(CLOCK_MONOTONIC, &wt); 61 | clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ct); 62 | walloffset = wt.tv_sec * 1000000000L + wt.tv_nsec; 63 | struct timespec res; 64 | clock_getres(CLOCK_THREAD_CPUTIME_ID, &res); 65 | fprintf(stderr, "ThreadTracer: clock resolution: %ld nsec.\n", 66 | res.tv_nsec); 67 | wallcutoff = walloffset; 68 | const char *d = getenv("THREADTRACERSKIP"); 69 | if (d) { 70 | int delayinseconds = atoi(d); 71 | wallcutoff += delayinseconds * 1000000000L; 72 | fprintf(stderr, 73 | "ThreadTracer: skipping the first %d seconds before " 74 | "recording.\n", 75 | delayinseconds); 76 | } 77 | isrecording = 1; 78 | } 79 | if (slot >= MAXTHREADS) { 80 | numthreads = MAXTHREADS; 81 | return -1; 82 | } 83 | threadnames[slot] = threadname; 84 | threadids[slot] = pthread_self(); 85 | samplecounts[slot] = 0; 86 | tidx = slot; 87 | return slot; 88 | } 89 | 90 | #if defined(__APPLE__) 91 | static int getrusage_thread(struct rusage *rusage) 92 | { 93 | /* FIXME: need correct implementation for macOS */ 94 | return getrusage(RUSAGE_SELF, rusage); 95 | } 96 | #endif 97 | 98 | //! Record a timestamp. 99 | int tt_stamp(const char *cat, const char *tag, const char *phase) 100 | { 101 | if (!isrecording) { 102 | if (!numthreads) 103 | fprintf(stderr, 104 | "ThreadTracer: ERROR. Threads did not sign in yet. Cannot " 105 | "record.\n"); 106 | return -1; 107 | } 108 | 109 | struct timespec wt, ct; 110 | clock_gettime(CLOCK_MONOTONIC, &wt); 111 | clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ct); 112 | struct rusage ru; 113 | int rv; 114 | #if defined(__APPLE__) 115 | rv = getrusage_thread(&ru); 116 | #else 117 | rv = getrusage(RUSAGE_THREAD, &ru); 118 | #endif 119 | if (rv < 0) { 120 | isrecording = 0; 121 | fprintf(stderr, "ThreadTracer: rusage() failed. Stopped Recording.\n"); 122 | return -1; 123 | } 124 | 125 | const int64_t wall_nsec = wt.tv_sec * 1000000000 + wt.tv_nsec; 126 | const int64_t cpu_nsec = ct.tv_sec * 1000000000 + ct.tv_nsec; 127 | 128 | if (wall_nsec < wallcutoff) 129 | return -1; 130 | 131 | const int cnt = samplecounts[tidx]; 132 | if (cnt >= MAXSAMPLES) { 133 | isrecording = 0; 134 | fprintf(stderr, 135 | "ThreadTracer: Stopped recording samples. Limit(%d) " 136 | "reached.\n", 137 | MAXSAMPLES); 138 | return -1; 139 | } 140 | sample_t *sample = samples[tidx] + samplecounts[tidx]; 141 | sample->wall_time = wall_nsec - walloffset; 142 | sample->cpu_time = cpu_nsec; 143 | sample->num_preemptive_switch = ru.ru_nivcsw; 144 | sample->num_voluntary_switch = ru.ru_nvcsw; 145 | sample->tag = tag; 146 | sample->cat = cat; 147 | sample->phase = phase; 148 | return samplecounts[tidx]++; 149 | 150 | fprintf(stderr, 151 | "ThreadTracer: Thread(%" PRIu64 152 | ") was not signed in before recording the first time stamp.\n", 153 | (uint64_t) threadids[tidx]); 154 | fprintf(stderr, 155 | "ThreadTracer: Recording has stopped due to sign-in error.\n"); 156 | isrecording = 0; 157 | return -1; 158 | } 159 | 160 | int tt_report(const char *user_oname) 161 | { 162 | char default_oname[256]; 163 | const char *oname; 164 | 165 | isrecording = 0; 166 | if (!user_oname) { 167 | long pid = (long) getpid(); 168 | snprintf(default_oname, 256, "threadtracer.%ld.json", pid); 169 | oname = default_oname; 170 | } else { 171 | oname = user_oname; 172 | } 173 | 174 | if (numthreads == 0) { 175 | fprintf(stderr, 176 | "ThreadTracer: Nothing to report, 0 threads signed in.\n"); 177 | return -1; 178 | } 179 | FILE *f = fopen(oname, "w"); 180 | if (!f) 181 | return -1; 182 | 183 | int total = 0; 184 | int discarded = 0; 185 | fprintf(f, "{\"traceEvents\":[\n"); 186 | 187 | for (int t = 0; t < numthreads; ++t) { 188 | for (int s = 0; s < samplecounts[t]; ++s) { 189 | const sample_t *sample = samples[t] + s; 190 | 191 | char argstr[128]; 192 | if (sample->phase[0] == 'E') { 193 | if (s == 0) { 194 | discarded++; 195 | goto notfound; 196 | } 197 | int i = s - 1; 198 | while (strcmp(samples[t][i].tag, sample->tag) || 199 | samples[t][i].phase[0] != 'B') { 200 | i--; 201 | if (i < 0) { 202 | discarded++; 203 | goto notfound; 204 | } 205 | }; 206 | const sample_t *beginsample = samples[t] + i; 207 | int64_t preempted = sample->num_preemptive_switch - 208 | beginsample->num_preemptive_switch; 209 | int64_t voluntary = sample->num_voluntary_switch - 210 | beginsample->num_voluntary_switch; 211 | int64_t walldur = sample->wall_time - beginsample->wall_time; 212 | int64_t cpudur = sample->cpu_time - beginsample->cpu_time; 213 | int64_t dutycycle = 100 * cpudur / walldur; 214 | snprintf(argstr, sizeof(argstr), 215 | "{\"preempted\":%" PRIu64 ",\"voluntary\":%" PRIu64 216 | ",\"dutycycle(%%)\":%" PRIu64 "}", 217 | preempted, voluntary, dutycycle); 218 | } else 219 | snprintf(argstr, sizeof(argstr), "{}"); 220 | 221 | if (total) 222 | fprintf(f, ",\n"); 223 | fprintf(f, 224 | "{\"cat\":\"%s\"," 225 | "\"pid\":%" PRIu64 226 | "," 227 | "\"tid\":%" PRIu64 228 | "," 229 | "\"ts\":%" PRIu64 230 | "," 231 | "\"tts\":%" PRIu64 232 | "," 233 | "\"ph\":\"%s\"," 234 | "\"name\":\"%s\"," 235 | "\"args\":%s}", 236 | sample->cat, (uint64_t) getpid(), (uint64_t) threadids[t], 237 | sample->wall_time / 1000, sample->cpu_time / 1000, 238 | sample->phase, sample->tag, argstr); 239 | total++; 240 | // Note: unfortunately, the chrome tracing JSON format no longer 241 | // supports 'I' (instant) events. 242 | notfound: 243 | (void) 0; 244 | } 245 | } 246 | for (int t = 0; t < numthreads; ++t) { 247 | fprintf(f, ",\n{"); 248 | fprintf(f, 249 | "\"name\": \"thread_name\", " 250 | "\"ph\": \"M\", " 251 | "\"pid\":%ld, " 252 | "\"tid\":%" PRIu64 253 | ", " 254 | "\"args\": { \"name\" : \"%s\" } }", 255 | (long) getpid(), (uint64_t) threadids[t], threadnames[t]); 256 | } 257 | 258 | fprintf(f, "\n]}\n"); 259 | fclose(f); 260 | fprintf(stderr, "ThreadTracer: Wrote %d events (%d discarded) to %s\n", 261 | total, discarded, oname); 262 | return total; 263 | } 264 | -------------------------------------------------------------------------------- /tests/test-heavy.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "threadpool.h" 7 | #include "threadtracer.h" 8 | 9 | #define THREAD 4 10 | #define SIZE 8192 11 | 12 | static threadpool_t *pool[1]; 13 | static int tasks[SIZE], left; 14 | static pthread_mutex_t lock; 15 | 16 | static void dummy_task(void *arg) 17 | { 18 | int *pi = (int *) arg; 19 | *pi += 1; 20 | 21 | TT_ENTRY(__func__); 22 | 23 | TT_BEGIN(__func__); 24 | if (*pi < 1) { 25 | assert(threadpool_add(pool[*pi], &dummy_task, arg) == 0); 26 | } else { 27 | pthread_mutex_lock(&lock); 28 | left--; 29 | pthread_mutex_unlock(&lock); 30 | } 31 | TT_END(__func__); 32 | } 33 | 34 | int main() 35 | { 36 | left = SIZE; 37 | pthread_mutex_init(&lock, NULL); 38 | 39 | pool[0] = threadpool_init(THREAD); 40 | assert(pool[0] != NULL); 41 | 42 | usleep(10); 43 | 44 | for (int i = 0; i < SIZE; i++) { 45 | tasks[i] = 0; 46 | assert(threadpool_add(pool[0], &dummy_task, &(tasks[i])) == 0); 47 | } 48 | 49 | int copy = 1; 50 | while (copy > 0) { 51 | usleep(10); 52 | pthread_mutex_lock(&lock); 53 | copy = left; 54 | pthread_mutex_unlock(&lock); 55 | } 56 | 57 | assert(threadpool_destroy(pool[0], 0) == 0); 58 | 59 | pthread_mutex_destroy(&lock); 60 | 61 | TT_REPORT(); 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /tests/test-shutdown.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "threadpool.h" 7 | #include "threadtracer.h" 8 | 9 | #define THREAD 4 10 | #define SIZE 8192 11 | 12 | threadpool_t *pool; 13 | int left; 14 | pthread_mutex_t lock; 15 | 16 | int error; 17 | 18 | void dummy_task(void *arg UNUSED) 19 | { 20 | TT_ENTRY(__func__); 21 | 22 | usleep(100); 23 | TT_BEGIN(__func__); 24 | pthread_mutex_lock(&lock); 25 | left--; 26 | pthread_mutex_unlock(&lock); 27 | TT_END(__func__); 28 | } 29 | 30 | int main() 31 | { 32 | pthread_mutex_init(&lock, NULL); 33 | 34 | /* Testing immediate shutdown */ 35 | left = SIZE; 36 | pool = threadpool_init(THREAD); 37 | for (int i = 0; i < SIZE; i++) 38 | assert(threadpool_add(pool, &dummy_task, NULL) == 0); 39 | assert(threadpool_destroy(pool, false) == 0); 40 | assert(left > 0); 41 | TT_REPORT(); 42 | 43 | /* Testing graceful shutdown */ 44 | left = SIZE; 45 | pool = threadpool_init(THREAD); 46 | for (int i = 0; i < SIZE; i++) 47 | assert(threadpool_add(pool, &dummy_task, NULL) == 0); 48 | assert(threadpool_destroy(pool, true) == 0); 49 | assert(left == 0); 50 | TT_REPORT(); 51 | 52 | pthread_mutex_destroy(&lock); 53 | 54 | return 0; 55 | } 56 | -------------------------------------------------------------------------------- /tests/test-skinny-mutex.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "skinny_mutex.h" 6 | 7 | static void test_static_mutex(void) 8 | { 9 | static skinny_mutex_t static_mutex = SKINNY_MUTEX_INITIALIZER; 10 | 11 | assert(!skinny_mutex_lock(&static_mutex)); 12 | assert(!skinny_mutex_unlock(&static_mutex)); 13 | assert(!skinny_mutex_destroy(&static_mutex)); 14 | } 15 | 16 | static void test_lock_unlock(skinny_mutex_t *mutex) 17 | { 18 | assert(!skinny_mutex_lock(mutex)); 19 | assert(!skinny_mutex_unlock(mutex)); 20 | } 21 | 22 | /* Wait a millisecond */ 23 | static void delay(void) 24 | { 25 | struct timespec ts = {.tv_sec = 0, .tv_nsec = 1000000}; 26 | assert(!nanosleep(&ts, NULL)); 27 | } 28 | 29 | struct test_contention { 30 | skinny_mutex_t *mutex; 31 | bool held; 32 | int count; 33 | }; 34 | 35 | static void *bump(void *v_tc) 36 | { 37 | struct test_contention *tc = v_tc; 38 | 39 | assert(!skinny_mutex_lock(tc->mutex)); 40 | assert(!tc->held); 41 | tc->held = true; 42 | delay(); 43 | tc->held = false; 44 | tc->count++; 45 | assert(!skinny_mutex_unlock(tc->mutex)); 46 | 47 | return NULL; 48 | } 49 | 50 | static void test_contention(skinny_mutex_t *mutex) 51 | { 52 | pthread_t threads[10]; 53 | 54 | struct test_contention tc = {.mutex = mutex, .held = false, .count = 0}; 55 | 56 | assert(!skinny_mutex_lock(tc.mutex)); 57 | 58 | for (int i = 0; i < 10; i++) 59 | assert(!pthread_create(&threads[i], NULL, bump, &tc)); 60 | 61 | assert(!skinny_mutex_unlock(tc.mutex)); 62 | 63 | for (int i = 0; i < 10; i++) 64 | assert(!pthread_join(threads[i], NULL)); 65 | 66 | assert(!skinny_mutex_lock(tc.mutex)); 67 | assert(!tc.held); 68 | assert(tc.count == 10); 69 | assert(!skinny_mutex_unlock(tc.mutex)); 70 | } 71 | 72 | static void *lock_cancellation_thread(void *v_mutex) 73 | { 74 | skinny_mutex_t *mutex = v_mutex; 75 | assert(!skinny_mutex_lock(mutex)); 76 | assert(!skinny_mutex_unlock(mutex)); 77 | return NULL; 78 | } 79 | 80 | /* skinny_mutex_lock is *not* a cancellation point. */ 81 | static void test_lock_cancellation(skinny_mutex_t *mutex) 82 | { 83 | pthread_t thread; 84 | void *retval; 85 | 86 | assert(!skinny_mutex_lock(mutex)); 87 | assert(!pthread_create(&thread, NULL, lock_cancellation_thread, mutex)); 88 | delay(); 89 | assert(!pthread_cancel(thread)); 90 | assert(!skinny_mutex_unlock(mutex)); 91 | assert(!pthread_join(thread, &retval)); 92 | assert(!retval); 93 | } 94 | 95 | static void *trylock_thread(void *v_mutex) 96 | { 97 | skinny_mutex_t *mutex = v_mutex; 98 | assert(skinny_mutex_trylock(mutex) == EBUSY); 99 | return NULL; 100 | } 101 | 102 | static void *trylock_contender_thread(void *v_mutex) 103 | { 104 | skinny_mutex_t *mutex = v_mutex; 105 | assert(!skinny_mutex_lock(mutex)); 106 | delay(); 107 | delay(); 108 | assert(!skinny_mutex_unlock(mutex)); 109 | return NULL; 110 | } 111 | 112 | static void test_trylock(skinny_mutex_t *mutex) 113 | { 114 | pthread_t thread1, thread2; 115 | 116 | assert(!skinny_mutex_trylock(mutex)); 117 | 118 | assert(!pthread_create(&thread1, NULL, trylock_thread, mutex)); 119 | assert(!pthread_join(thread1, NULL)); 120 | 121 | assert(!pthread_create(&thread1, NULL, trylock_contender_thread, mutex)); 122 | delay(); 123 | assert(!pthread_create(&thread2, NULL, trylock_thread, mutex)); 124 | assert(!pthread_join(thread2, NULL)); 125 | assert(!skinny_mutex_unlock(mutex)); 126 | assert(!pthread_join(thread1, NULL)); 127 | } 128 | 129 | struct test_cond_wait { 130 | skinny_mutex_t *mutex; 131 | pthread_cond_t cond; 132 | int flag; 133 | }; 134 | 135 | static void test_cond_wait_cleanup(void *v_tcw) 136 | { 137 | struct test_cond_wait *tcw = v_tcw; 138 | assert(!skinny_mutex_unlock(tcw->mutex)); 139 | } 140 | 141 | static void *test_cond_wait_thread(void *v_tcw) 142 | { 143 | struct test_cond_wait *tcw = v_tcw; 144 | 145 | assert(!skinny_mutex_lock(tcw->mutex)); 146 | pthread_cleanup_push(test_cond_wait_cleanup, tcw); 147 | 148 | while (!tcw->flag) 149 | assert(!skinny_mutex_cond_wait(&tcw->cond, tcw->mutex)); 150 | 151 | pthread_cleanup_pop(1); 152 | return NULL; 153 | } 154 | 155 | static void test_cond_wait(skinny_mutex_t *mutex) 156 | { 157 | struct test_cond_wait tcw = {.mutex = mutex}; 158 | pthread_t thread; 159 | 160 | assert(!pthread_cond_init(&tcw.cond, NULL)); 161 | tcw.flag = 0; 162 | 163 | assert(!pthread_create(&thread, NULL, test_cond_wait_thread, &tcw)); 164 | 165 | delay(); 166 | assert(!skinny_mutex_lock(mutex)); 167 | tcw.flag = 1; 168 | assert(!pthread_cond_signal(&tcw.cond)); 169 | assert(!skinny_mutex_unlock(mutex)); 170 | 171 | assert(!pthread_join(thread, NULL)); 172 | 173 | assert(!pthread_cond_destroy(&tcw.cond)); 174 | } 175 | 176 | static void test_cond_timedwait(skinny_mutex_t *mutex) 177 | { 178 | pthread_cond_t cond; 179 | struct timespec t; 180 | 181 | assert(!pthread_cond_init(&cond, NULL)); 182 | 183 | assert(!clock_gettime(CLOCK_REALTIME, &t)); 184 | 185 | t.tv_nsec += 1000000; 186 | if (t.tv_nsec > 1000000000) { 187 | t.tv_nsec -= 1000000000; 188 | t.tv_sec++; 189 | } 190 | 191 | assert(!skinny_mutex_lock(mutex)); 192 | assert(skinny_mutex_cond_timedwait(&cond, mutex, &t) == ETIMEDOUT); 193 | assert(!skinny_mutex_unlock(mutex)); 194 | 195 | assert(!pthread_cond_destroy(&cond)); 196 | } 197 | 198 | static void test_cond_wait_cancellation(skinny_mutex_t *mutex) 199 | { 200 | struct test_cond_wait tcw = {.mutex = mutex}; 201 | pthread_t thread; 202 | void *retval; 203 | 204 | assert(!pthread_cond_init(&tcw.cond, NULL)); 205 | tcw.flag = 0; 206 | 207 | assert(!pthread_create(&thread, NULL, test_cond_wait_thread, &tcw)); 208 | 209 | delay(); 210 | assert(!pthread_cancel(thread)); 211 | assert(!pthread_join(thread, &retval)); 212 | assert(retval == PTHREAD_CANCELED); 213 | 214 | assert(!pthread_cond_destroy(&tcw.cond)); 215 | } 216 | 217 | static void test_unlock_not_held(skinny_mutex_t *mutex) 218 | { 219 | assert(skinny_mutex_unlock(mutex) == EPERM); 220 | } 221 | 222 | static void do_test_simple(void (*f)(skinny_mutex_t *m)) 223 | { 224 | skinny_mutex_t mutex; 225 | 226 | assert(!skinny_mutex_init(&mutex)); 227 | f(&mutex); 228 | assert(!skinny_mutex_destroy(&mutex)); 229 | } 230 | 231 | struct do_test_cond { 232 | skinny_mutex_t mutex; 233 | pthread_cond_t cond; 234 | int phase; 235 | }; 236 | 237 | static void *do_test_cond_thread(void *v_dt) 238 | { 239 | struct do_test_cond *dt = v_dt; 240 | 241 | assert(!skinny_mutex_lock(&dt->mutex)); 242 | dt->phase = 1; 243 | assert(!pthread_cond_signal(&dt->cond)); 244 | 245 | do { 246 | assert(!skinny_mutex_cond_wait(&dt->cond, &dt->mutex)); 247 | } while (dt->phase != 2); 248 | 249 | assert(!skinny_mutex_unlock(&dt->mutex)); 250 | 251 | return NULL; 252 | } 253 | 254 | /* Run the test with a thread waiting on a cond var associated with the 255 | mutex. This ensures that the skinny mutex has a fat mutex during 256 | the text. */ 257 | static void do_test_cond_wait(void (*f)(skinny_mutex_t *m)) 258 | { 259 | struct do_test_cond dt; 260 | pthread_t thread; 261 | 262 | assert(!skinny_mutex_init(&dt.mutex)); 263 | assert(!pthread_cond_init(&dt.cond, NULL)); 264 | dt.phase = 0; 265 | assert(!pthread_create(&thread, NULL, do_test_cond_thread, &dt)); 266 | 267 | assert(!skinny_mutex_lock(&dt.mutex)); 268 | while (dt.phase != 1) { 269 | assert(!skinny_mutex_cond_wait(&dt.cond, &dt.mutex)); 270 | }; 271 | assert(!skinny_mutex_unlock(&dt.mutex)); 272 | 273 | f(&dt.mutex); 274 | 275 | assert(!skinny_mutex_lock(&dt.mutex)); 276 | dt.phase = 2; 277 | assert(!pthread_cond_signal(&dt.cond)); 278 | assert(!skinny_mutex_unlock(&dt.mutex)); 279 | 280 | assert(!pthread_join(thread, NULL)); 281 | assert(!skinny_mutex_destroy(&dt.mutex)); 282 | assert(!pthread_cond_destroy(&dt.cond)); 283 | } 284 | 285 | static void *do_test_hammer_thread(void *v_m) 286 | { 287 | skinny_mutex_t *m = v_m; 288 | 289 | for (;;) { 290 | assert(!skinny_mutex_lock(m)); 291 | assert(!skinny_mutex_unlock(m)); 292 | pthread_testcancel(); 293 | } 294 | 295 | return NULL; 296 | } 297 | 298 | /* Run the test with a bunch of other threads hammering on the 299 | mutex. */ 300 | static void do_test_hammer(void (*f)(skinny_mutex_t *m)) 301 | { 302 | skinny_mutex_t mutex; 303 | pthread_t threads[10]; 304 | int i; 305 | 306 | assert(!skinny_mutex_init(&mutex)); 307 | 308 | for (i = 0; i < 10; i++) 309 | assert( 310 | !pthread_create(&threads[i], NULL, do_test_hammer_thread, &mutex)); 311 | 312 | delay(); 313 | f(&mutex); 314 | 315 | for (i = 0; i < 10; i++) { 316 | void *retval; 317 | assert(!pthread_cancel(threads[i])); 318 | assert(!pthread_join(threads[i], &retval)); 319 | assert(retval == PTHREAD_CANCELED); 320 | } 321 | 322 | assert(!skinny_mutex_destroy(&mutex)); 323 | } 324 | 325 | static void do_test(void (*f)(skinny_mutex_t *m), int hammer) 326 | { 327 | do_test_simple(f); 328 | do_test_cond_wait(f); 329 | if (hammer) 330 | do_test_hammer(f); 331 | } 332 | 333 | int main(void) 334 | { 335 | test_static_mutex(); 336 | 337 | do_test(test_lock_unlock, 1); 338 | do_test(test_contention, 1); 339 | do_test(test_lock_cancellation, 1); 340 | do_test(test_trylock, 0); 341 | do_test(test_cond_wait, 1); 342 | do_test(test_cond_timedwait, 1); 343 | do_test(test_cond_wait_cancellation, 1); 344 | do_test(test_unlock_not_held, 0); 345 | 346 | return 0; 347 | } 348 | -------------------------------------------------------------------------------- /tests/test-tasklet.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "tasklet.h" 5 | 6 | struct test_tasklet { 7 | struct mutex mutex; 8 | struct tasklet tasklet; 9 | struct wait_list *sema; 10 | unsigned int got; 11 | }; 12 | 13 | void test_tasklet_wait(void *v_tt) 14 | { 15 | struct test_tasklet *tt = v_tt; 16 | 17 | while (wait_list_down(tt->sema, 1, &tt->tasklet)) 18 | tt->got++; 19 | } 20 | 21 | struct test_tasklet *test_tasklet_create(struct wait_list *sema) 22 | { 23 | struct test_tasklet *tt = malloc(sizeof *tt); 24 | 25 | mutex_init(&tt->mutex); 26 | tasklet_init(&tt->tasklet, &tt->mutex, tt); 27 | tt->sema = sema; 28 | tt->got = 0; 29 | 30 | mutex_lock(&tt->mutex); 31 | tasklet_goto(&tt->tasklet, test_tasklet_wait); 32 | mutex_unlock(&tt->mutex); 33 | 34 | return tt; 35 | } 36 | 37 | void test_tasklet_destroy(struct test_tasklet *tt) 38 | { 39 | mutex_lock(&tt->mutex); 40 | tasklet_fini(&tt->tasklet); 41 | mutex_unlock_fini(&tt->mutex); 42 | free(tt); 43 | } 44 | 45 | static void test_wait_list(void) 46 | { 47 | struct run_queue *runq = run_queue_create(); 48 | const int count = 3; 49 | struct test_tasklet **tts = malloc(count * sizeof *tts); 50 | struct wait_list sema; 51 | 52 | run_queue_target(runq); 53 | 54 | wait_list_init(&sema, 0); 55 | 56 | for (int i = 0; i < count; i++) 57 | tts[i] = test_tasklet_create(&sema); 58 | 59 | wait_list_broadcast(&sema); 60 | run_queue_run(runq, false); 61 | 62 | for (int i = 0; i < count; i++) { 63 | wait_list_up(&sema, 2); 64 | run_queue_run(runq, false); 65 | } 66 | 67 | int total_got = 0; 68 | for (int i = 0; i < count; i++) 69 | total_got += tts[i]->got; 70 | 71 | assert(total_got == count * 2); 72 | 73 | for (int i = 0; i < count; i++) 74 | test_tasklet_destroy(tts[i]); 75 | 76 | wait_list_fini(&sema); 77 | free(tts); 78 | run_queue_target(NULL); 79 | } 80 | 81 | /* Wait a millisecond */ 82 | static void delay(void) 83 | { 84 | struct timespec ts = {.tv_sec = 0, .tv_nsec = 1000000}; 85 | assert(!nanosleep(&ts, NULL)); 86 | } 87 | 88 | struct trqw { 89 | struct run_queue *runq; 90 | bool ran; 91 | 92 | struct mutex mutex; 93 | struct tasklet tasklet; 94 | }; 95 | 96 | static void test_run_queue_waiting_done(void *v_t) 97 | { 98 | struct trqw *t = v_t; 99 | 100 | t->ran = true; 101 | tasklet_fini(&t->tasklet); 102 | mutex_unlock_fini(&t->mutex); 103 | } 104 | 105 | static void test_run_queue_waiting_thread(void *v_t) 106 | { 107 | struct trqw *t = v_t; 108 | 109 | mutex_init(&t->mutex); 110 | tasklet_init(&t->tasklet, &t->mutex, t); 111 | 112 | run_queue_target(t->runq); 113 | delay(); 114 | tasklet_later(&t->tasklet, test_run_queue_waiting_done); 115 | } 116 | 117 | static void test_run_queue_waiting(void) 118 | { 119 | struct thread thr; 120 | struct trqw t = {.runq = run_queue_create(), .ran = false}; 121 | 122 | thread_init(&thr, test_run_queue_waiting_thread, &t); 123 | run_queue_run(t.runq, true); 124 | assert(t.ran); 125 | thread_fini(&thr); 126 | } 127 | 128 | int main(void) 129 | { 130 | test_wait_list(); 131 | test_run_queue_waiting(); 132 | return 0; 133 | } 134 | -------------------------------------------------------------------------------- /tests/test-threadpool.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "logger.h" 4 | #include "threadpool.h" 5 | #include "threadtracer.h" 6 | 7 | #define THREAD_NUM 4 8 | 9 | pthread_mutex_t lock; 10 | size_t sum = 0; 11 | 12 | static void sum_n(void *arg) 13 | { 14 | size_t n = (size_t) arg; 15 | 16 | TT_ENTRY(__func__); 17 | 18 | TT_BEGIN(__func__); 19 | int rc = pthread_mutex_lock(&lock); 20 | check_exit(rc == 0, "pthread_mutex_lock error"); 21 | 22 | sum += n; 23 | 24 | rc = pthread_mutex_unlock(&lock); 25 | check_exit(rc == 0, "pthread_mutex_unlock error"); 26 | TT_END(__func__); 27 | } 28 | 29 | int main() 30 | { 31 | check_exit(pthread_mutex_init(&lock, NULL) == 0, "lock init error"); 32 | 33 | threadpool_t *tp = threadpool_init(THREAD_NUM); 34 | check_exit(tp != NULL, "threadpool_init error"); 35 | 36 | for (size_t i = 1; i < 16; i++) { 37 | int rc = threadpool_add(tp, sum_n, (void *) i); 38 | check_exit(rc == 0, "threadpool_add error"); 39 | } 40 | 41 | check_exit(threadpool_destroy(tp, 1) == 0, "threadpool_destroy error"); 42 | 43 | check_exit(sum == 120, "sum error"); 44 | 45 | TT_REPORT(); 46 | return 0; 47 | } 48 | --------------------------------------------------------------------------------