git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
From: "Philip Peterson via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Johannes Schindelin <johannes.schindelin@gmx.de>,
	Emily Shaffer <nasamuffin@google.com>,
	Philip Peterson <philip.c.peterson@gmail.com>,
	Philip Peterson <philip.c.peterson@gmail.com>
Subject: [PATCH 1/5] promise: add promise pattern to track success/error from operations
Date: Sun, 18 Feb 2024 07:33:28 +0000	[thread overview]
Message-ID: <be270db2ff5c63612356c4ef2fafdbe1724b5b71.1708241613.git.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.1666.git.git.1708241612.gitgitgadget@gmail.com>

From: Philip Peterson <philip.c.peterson@gmail.com>

Introduce a promise paradigm. A promise starts off in the pending state,
and represents an asynchronous (or synchronous) action that will
eventually end in either a successful result or a failure result. If a
failure result, an error message may be provided.

This allows us to represent tasks which may fail, while deferring any
control flow actions or error printing that may occur in relation to
said task.

Signed-off-by: Philip Peterson <philip.c.peterson@gmail.com>
---
 Makefile  |  1 +
 promise.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 promise.h | 71 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 161 insertions(+)
 create mode 100644 promise.c
 create mode 100644 promise.h

diff --git a/Makefile b/Makefile
index 78e874099d9..4851eb2d822 100644
--- a/Makefile
+++ b/Makefile
@@ -1109,6 +1109,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
diff --git a/promise.c b/promise.c
new file mode 100644
index 00000000000..58ed8b67880
--- /dev/null
+++ b/promise.c
@@ -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) {
+		*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);
+}
diff --git a/promise.h b/promise.h
new file mode 100644
index 00000000000..c5500eba986
--- /dev/null
+++ b/promise.h
@@ -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); \
+	promise_release(src); \
+	return; \
+} while (0)
+
+#endif
-- 
gitgitgadget



  reply	other threads:[~2024-02-18  7:33 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-02-18  7:33 [PATCH 0/5] promise: introduce promises to track success or error Philip Peterson via GitGitGadget
2024-02-18  7:33 ` Philip Peterson via GitGitGadget [this message]
2024-02-18  7:33 ` [PATCH 2/5] apply: use new promise structures in git-apply logic as a proving ground Philip Peterson via GitGitGadget
2024-02-18  7:33 ` [PATCH 3/5] apply: update t4012 test suite Philip Peterson via GitGitGadget
2024-02-18  7:33 ` [PATCH 4/5] apply: pass through quiet flag to fix t4150 Philip Peterson via GitGitGadget
2024-02-18  7:33 ` [PATCH 5/5] am: update test t4254 by adding the new error text Philip Peterson via GitGitGadget
2024-02-19 14:25 ` [PATCH 0/5] promise: introduce promises to track success or error Phillip Wood
2024-02-20  2:57   ` Jeff King
2024-02-20 10:53     ` Phillip Wood
2024-02-20 12:19       ` Patrick Steinhardt
2024-02-20 16:20         ` Junio C Hamano
2024-02-21 18:03         ` Jeff King
2024-03-12  4:18           ` Philip

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: http://vger.kernel.org/majordomo-info.html

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=be270db2ff5c63612356c4ef2fafdbe1724b5b71.1708241613.git.gitgitgadget@gmail.com \
    --to=gitgitgadget@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=johannes.schindelin@gmx.de \
    --cc=nasamuffin@google.com \
    --cc=philip.c.peterson@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).