aboutsummaryrefslogtreecommitdiffstats
path: root/prototypes/connections/test-async.c
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-24 17:14:54 +0100
committereunhhu <[email protected]>2026-09-24 17:14:54 +0100
commitb1ce1927022d374198441afafeb27199fc8a052a (patch)
tree6021f175f4c67ed03b44240eaa2f90d846f12f6f /prototypes/connections/test-async.c
parentd34f9426d5317a1948f12f229439946728bc342a (diff)
downloadpure-b1ce1927022d374198441afafeb27199fc8a052a.tar.gz
pure-b1ce1927022d374198441afafeb27199fc8a052a.zip
Establish native micro runtime checkpoint
Diffstat (limited to 'prototypes/connections/test-async.c')
-rw-r--r--prototypes/connections/test-async.c330
1 files changed, 330 insertions, 0 deletions
diff --git a/prototypes/connections/test-async.c b/prototypes/connections/test-async.c
new file mode 100644
index 0000000..6efaf0b
--- /dev/null
+++ b/prototypes/connections/test-async.c
@@ -0,0 +1,330 @@
+#define _POSIX_C_SOURCE 200809L
+#include "runtime.h"
+#include "store-port.h"
+#include "store.h"
+#include <dirent.h>
+#include <poll.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define CHECK(test) do { if (!(test)) { fprintf(stderr, "line %d: %s\n", __LINE__, #test); exit(1); } } while (0)
+
+static const PureProgram identity = {
+ .read = {.to_end = true}, .write = {.to_end = true},
+ .reply = {.to_end = true}, .reply_input = true
+};
+
+typedef struct {
+ PureEffect effect;
+ const PureProgram *definition;
+ const char *directory;
+ int start, done;
+ PureResult status;
+ PureSpan reply;
+ int64_t revision;
+} Worker;
+
+static void *write_after_signal(void *context) {
+ Worker *worker = context;
+ char byte = 0;
+ if (read(worker->start, &byte, 1) != 1) worker->status = PURE_STORE_ERROR;
+ else {
+ PureHost store = pure_store_host(worker->directory);
+ worker->status = store.write(store.context, worker->definition,
+ worker->effect.key, worker->effect.expected,
+ worker->effect.value, &worker->reply,
+ &worker->revision);
+ }
+ (void)write(worker->done, &byte, 1);
+ return NULL;
+}
+
+static PureHost async_host(const char *directory) {
+ PureHost host = pure_store_host(directory);
+ host.read = NULL;
+ host.write = NULL;
+ return host;
+}
+
+static PureTicket submit(PureRuntime *runtime, const char *key,
+ const void *body, size_t size) {
+ PureTicket ticket;
+ CHECK(pure_runtime_accept(runtime, PURE_INPUT_WRITE, key, 0, size, &ticket) == PURE_OK);
+ if (size) memcpy(pure_runtime_ingress(ticket, NULL), body, size);
+ CHECK(pure_runtime_ready(ticket));
+ return ticket;
+}
+
+static void expect(const char *directory, const char *key, const void *body, size_t size) {
+ PureStoredView view;
+ CHECK(pure_store_get(directory, key, 64, &view) == PURE_OK);
+ CHECK(view.body.size == size && !memcmp(view.body.data, body, size));
+ pure_store_release(&view);
+}
+
+static void worker_and_forward(const char *directory) {
+ PureRuntime source, destination;
+ PureWork source_slots[2], destination_slot[1];
+ CHECK(pure_runtime_init(&source, source_slots, 2, &identity,
+ async_host(directory), 64));
+ PureTicket ignored = {0};
+ CHECK(pure_runtime_accept(&source, PURE_INPUT_WRITE, "denied", 0, 1,
+ &ignored) == PURE_DENIED);
+ CHECK(pure_runtime_enable_async(&source));
+ PureTicket slow = submit(&source, "slow", "alpha", 5);
+ PureTicket fast = submit(&source, "fast", "beta", 4);
+ CHECK(!pure_runtime_step(&source)); /* No synchronous host callback. */
+ PureEffect deferred;
+ CHECK(pure_runtime_take(&source, &deferred) == PURE_TAKE_EFFECT);
+ CHECK(deferred.ticket.generation == slow.generation &&
+ deferred.value.size == 5 && !memcmp(deferred.value.data, "alpha", 5));
+ CHECK(!pure_runtime_cancel(slow));
+ int start_pipe[2], done_pipe[2];
+ CHECK(!pipe(start_pipe) && !pipe(done_pipe));
+ Worker worker = {.effect = deferred, .definition = &identity, .directory = directory,
+ .start = start_pipe[0], .done = done_pipe[1]};
+ pthread_t thread;
+ CHECK(!pthread_create(&thread, NULL, write_after_signal, &worker));
+ CHECK(pure_runtime_release(slow));
+ CHECK(source.active == 2 && source.retained == 9);
+ PureEffect immediate;
+ CHECK(pure_runtime_take(&source, &immediate) == PURE_TAKE_EFFECT);
+ int64_t revision = 0;
+ PureHost store = pure_store_host(directory);
+ PureSpan reply = {0};
+ PureResult written = store.write(store.context, &identity, immediate.key,
+ immediate.expected, immediate.value, &reply, &revision);
+ CHECK(pure_runtime_complete(immediate.ticket, written, NULL, reply, revision));
+ PureOutcome result;
+ CHECK(pure_runtime_result(fast, &result) && result.status == PURE_OK &&
+ result.body.size == 4 && !memcmp(result.body.data, "beta", 4));
+ CHECK(pure_runtime_release(fast));
+ expect(directory, "fast", "beta", 4);
+ PureStoredView absent;
+ CHECK(pure_store_get(directory, "slow", 64, &absent) == PURE_NOT_FOUND);
+ pure_runtime_stop(&source);
+ CHECK(!pure_runtime_restart(&source, &identity, async_host(directory), 64));
+ CHECK(pure_runtime_accept(&source, PURE_INPUT_READ, "fast", 0, 0,
+ &ignored) == PURE_CLOSED);
+ CHECK(write(start_pipe[1], "x", 1) == 1);
+ struct pollfd done = {.fd = done_pipe[0], .events = POLLIN};
+ CHECK(poll(&done, 1, 5000) == 1 && (done.revents & POLLIN));
+ CHECK(!pthread_join(thread, NULL));
+ CHECK(pure_runtime_complete(deferred.ticket, worker.status, NULL,
+ worker.reply, worker.revision));
+ CHECK(!pure_runtime_complete(deferred.ticket, PURE_OK, NULL, (PureSpan){0}, 1));
+ CHECK(!pure_runtime_result(slow, &result)); /* Caller released before I/O ended. */
+ CHECK(source.active == 0 && source.retained == 0 && source.commits == 2);
+ expect(directory, "slow", "alpha", 5);
+ CHECK(!close(start_pipe[0]) && !close(start_pipe[1]) &&
+ !close(done_pipe[0]) && !close(done_pipe[1]));
+ CHECK(pure_runtime_restart(&source, &identity, async_host(directory), 64));
+
+ CHECK(pure_runtime_init(&destination, destination_slot, 1, &identity,
+ async_host(directory), 64));
+ CHECK(pure_runtime_enable_async(&destination));
+ PureTicket read_ticket;
+ CHECK(pure_runtime_accept(&source, PURE_INPUT_READ, "fast", 0, 0,
+ &read_ticket) == PURE_OK);
+ PureEffect read_effect;
+ CHECK(pure_runtime_take(&source, &read_effect) == PURE_TAKE_EFFECT &&
+ read_effect.input == PURE_INPUT_READ);
+ PureResource mapping = {0};
+ revision = 0;
+ PureResult read_status = store.read(store.context, &identity, read_effect.key,
+ read_effect.available, &mapping, &revision);
+ CHECK(pure_runtime_complete(read_effect.ticket, read_status, &mapping,
+ (PureSpan){0}, revision));
+ CHECK(!mapping.allocation);
+ CHECK(pure_runtime_result(read_ticket, &result) && result.status == PURE_OK &&
+ result.body.size == 4);
+ PureTicket forwarded;
+ CHECK(pure_runtime_forward(read_ticket, &destination, "copy", 0,
+ &forwarded) == PURE_OK);
+ const unsigned char *borrowed = result.body.data;
+ CHECK(pure_runtime_release(read_ticket));
+ CHECK(source.active == 1 && source.retained == 4);
+ PureEffect output;
+ CHECK(pure_runtime_take(&destination, &output) == PURE_TAKE_EFFECT);
+ CHECK(output.value.data == borrowed && output.value.size == 4);
+ revision = 0;
+ reply = (PureSpan){0};
+ written = store.write(store.context, &identity, output.key, output.expected,
+ output.value, &reply, &revision);
+ CHECK(pure_runtime_complete(output.ticket, written, NULL, reply, revision));
+ CHECK(pure_runtime_result(forwarded, &result) && result.status == PURE_OK &&
+ result.body.data == borrowed);
+ CHECK(pure_runtime_release(forwarded));
+ CHECK(source.active == 0 && source.retained == 0 &&
+ destination.active == 0 && destination.retained == 0);
+ expect(directory, "copy", "beta", 4);
+}
+
+static void rejection_and_budget(const char *directory) {
+ PureProgram invalid = identity;
+ invalid.input_required = 4;
+ PureRuntime runtime;
+ PureWork slots[2];
+ CHECK(pure_runtime_init(&runtime, slots, 2, &invalid,
+ async_host(directory), 6));
+ CHECK(pure_runtime_enable_async(&runtime));
+ PureTicket rejected = submit(&runtime, "bad", "x", 1);
+ PureEffect effect;
+ CHECK(pure_runtime_take(&runtime, &effect) == PURE_TAKE_EFFECT);
+ PureHost store = pure_store_host(directory);
+ PureSpan reply = {0};
+ int64_t revision = 0;
+ PureResult status = store.write(store.context, &invalid, effect.key,
+ effect.expected, effect.value, &reply, &revision);
+ CHECK(status == PURE_LIMIT);
+ CHECK(pure_runtime_complete(effect.ticket, status, NULL, reply, revision));
+ PureOutcome result;
+ CHECK(pure_runtime_result(rejected, &result) && result.status == PURE_LIMIT);
+ CHECK(!pure_runtime_complete(rejected, PURE_OK, NULL, (PureSpan){0}, 1));
+ CHECK(pure_runtime_release(rejected));
+ CHECK(runtime.active == 0 && runtime.retained == 0);
+
+ CHECK(pure_runtime_restart(&runtime, &identity, async_host(directory), 6));
+ PureTicket cancelled = submit(&runtime, "unused", "x", 1);
+ CHECK(pure_runtime_cancel(cancelled));
+ CHECK(pure_runtime_result(cancelled, &result) && result.status == PURE_CANCELLED);
+ CHECK(pure_runtime_release(cancelled));
+ PureTicket invalid_read;
+ CHECK(pure_runtime_accept(&runtime, PURE_INPUT_READ, "fast", 0, 0,
+ &invalid_read) == PURE_OK);
+ PureEffect invalid_effect;
+ CHECK(pure_runtime_take(&runtime, &invalid_effect) == PURE_TAKE_EFFECT);
+ PureResource foreign = {.body = {(const unsigned char *)"x", 1}};
+ CHECK(!pure_runtime_complete(invalid_effect.ticket, PURE_OK, &foreign,
+ (PureSpan){0}, 1));
+ CHECK(foreign.body.size == 1 && runtime.active == 1);
+ CHECK(pure_runtime_complete(invalid_effect.ticket, PURE_STORE_ERROR, NULL,
+ (PureSpan){0}, 0));
+ CHECK(pure_runtime_release(invalid_read));
+ PureTicket bad_reply = submit(&runtime, "bad_reply", "x", 1);
+ PureEffect bad_effect;
+ CHECK(pure_runtime_take(&runtime, &bad_effect) == PURE_TAKE_EFFECT);
+ CHECK(!pure_runtime_complete(bad_effect.ticket, PURE_OK, NULL,
+ (PureSpan){(const unsigned char *)"outside", 7}, 1));
+ CHECK(runtime.active == 1 && runtime.retained == 1);
+ CHECK(pure_runtime_complete(bad_effect.ticket, PURE_STORE_ERROR, NULL,
+ (PureSpan){0}, 0));
+ CHECK(pure_runtime_release(bad_reply) && runtime.active == 0 && runtime.retained == 0);
+ PureTicket first, second;
+ CHECK(pure_runtime_accept(&runtime, PURE_INPUT_READ, "fast", 0, 0, &first) == PURE_OK);
+ CHECK(pure_runtime_accept(&runtime, PURE_INPUT_READ, "fast", 0, 0, &second) == PURE_OK);
+ PureEffect one, two;
+ CHECK(pure_runtime_take(&runtime, &one) == PURE_TAKE_EFFECT);
+ CHECK(pure_runtime_take(&runtime, &two) == PURE_TAKE_EFFECT);
+ CHECK(pure_runtime_take(&runtime, &effect) == PURE_TAKE_EMPTY);
+ PureResource a = {0}, b = {0};
+ int64_t revision_a = 0, revision_b = 0;
+ CHECK(store.read(store.context, &identity, one.key, one.available,
+ &a, &revision_a) == PURE_OK);
+ CHECK(store.read(store.context, &identity, two.key, two.available,
+ &b, &revision_b) == PURE_OK);
+ CHECK(pure_runtime_complete(two.ticket, PURE_OK, &b, (PureSpan){0}, revision_b));
+ CHECK(!b.allocation);
+ CHECK(pure_runtime_complete(one.ticket, PURE_OK, &a, (PureSpan){0}, revision_a));
+ CHECK(!a.allocation);
+ CHECK(pure_runtime_result(first, &result) && result.status == PURE_BUDGET);
+ CHECK(pure_runtime_result(second, &result) && result.status == PURE_OK);
+ CHECK(runtime.budget_rejections == 1 && runtime.retained == 4);
+ CHECK(pure_runtime_release(first) && pure_runtime_release(second));
+ CHECK(runtime.active == 0 && runtime.retained == 0);
+}
+
+static void authored_document(const char *directory) {
+ PureProgram program;
+ char error[160];
+ CHECK(pure_program_load("examples/editor.pure", &program, error, sizeof(error)));
+ PureRuntime runtime;
+ PureWork slot[1];
+ CHECK(pure_runtime_init(&runtime, slot, 1, &program,
+ async_host(directory), 64));
+ CHECK(pure_runtime_enable_async(&runtime));
+ const unsigned char create[] = {0,0,0,0, 0,0,0,0, 'H','i'};
+ PureTicket ticket = submit(&runtime, "document", create, sizeof(create));
+ PureEffect effect;
+ CHECK(pure_runtime_take(&runtime, &effect) == PURE_TAKE_EFFECT &&
+ effect.value.size == sizeof(create));
+ PureSpan value, reply = {0};
+ size_t offset, remove;
+ CHECK(pure_program_splice(&program, effect.value, &offset, &remove) &&
+ pure_program_input(&program, effect.value, &value, &reply) &&
+ offset == 0 && remove == 0 && value.size == 2 &&
+ !memcmp(value.data, "Hi", 2));
+ PureHost store = pure_store_host(directory);
+ int64_t revision = 0;
+ PureResult status = store.write(store.context, &program, effect.key,
+ effect.expected, effect.value, &reply, &revision);
+ CHECK(pure_runtime_complete(effect.ticket, status, NULL, reply, revision));
+ PureOutcome result;
+ CHECK(pure_runtime_result(ticket, &result) && result.status == PURE_OK &&
+ result.body.size == 2 && !memcmp(result.body.data, "Hi", 2));
+ CHECK(pure_runtime_release(ticket));
+
+ const unsigned char append[] = {2,0,0,0, 0,0,0,0, '!'};
+ CHECK(pure_runtime_accept(&runtime, PURE_INPUT_WRITE, "document", 1,
+ sizeof(append), &ticket) == PURE_OK);
+ memcpy(pure_runtime_ingress(ticket, NULL), append, sizeof(append));
+ CHECK(pure_runtime_ready(ticket));
+ CHECK(pure_runtime_take(&runtime, &effect) == PURE_TAKE_EFFECT &&
+ effect.value.size == sizeof(append));
+ CHECK(pure_program_splice(&program, effect.value, &offset, &remove) &&
+ offset == 2 && remove == 0);
+ reply = (PureSpan){0};
+ status = store.write(store.context, &program, effect.key, effect.expected,
+ effect.value, &reply, &revision);
+ CHECK(pure_runtime_complete(effect.ticket, status, NULL, reply, revision));
+ CHECK(pure_runtime_result(ticket, &result) && result.status == PURE_OK &&
+ result.body.size == 1 && result.body.data[0] == '!');
+ CHECK(pure_runtime_release(ticket));
+ expect(directory, "document", "Hi!", 3);
+
+ ticket = submit(&runtime, "document", append, sizeof(append));
+ CHECK(pure_runtime_take(&runtime, &effect) == PURE_TAKE_EFFECT);
+ reply = (PureSpan){0};
+ status = store.write(store.context, &program, effect.key, effect.expected,
+ effect.value, &reply, &revision);
+ CHECK(pure_runtime_complete(effect.ticket, status, NULL, reply, revision));
+ CHECK(pure_runtime_result(ticket, &result) && result.status == PURE_CONFLICT &&
+ !result.body.size);
+ CHECK(pure_runtime_release(ticket));
+ expect(directory, "document", "Hi!", 3);
+
+ CHECK(pure_runtime_accept(&runtime, PURE_INPUT_READ, "document", 0, 0,
+ &ticket) == PURE_OK);
+ CHECK(pure_runtime_take(&runtime, &effect) == PURE_TAKE_EFFECT);
+ PureResource mapping = {0};
+ status = store.read(store.context, &program, effect.key,
+ effect.available, &mapping, &revision);
+ CHECK(pure_runtime_complete(effect.ticket, status, &mapping,
+ (PureSpan){0}, revision));
+ CHECK(pure_runtime_result(ticket, &result) && result.status == PURE_OK &&
+ result.body.size == 3 && !memcmp(result.body.data, "Hi!", 3));
+ CHECK(pure_runtime_release(ticket));
+ CHECK(runtime.active == 0 && runtime.retained == 0 &&
+ runtime.commits == 2 && runtime.conflicts == 1 && runtime.gets == 1);
+}
+
+int main(void) {
+ char template[] = "/tmp/pure-async-effects-XXXXXX";
+ char *directory = mkdtemp(template);
+ CHECK(directory && pure_store_init(directory) == PURE_OK);
+ worker_and_forward(directory);
+ rejection_and_budget(directory);
+ authored_document(directory);
+ DIR *opened = opendir(directory);
+ CHECK(opened);
+ struct dirent *entry;
+ while ((entry = readdir(opened))) {
+ if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
+ CHECK(unlinkat(dirfd(opened), entry->d_name, 0) == 0);
+ }
+ CHECK(!closedir(opened) && !rmdir(directory));
+ puts("{\"async_effects\":\"authored-document-worker-overlap-release-forward-budget-recovery\"}");
+}