Skip to content

Commit

Permalink
promise: add promise paradigm to track success or error from asynchro…
Browse files Browse the repository at this point in the history
…nous operations
  • Loading branch information
philip-peterson committed Feb 5, 2024
1 parent 1a87c84 commit dea1420
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 0 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,7 @@ LIB_OBJS += preload-index.o
LIB_OBJS += pretty.o
LIB_OBJS += prio-queue.o
LIB_OBJS += progress.o
LIB_OBJS += promise.o
LIB_OBJS += promisor-remote.o
LIB_OBJS += prompt.o
LIB_OBJS += protocol.o
Expand Down
89 changes: 89 additions & 0 deletions promise.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Generic implementation of callbacks with await checking.
*/
#include "promise.h"

void promise_assert_finished(struct promise_t *p) {
if (p->state == PROMISE_UNRESOLVED) {
BUG("expected promise to have been resolved/rejected");
}
}

void promise_assert_failure(struct promise_t *p) {
if (p->state != PROMISE_FAILURE) {
BUG("expected promise to have been rejected");
}
}

void promise_resolve(struct promise_t *p, int status) {
if (p->state != PROMISE_UNRESOLVED) {
BUG("promise was already resolved/rejected");
return;
}
p->result.success_result = status;
p->state = PROMISE_SUCCESS;
}

void promise_reject(struct promise_t *p, int status, const char* fmt, ...) {
va_list args;
if (p->state != PROMISE_UNRESOLVED) {
BUG("promise was already resolved/rejected");
return;
}
p->result.failure_result.status = status;

strbuf_init(&p->result.failure_result.message, 0);

va_start(args, fmt);
strbuf_vaddf(&p->result.failure_result.message, fmt, args);
va_end(args);

p->state = PROMISE_FAILURE;
}

struct promise_t *promise_init(void) {
// Promises are allocated on the heap, because they represent potentially long-running tasks,
// and a stack-allocated value might not live long enough.
struct promise_t *new_promise = xmalloc(sizeof(struct promise_t));
struct failure_result_t failure_result;

new_promise->state = PROMISE_UNRESOLVED;
failure_result.status = 0;
new_promise->result.failure_result = failure_result;

return new_promise;
}

/**
* Outputs an error message and size from a failed promise. The error message must be
* free()'ed by the caller. Calling this function is not allowed if the promise is not
* failed.
*
* Argument `size` may be omitted by passing in NULL.
*
* Note that although *error_message is null-terminated, its size may be larger
* than the terminated string, and its actual size is indicated by *size.
*/
void promise_copy_error(struct promise_t *p, char **error_message, size_t *size) {
size_t local_size;
promise_assert_failure(p);

*error_message = strbuf_detach(&p->result.failure_result.message, &local_size);
if (size != NULL) {
*size = local_size;
}

// We are only doing a copy, not a consume, so we need to put the error message back
// the way we found it.
strbuf_add(&p->result.failure_result.message, *error_message, strlen(*error_message));
}

/**
* Fully deallocates the promise as well as the error message, if any.
*/
void promise_release(struct promise_t *p) {
if (p->state == PROMISE_FAILURE) {
strbuf_release(&p->result.failure_result.message);
}
free(p);
}
71 changes: 71 additions & 0 deletions promise.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#ifndef PROMISE_H
#define PROMISE_H

#include "git-compat-util.h"
#include "strbuf.h"

enum promise_state {
PROMISE_UNRESOLVED = 0,
PROMISE_SUCCESS = 1,
PROMISE_FAILURE = 2,
};

typedef int success_result_t;

struct failure_result_t {
int status;
struct strbuf message;
};

struct promise_t {
enum promise_state state;
union {
success_result_t success_result;
struct failure_result_t failure_result;
} result;
};

// Function to assert that a promise has been resolved
void promise_assert_finished(struct promise_t *p);

// Function to assert that a promise has been rejected
void promise_assert_failure(struct promise_t *p);

// Function to resolve a promise with a success result
void promise_resolve(struct promise_t *p, int status);

// Function to reject a promise with a failure result and an optional formatted error message
void promise_reject(struct promise_t *p, int status, const char* fmt, ...);

// Function to create a new promise
struct promise_t *promise_init(void);

// Copies the error out of a failed promise
void promise_copy_error(struct promise_t *promise, char **error_message, size_t *size);

// Fully deallocates the promise
void promise_release(struct promise_t *promise);

#define PROMISE_SUCCEED(p, errcode) do { \
promise_resolve(p, errcode); \
return; \
} while (0)

#define PROMISE_THROW(p, errcode, ...) do { \
promise_reject(p, errcode, __VA_ARGS__); \
return; \
} while (0)

#define PROMISE_BUBBLE_UP(dst, src, ...) do { \
if (strlen(src->result.failure_result.message.buf) != 0) { \
strbuf_insertf(&src->result.failure_result.message, 0, "\n\t"); \
strbuf_insertf(&src->result.failure_result.message, 0, _("caused by:")); \
strbuf_insertf(&src->result.failure_result.message, 0, "\n"); \
strbuf_insertf(&src->result.failure_result.message, 0, __VA_ARGS__); \
} \
promise_reject(dst, src->result.failure_result.status, "%s", src->result.failure_result.message.buf); \
strbuf_release(&src->result.failure_result.message); \
return; \
} while (0)

#endif

0 comments on commit dea1420

Please sign in to comment.