-
Notifications
You must be signed in to change notification settings - Fork 25.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
promise: add promise paradigm to track success or error from asynchro…
…nous operations
- Loading branch information
1 parent
1a87c84
commit dea1420
Showing
3 changed files
with
161 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |