aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--prototypes/connections/state.h5
-rw-r--r--prototypes/connections/store-tree.c9
-rw-r--r--prototypes/connections/test-tree-store.c37
3 files changed, 49 insertions, 2 deletions
diff --git a/prototypes/connections/state.h b/prototypes/connections/state.h
index d0b386c..461f71a 100644
--- a/prototypes/connections/state.h
+++ b/prototypes/connections/state.h
@@ -12,6 +12,9 @@
* appends only the draft's new extents. One owner thread per draft. */
typedef struct PureDraft PureDraft;
+/* A working value with no stored base, for runtime-owned state such as an
+ * app's text. It edits and reads like any draft but cannot be committed. */
+PureDraft *pure_draft_new(void);
/* A missing key opens as an empty draft at revision 0; commit creates it. */
PureResult pure_draft_open(const char *path, const char *key, PureDraft **out);
/* Memory only. Fails without change on a bad range or when memory runs out. */
@@ -32,6 +35,8 @@ PureResult pure_draft_read_version(const PureDraft *, PureVersion, size_t offset
bool (*visit)(void *, PureSpan), void *context);
/* While held, edits keep every earlier version's nodes (one transaction). */
void pure_draft_hold(PureDraft *, bool held);
+/* Abandon edits made since version was taken; the draft was held since then. */
+void pure_draft_restore(PureDraft *, PureVersion);
/* Publish if the key is still at the draft's base revision. On success the
* draft continues from the new revision; on any failure it is unchanged. */
PureResult pure_draft_commit(PureDraft *, int64_t *revision);
diff --git a/prototypes/connections/store-tree.c b/prototypes/connections/store-tree.c
index 74763ca..414c8ce 100644
--- a/prototypes/connections/store-tree.c
+++ b/prototypes/connections/store-tree.c
@@ -987,6 +987,10 @@ PureResult pure_draft_open(const char *path, const char *key, PureDraft **out) {
return PURE_OK;
}
+PureDraft *pure_draft_new(void) {
+ return calloc(1, sizeof(PureDraft)); /* no path: nothing mapped, nothing to commit */
+}
+
void pure_draft_close(PureDraft *draft) {
if (!draft) return;
release_tree(&draft->tree);
@@ -1066,6 +1070,10 @@ PureResult pure_draft_read(const PureDraft *draft, size_t offset, size_t length,
return pure_draft_read_version(draft, pure_draft_version(draft), offset, length, visit, context);
}
+void pure_draft_restore(PureDraft *draft, PureVersion version) {
+ draft->root = (Entry){version.ref, version.bytes};
+}
+
void pure_draft_hold(PureDraft *draft, bool held) {
draft->held = held;
if (!held) collect(draft);
@@ -1073,6 +1081,7 @@ void pure_draft_hold(PureDraft *draft, bool held) {
PureResult pure_draft_commit(PureDraft *draft, int64_t *revision) {
*revision = draft->revision;
+ if (!draft->path) return PURE_INVALID;
Session session;
Writer *writer = NULL;
Entry current, root = draft->root;
diff --git a/prototypes/connections/test-tree-store.c b/prototypes/connections/test-tree-store.c
index a9aea86..971abba 100644
--- a/prototypes/connections/test-tree-store.c
+++ b/prototypes/connections/test-tree-store.c
@@ -572,6 +572,39 @@ static void draft_versions(const char *path) {
pure_draft_close(draft);
}
+/* A draft without a store: edits, versions, restore and reclamation. */
+static void anonymous_draft(const char *path) {
+ (void)path;
+ Oracle o = {malloc(CAPACITY), 0, 0};
+ unsigned char *scratch = malloc(CAPACITY), insert[3000];
+ PureDraft *draft = pure_draft_new();
+ CHECK(draft && o.bytes && scratch && pure_draft_size(draft) == 0 && pure_draft_revision(draft) == 0);
+ uint32_t state = 42;
+ for (unsigned step = 0; step < 4000; ++step) {
+ size_t count = random_value(&state) % (step % 50 ? 12 : sizeof(insert));
+ size_t offset = o.size ? random_value(&state) % (o.size + 1) : 0;
+ size_t remove = o.size - offset ? random_value(&state) % (o.size - offset + 1) % 32 : 0;
+ fill(insert, count, &state);
+ bool abandon = random_value(&state) % 5 == 0;
+ pure_draft_hold(draft, true);
+ PureVersion before = pure_draft_version(draft);
+ CHECK(pure_draft_splice(draft, offset, remove, (PureSpan){insert, count}) == PURE_OK);
+ if (abandon) pure_draft_restore(draft, before);
+ else apply(&o, offset, remove, insert, count);
+ pure_draft_hold(draft, false);
+ if (step % 37 == 0) expect_draft(draft, &o, scratch);
+ }
+ expect_draft(draft, &o, scratch);
+ int64_t revision;
+ CHECK(pure_draft_commit(draft, &revision) == PURE_INVALID);
+ size_t nodes, bytes;
+ pure_draft_test_arena(draft, &nodes, &bytes);
+ CHECK(bytes < 2 * o.size + 65536);
+ pure_draft_close(draft);
+ free(o.bytes);
+ free(scratch);
+}
+
/* A failed commit changes neither the store nor the draft. */
static void draft_faults(const char *path) {
unsigned char scratch[64];
@@ -621,7 +654,7 @@ int main(void) {
void (*checks[])(const char *) = {oracle, amplification, faults, crashes, torn_head,
isolation, corruption, many_keys, revision_limit, keys,
drafts, typing, draft_conflict, draft_relocation, draft_faults,
- draft_versions};
+ draft_versions, anonymous_draft};
for (size_t i = 0; i < sizeof(checks) / sizeof(*checks); ++i) {
char path[] = "/tmp/pure-tree-check-XXXXXX";
CHECK(mkdtemp(path) && pure_store_init(path) == PURE_OK);
@@ -631,6 +664,6 @@ int main(void) {
}
puts("{\"tree_store\":\"random-oracle-balance-held-views-one-byte-amplification-faults-"
"process-crash-torn-head-lockless-isolation-compaction-corruption-1024-keys-revision-limit-key-listing-"
- "draft-oracle-typing-arena-conflict-relocation-commit-faults-held-versions\"}");
+ "draft-oracle-typing-arena-conflict-relocation-commit-faults-held-versions-anonymous\"}");
return 0;
}