/*
* 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;
}