#include "../../include/std/thread.h" #include "../../include/std/log.h" #include #include #include #include #include #include #include #include static int thread_type_to_system_thread_type(amal_thread_type thread_type) { switch(thread_type) { case AMAL_THREAD_JOINABLE: return PTHREAD_CREATE_JOINABLE; case AMAL_THREAD_DETACHED: return PTHREAD_CREATE_DETACHED; } assert(bool_false); return PTHREAD_CREATE_JOINABLE; } int amal_thread_create(amal_thread *self, amal_thread_type thread_type, const char *name, AmalThreadCallbackFunc callback_func, void *userdata) { int result; self->name = name; self->thread_id = 0; self->cancellable = bool_false; self->destroyable = bool_false; if((result = pthread_attr_init(&self->thread_attr)) != 0) { perror("amal_thread_create"); return result; } self->destroyable = bool_true; if((result = pthread_attr_setdetachstate(&self->thread_attr, thread_type_to_system_thread_type(thread_type))) != 0) { perror("amal_thread_create"); return result; } if((result = pthread_create(&self->thread_id, NULL, callback_func, userdata)) != 0) { perror("amal_thread_create"); return result; } self->cancellable = bool_true; return 0; } int amal_thread_deinit(amal_thread *self) { int r1; int r2; r1 = 0; r2 = 0; if(self->cancellable) { r1 = pthread_cancel(self->thread_id); /* thread deinit on a thread that has not been started shouldn't be an error */ if(r1 == ESRCH) r1 = 0; if(r1 != 0) amal_log_error("amal_thread_deinit: failed to cancel thread, error: %s", strerror(r1)); self->cancellable = bool_false; } if(self->destroyable) { r2 = pthread_attr_destroy(&self->thread_attr); if(r2 != 0) amal_log_error("amal_thread_deinit: failed to destroy thread attributes, error: %s", strerror(r2)); self->destroyable = bool_false; } return r1 != 0 ? r1 : r2; } int amal_thread_detach(amal_thread *self) { int result_err; int thread_type; if((result_err = pthread_attr_getdetachstate(&self->thread_attr, &thread_type)) != 0) return result_err; if(thread_type != PTHREAD_CREATE_DETACHED) return AMAL_THREAD_ERR; if((result_err = pthread_detach(self->thread_id)) != 0) return result_err; self->cancellable = bool_false; return 0; } int amal_thread_join(amal_thread *self, void **result) { int result_err; int thread_type; if((result_err = pthread_attr_getdetachstate(&self->thread_attr, &thread_type)) != 0) { amal_log_error("amal_thread_join: failed to get detach state, error: %d", result_err); return result_err; } if(thread_type != PTHREAD_CREATE_JOINABLE) { amal_log_error("amal_thread_join: thread not joinable type"); return AMAL_THREAD_NOT_JOINABLE; } if((result_err = pthread_join(self->thread_id, result)) != 0) { /* Joining a thread that is not joinable (has already finished) shouldn't be an error */ if(result_err == ESRCH) goto cleanup; amal_log_error("amal_thread_join: failed to join thread %llu, error: %s", self->thread_id, strerror(result_err)); return result_err == EINVAL ? AMAL_THREAD_NOT_JOINABLE : result_err; } cleanup: self->cancellable = bool_false; return 0; } int amal_mutex_init(amal_mutex *self) { #ifdef AMAL_MUTEX_DEBUG self->lock_identifier = NULL; #endif self->locked = bool_false; self->owner_thread = 0; return pthread_mutex_init(&self->mutex, NULL); } void amal_mutex_deinit(amal_mutex *self) { pthread_mutex_destroy(&self->mutex); } static long amal_process_get_id(void) { return getpid(); } static long amal_thread_get_id(void) { return syscall(SYS_gettid); } int amal_mutex_lock(amal_mutex *self, const char *lock_identifier) { int result; result = pthread_mutex_lock(&self->mutex); self->owner_thread = pthread_self(); self->locked = bool_true; (void)lock_identifier; #ifdef AMAL_MUTEX_DEBUG self->lock_identifier = lock_identifier; if(result == 0 && self->lock_identifier) { amal_log_debug("amal_mutex_lock: mutex locked by thread %lu (%s), identification: %s", amal_thread_get_id(), amal_thread_is_main() ? "main" : "not main", self->lock_identifier ? self->lock_identifier : "none"); } #endif return result; } int amal_mutex_unlock(amal_mutex *self) { int result; #ifdef AMAL_MUTEX_DEBUG const char *identifier; identifier = self->lock_identifier; #endif /* No-op if mutex isn't locked or if another thread owns the mutex lock */ if(!self->locked || pthread_equal(self->owner_thread, pthread_self()) == 0) return 0; self->locked = bool_false; self->owner_thread = 0; result = pthread_mutex_unlock(&self->mutex); #ifdef AMAL_MUTEX_DEBUG if(result == 0 && identifier) { amal_log_debug("amal_mutex_unlock: mutex unlocked by thread %lu (%s), identification: %s", amal_thread_get_id(), amal_thread_is_main() ? "main" : "not main", identifier ? identifier : "none"); } #endif return result; } void amal_mutex_tryunlock(amal_mutex *self) { ignore_result_int(amal_mutex_unlock(self)); } bool amal_thread_is_main(void) { /* TODO: This only works for linux, use equivalent functions on other platforms */ return amal_thread_get_id() == amal_process_get_id(); } int amal_get_usable_thread_count(void) { return get_nprocs(); }