334 lines
8.5 KiB
C
334 lines
8.5 KiB
C
|
|
/*
|
||
|
|
* DWN - Desktop Window Manager
|
||
|
|
* retoor <retoor@molodetz.nl>
|
||
|
|
* Future/Promise Implementation - Async Result Handling
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include "threading.h"
|
||
|
|
#include "util.h"
|
||
|
|
|
||
|
|
#include <errno.h>
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
/* ============================================================================
|
||
|
|
* Future Implementation
|
||
|
|
* ============================================================================ */
|
||
|
|
|
||
|
|
typedef struct FutureCallbackNode {
|
||
|
|
FutureCallback callback;
|
||
|
|
void *user_data;
|
||
|
|
struct FutureCallbackNode *next;
|
||
|
|
} FutureCallbackNode;
|
||
|
|
|
||
|
|
struct Future {
|
||
|
|
pthread_mutex_t mutex;
|
||
|
|
pthread_cond_t ready;
|
||
|
|
|
||
|
|
FutureResult result;
|
||
|
|
int error_code;
|
||
|
|
_Atomic int ready_flag;
|
||
|
|
_Atomic int has_result;
|
||
|
|
|
||
|
|
FutureCallbackNode *callbacks;
|
||
|
|
_Atomic int callback_count;
|
||
|
|
};
|
||
|
|
|
||
|
|
Future* future_create(void)
|
||
|
|
{
|
||
|
|
Future *f = dwn_malloc(sizeof(Future));
|
||
|
|
if (!f) return NULL;
|
||
|
|
|
||
|
|
atomic_store_explicit(&f->ready_flag, 0, ATOMIC_RELEASE);
|
||
|
|
atomic_store_explicit(&f->has_result, 0, ATOMIC_RELEASE);
|
||
|
|
atomic_store_explicit(&f->callback_count, 0, ATOMIC_RELAXED);
|
||
|
|
f->result = NULL;
|
||
|
|
f->error_code = 0;
|
||
|
|
f->callbacks = NULL;
|
||
|
|
|
||
|
|
if (pthread_mutex_init(&f->mutex, NULL) != 0) {
|
||
|
|
dwn_free(f);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (pthread_cond_init(&f->ready, NULL) != 0) {
|
||
|
|
pthread_mutex_destroy(&f->mutex);
|
||
|
|
dwn_free(f);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
return f;
|
||
|
|
}
|
||
|
|
|
||
|
|
void future_destroy(Future *f)
|
||
|
|
{
|
||
|
|
if (!f) return;
|
||
|
|
|
||
|
|
/* Free callback chain */
|
||
|
|
FutureCallbackNode *node = f->callbacks;
|
||
|
|
while (node) {
|
||
|
|
FutureCallbackNode *next = node->next;
|
||
|
|
dwn_free(node);
|
||
|
|
node = next;
|
||
|
|
}
|
||
|
|
|
||
|
|
pthread_cond_destroy(&f->ready);
|
||
|
|
pthread_mutex_destroy(&f->mutex);
|
||
|
|
dwn_free(f);
|
||
|
|
}
|
||
|
|
|
||
|
|
void future_set_result(Future *f, FutureResult result)
|
||
|
|
{
|
||
|
|
if (!f) return;
|
||
|
|
|
||
|
|
pthread_mutex_lock(&f->mutex);
|
||
|
|
|
||
|
|
if (atomic_load_explicit(&f->has_result, ATOMIC_ACQUIRE)) {
|
||
|
|
pthread_mutex_unlock(&f->mutex);
|
||
|
|
return; /* Already set */
|
||
|
|
}
|
||
|
|
|
||
|
|
f->result = result;
|
||
|
|
f->error_code = 0;
|
||
|
|
atomic_store_explicit(&f->has_result, 1, ATOMIC_RELEASE);
|
||
|
|
atomic_store_explicit(&f->ready_flag, 1, ATOMIC_RELEASE);
|
||
|
|
|
||
|
|
/* Execute callbacks */
|
||
|
|
FutureCallbackNode *node = f->callbacks;
|
||
|
|
while (node) {
|
||
|
|
node->callback(f, result, node->user_data);
|
||
|
|
node = node->next;
|
||
|
|
}
|
||
|
|
|
||
|
|
pthread_cond_broadcast(&f->ready);
|
||
|
|
pthread_mutex_unlock(&f->mutex);
|
||
|
|
}
|
||
|
|
|
||
|
|
void future_set_error(Future *f, int error_code)
|
||
|
|
{
|
||
|
|
if (!f) return;
|
||
|
|
|
||
|
|
pthread_mutex_lock(&f->mutex);
|
||
|
|
|
||
|
|
if (atomic_load_explicit(&f->has_result, ATOMIC_ACQUIRE)) {
|
||
|
|
pthread_mutex_unlock(&f->mutex);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
f->result = NULL;
|
||
|
|
f->error_code = error_code;
|
||
|
|
atomic_store_explicit(&f->has_result, 1, ATOMIC_RELEASE);
|
||
|
|
atomic_store_explicit(&f->ready_flag, 1, ATOMIC_RELEASE);
|
||
|
|
|
||
|
|
/* Execute callbacks with NULL result */
|
||
|
|
FutureCallbackNode *node = f->callbacks;
|
||
|
|
while (node) {
|
||
|
|
node->callback(f, NULL, node->user_data);
|
||
|
|
node = node->next;
|
||
|
|
}
|
||
|
|
|
||
|
|
pthread_cond_broadcast(&f->ready);
|
||
|
|
pthread_mutex_unlock(&f->mutex);
|
||
|
|
}
|
||
|
|
|
||
|
|
FutureResult future_get(Future *f, ThreadStatus *status)
|
||
|
|
{
|
||
|
|
return future_get_timeout(f, 0, status);
|
||
|
|
}
|
||
|
|
|
||
|
|
FutureResult future_get_timeout(Future *f, uint64_t timeout_ms, ThreadStatus *status)
|
||
|
|
{
|
||
|
|
if (!f) {
|
||
|
|
if (status) *status = THREAD_ERROR_INVALID;
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
pthread_mutex_lock(&f->mutex);
|
||
|
|
|
||
|
|
if (!atomic_load_explicit(&f->ready_flag, ATOMIC_ACQUIRE)) {
|
||
|
|
if (timeout_ms == 0) {
|
||
|
|
pthread_cond_wait(&f->ready, &f->mutex);
|
||
|
|
} else {
|
||
|
|
struct timespec ts;
|
||
|
|
clock_gettime(CLOCK_REALTIME, &ts);
|
||
|
|
ts.tv_sec += timeout_ms / 1000;
|
||
|
|
ts.tv_nsec += (timeout_ms % 1000) * 1000000;
|
||
|
|
if (ts.tv_nsec >= 1000000000) {
|
||
|
|
ts.tv_sec++;
|
||
|
|
ts.tv_nsec -= 1000000000;
|
||
|
|
}
|
||
|
|
|
||
|
|
int ret = pthread_cond_timedwait(&f->ready, &f->mutex, &ts);
|
||
|
|
if (ret == ETIMEDOUT) {
|
||
|
|
pthread_mutex_unlock(&f->mutex);
|
||
|
|
if (status) *status = THREAD_ERROR_TIMEOUT;
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
FutureResult result = f->result;
|
||
|
|
if (status) {
|
||
|
|
*status = (f->error_code != 0) ? THREAD_ERROR : THREAD_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
pthread_mutex_unlock(&f->mutex);
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool future_is_ready(Future *f)
|
||
|
|
{
|
||
|
|
if (!f) return false;
|
||
|
|
return atomic_load_explicit(&f->ready_flag, ATOMIC_ACQUIRE) != 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
void future_then(Future *f, FutureCallback callback, void *user_data)
|
||
|
|
{
|
||
|
|
if (!f || !callback) return;
|
||
|
|
|
||
|
|
pthread_mutex_lock(&f->mutex);
|
||
|
|
|
||
|
|
/* If already ready, execute immediately */
|
||
|
|
if (atomic_load_explicit(&f->ready_flag, ATOMIC_ACQUIRE)) {
|
||
|
|
pthread_mutex_unlock(&f->mutex);
|
||
|
|
callback(f, f->result, user_data);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Add to callback chain */
|
||
|
|
FutureCallbackNode *node = dwn_malloc(sizeof(FutureCallbackNode));
|
||
|
|
if (!node) {
|
||
|
|
pthread_mutex_unlock(&f->mutex);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
node->callback = callback;
|
||
|
|
node->user_data = user_data;
|
||
|
|
node->next = f->callbacks;
|
||
|
|
f->callbacks = node;
|
||
|
|
|
||
|
|
atomic_fetch_add_explicit(&f->callback_count, 1, ATOMIC_RELAXED);
|
||
|
|
|
||
|
|
pthread_mutex_unlock(&f->mutex);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ============================================================================
|
||
|
|
* Composite Futures
|
||
|
|
* ============================================================================ */
|
||
|
|
|
||
|
|
typedef struct {
|
||
|
|
Future *composite;
|
||
|
|
_Atomic int remaining;
|
||
|
|
uint32_t total;
|
||
|
|
pthread_mutex_t mutex;
|
||
|
|
} CompositeFutureData;
|
||
|
|
|
||
|
|
static void all_callback(Future *f, FutureResult result, void *user_data)
|
||
|
|
{
|
||
|
|
(void)f;
|
||
|
|
(void)result;
|
||
|
|
|
||
|
|
CompositeFutureData *data = user_data;
|
||
|
|
|
||
|
|
int rem = atomic_fetch_sub_explicit(&data->remaining, 1, ATOMIC_ACQ_REL) - 1;
|
||
|
|
|
||
|
|
if (rem == 0) {
|
||
|
|
future_set_result(data->composite, (void*)(uintptr_t)data->total);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Future* future_all(Future **futures, uint32_t count)
|
||
|
|
{
|
||
|
|
if (!futures || count == 0) return NULL;
|
||
|
|
|
||
|
|
Future *composite = future_create();
|
||
|
|
if (!composite) return NULL;
|
||
|
|
|
||
|
|
if (count == 1) {
|
||
|
|
/* Optimize single future */
|
||
|
|
FutureResult r = future_get(futures[0], NULL);
|
||
|
|
future_set_result(composite, r);
|
||
|
|
return composite;
|
||
|
|
}
|
||
|
|
|
||
|
|
CompositeFutureData *data = dwn_malloc(sizeof(CompositeFutureData));
|
||
|
|
if (!data) {
|
||
|
|
future_destroy(composite);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
data->composite = composite;
|
||
|
|
atomic_store_explicit(&data->remaining, count, ATOMIC_RELEASE);
|
||
|
|
data->total = count;
|
||
|
|
pthread_mutex_init(&data->mutex, NULL);
|
||
|
|
|
||
|
|
/* Attach callback to each future */
|
||
|
|
for (uint32_t i = 0; i < count; i++) {
|
||
|
|
if (future_is_ready(futures[i])) {
|
||
|
|
/* Already done */
|
||
|
|
if (atomic_fetch_sub_explicit(&data->remaining, 1, ATOMIC_ACQ_REL) - 1 == 0) {
|
||
|
|
future_set_result(composite, (void*)(uintptr_t)count);
|
||
|
|
pthread_mutex_destroy(&data->mutex);
|
||
|
|
dwn_free(data);
|
||
|
|
return composite;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
future_then(futures[i], all_callback, data);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return composite;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void any_callback(Future *f, FutureResult result, void *user_data)
|
||
|
|
{
|
||
|
|
(void)f;
|
||
|
|
|
||
|
|
CompositeFutureData *data = user_data;
|
||
|
|
|
||
|
|
pthread_mutex_lock(&data->mutex);
|
||
|
|
|
||
|
|
if (!future_is_ready(data->composite)) {
|
||
|
|
future_set_result(data->composite, result);
|
||
|
|
}
|
||
|
|
|
||
|
|
pthread_mutex_unlock(&data->mutex);
|
||
|
|
}
|
||
|
|
|
||
|
|
Future* future_any(Future **futures, uint32_t count)
|
||
|
|
{
|
||
|
|
if (!futures || count == 0) return NULL;
|
||
|
|
|
||
|
|
Future *composite = future_create();
|
||
|
|
if (!composite) return NULL;
|
||
|
|
|
||
|
|
if (count == 1) {
|
||
|
|
FutureResult r = future_get(futures[0], NULL);
|
||
|
|
future_set_result(composite, r);
|
||
|
|
return composite;
|
||
|
|
}
|
||
|
|
|
||
|
|
CompositeFutureData *data = dwn_malloc(sizeof(CompositeFutureData));
|
||
|
|
if (!data) {
|
||
|
|
future_destroy(composite);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
data->composite = composite;
|
||
|
|
pthread_mutex_init(&data->mutex, NULL);
|
||
|
|
|
||
|
|
/* Attach callback to each future */
|
||
|
|
for (uint32_t i = 0; i < count; i++) {
|
||
|
|
if (future_is_ready(futures[i])) {
|
||
|
|
future_set_result(composite, futures[i]->result);
|
||
|
|
pthread_mutex_destroy(&data->mutex);
|
||
|
|
dwn_free(data);
|
||
|
|
return composite;
|
||
|
|
} else {
|
||
|
|
future_then(futures[i], any_callback, data);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return composite;
|
||
|
|
}
|