diff --git a/Makefile b/Makefile index 442f685..32c0d56 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,10 @@ include common.mk all: server -server: $(BUILD_DIR)/server/main.o $(BUILD_DIR)/common/util/hash_table.o +$(BUILD_DIR)/common/util/thread.o: $(BUILD_DIR)/common/util/thread.posix.o + cp $< $@ + +server: $(BUILD_DIR)/server/main.o $(BUILD_DIR)/common/util/hash_table.o $(BUILD_DIR)/common/util/byte_stream.o $(BUILD_DIR)/common/util/thread.o $(COMPILE_EXE) run: server diff --git a/common.mk b/common.mk index 7735e53..0e4bc76 100644 --- a/common.mk +++ b/common.mk @@ -31,6 +31,7 @@ ifneq ($(strip $(DEPS)),) CPPFLAGS += $(shell $(PKGCONFIG) --cflags $(DEPS)) LDLIBS += $(shell $(PKGCONFIG) --libs $(DEPS)) endif +LDLIBS += -lpthread INCPATH += -iquote $(SRC_DIR) ENSURE_DIR = mkdir -p $(shell dirname "$@") COMPILE_EXE = $(CC) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@ diff --git a/src/common/util/closure.h b/src/common/util/closure.h new file mode 100644 index 0000000..400f4b7 --- /dev/null +++ b/src/common/util/closure.h @@ -0,0 +1,13 @@ +#ifndef COMMON_UTIL_CLOSURE_H_ +#define COMMON_UTIL_CLOSURE_H_ + +#include "common/defs.h" + +typedef struct { + void *opaque; +} ClosureEnvironment; + +#define CLOSURE_CALLBACK_FN(TRet, ...) typeof(TRet (ClosureEnvironment closure ,## __VA_ARGS__)) +#define CLOSURE_T(...) struct { CLOSURE_CALLBACK_FN(__VA_ARGS__) *callback; ClosureEnvironment env; } + +#endif /* end of include guard: COMMON_UTIL_CLOSURE_H_ */ diff --git a/src/common/util/thread.h b/src/common/util/thread.h new file mode 100644 index 0000000..cbbe00e --- /dev/null +++ b/src/common/util/thread.h @@ -0,0 +1,44 @@ +#ifndef COMMON_UTIL_THREAD_H_ +#define COMMON_UTIL_THREAD_H_ + +#include "common/defs.h" +#include "common/util/closure.h" + +typedef union { + uintptr_t handle; + void *opaque; +} Thread; + +typedef union { + uintptr_t value; + void *pointer; +} ThreadResult; + +#define THREAD_NONE (Thread) { .handle = 0, } + +typedef CLOSURE_T(ThreadResult) ThreadEntry; + +/** + * Creates and starts new thread + * @param entry main thread function + * @param error_result join result in case of invalid entry + * @result new thread + */ +Thread thread_spawn(ThreadEntry entry, ThreadResult error_result); + +/** + * Frees memory used to store thread handle (if allocated by the implementation) + * Usage of th is invalid after this + * @return THREAD_NONE + */ +Thread thread_delete_handle(Thread th); + +/** + * Waits for a thread to exit + * @param th the thread to join + * @param result_ptr pointer to store the exit result or NULL + * @return bool on success, false on failure (errno may be set) + */ +bool thread_join(Thread th, ThreadResult *result_ptr); + +#endif /* end of include guard: COMMON_UTIL_THREAD_H_ */ diff --git a/src/common/util/thread.posix.c b/src/common/util/thread.posix.c new file mode 100644 index 0000000..8c4ce9d --- /dev/null +++ b/src/common/util/thread.posix.c @@ -0,0 +1,115 @@ +#include "thread.h" + +#include +#include +#include + +typedef struct { + ThreadEntry entry; + ThreadResult error_result; +} NativeEntryArg; + +inline static pthread_t +get_handle(Thread wrapper) +{ + assert(wrapper.opaque != NULL); + return *(pthread_t*) wrapper.opaque; +} + +inline static bool +new_uninitialized(Thread *th) +{ + pthread_t *data = T_ALLOC(1, pthread_t); + if (!data) { + return false; + } + *th = (Thread) { .opaque = data }; + return true; +} + +inline static void +initialize_handle(Thread *th, pthread_t handle) +{ + assert(th != NULL); + assert(th->opaque != NULL); + *(pthread_t*) th->opaque = handle; + return; +} + +inline static Thread +delete_handle(Thread th) +{ + if (!th.opaque) { + return THREAD_NONE; + } + free(th.opaque); + return THREAD_NONE; +} + +static void* +run_entry(void *arg) +{ + assert(arg != NULL); + NativeEntryArg casted_arg = *(NativeEntryArg*) arg; + ThreadEntry entry = casted_arg.entry; + void *error_result = casted_arg.error_result.pointer; + free(arg); + if (!entry.callback) { + return error_result; + } + ThreadResult result = entry.callback(entry.env); + return result.pointer; +} + +Thread +thread_spawn(ThreadEntry entry, ThreadResult error_result) +{ + NativeEntryArg *native_arg = T_ALLOC(1, NativeEntryArg); + if (!native_arg) { + return THREAD_NONE; + } + *native_arg = (NativeEntryArg) { + .entry = entry, + .error_result = error_result, + }; + Thread th; + pthread_t handle; + int error; + if (!new_uninitialized(&th)) { + return THREAD_NONE; + } + error = pthread_create(&handle, NULL, &run_entry, native_arg); + if (error) { + errno = error; + return delete_handle(th); + } + assert(handle && "System allows 0 as thread id"); + initialize_handle(&th, handle); + return th; +} + +Thread +thread_delete_handle(Thread th) +{ + return delete_handle(th); +} + +bool +thread_join(Thread th, ThreadResult *result_ptr) +{ + if (!th.opaque) { + errno = EINVAL; + return false; + } + pthread_t handle = get_handle(th); + void **retval = NULL; + if (result_ptr) { + retval = &result_ptr->pointer; + } + int error = pthread_join(handle, retval); + if (error) { + errno = error; + return false; + } + return true; +} diff --git a/src/server/main.c b/src/server/main.c index 1d09335..4daadc4 100644 --- a/src/server/main.c +++ b/src/server/main.c @@ -3,6 +3,7 @@ #include #include "common/util/hash_table.h" #include "common/util/byte_serdes.h" +#include "common/util/thread.h" int main(int argc, char **argv)