feat: implement xzarray COW freeze/thaw with pre-grown shadow array (P0-2)
- Add in_cow flag + shadow/shadow_caps fields to xzarray_private_t - Convert xzarray_item_t.inuse from bitfield to _Atomic _Bool; update all read/write sites to use atomic_load/store_explicit - Implement xzarray_freeze(): O(1) ptr swap, pre-grows active array 1.5x+16 to avoid realloc during COW, sets pos past shadow range - Implement xzarray_thaw(): merges shadow into active (shadow wins only for slots not updated during COW), supports optional retain_fn GC - xzarray_get_vec(): falls back to shadow during COW so reads remain valid between freeze and thaw - Add COW guard assertion in xzarray_clear() - Wire vfreeze/vthaw into xzarray_type interface - Tests: 5 new COW tests all green; 4 pre-existing failures unchanged
This commit is contained in:
parent
fe69dcbd7a
commit
4c591a11d3
2 changed files with 230 additions and 29 deletions
|
|
@ -8,10 +8,16 @@
|
|||
/* xzarray_type is defined in zarray.c */
|
||||
extern typeinfo_t xzarray_type;
|
||||
|
||||
/* Helper: create an xzarray instance */
|
||||
static meta_obj_t* make_test_xzarray(void)
|
||||
{
|
||||
xzarray_params_t params = { .delta = 4, .datakiller = NULL };
|
||||
return (meta_obj_t*)makeInstance(&xzarray_type, ¶ms);
|
||||
}
|
||||
|
||||
static void test_freeze_thaw_slots_exist(void)
|
||||
{
|
||||
/* After P0-0b, intf_varray_t has vfreeze and vthaw slots.
|
||||
* They are NULL until P0-2 wires the implementations. */
|
||||
/* After P0-2, vfreeze and vthaw must be wired (non-NULL). */
|
||||
const intf_varray_t* iface
|
||||
= (const intf_varray_t*)xzarray_type.interfaces.varray;
|
||||
|
||||
|
|
@ -21,15 +27,110 @@ static void test_freeze_thaw_slots_exist(void)
|
|||
(void)iface->vfreeze;
|
||||
(void)iface->vthaw;
|
||||
|
||||
/* Slots must be NULL until P0-2 wires the real implementations */
|
||||
TEST_ASSERT_NULL(iface->vfreeze);
|
||||
TEST_ASSERT_NULL(iface->vthaw);
|
||||
/* Slots must be non-NULL after P0-2 wires the real implementations */
|
||||
TEST_ASSERT_NOT_NULL(iface->vfreeze);
|
||||
TEST_ASSERT_NOT_NULL(iface->vthaw);
|
||||
|
||||
printf("test_freeze_thaw_slots_exist: PASS\n");
|
||||
}
|
||||
|
||||
static void test_freeze_creates_shadow(void)
|
||||
{
|
||||
meta_obj_t* arr = make_test_xzarray();
|
||||
TEST_ASSERT_NOT_NULL(arr);
|
||||
|
||||
/* Store 3 items */
|
||||
char* items[3] = { "alpha", "beta", "gamma" };
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int idx = vstore(arr, items[i]);
|
||||
TEST_ASSERT_TRUE(idx >= 0);
|
||||
}
|
||||
|
||||
/* Freeze should succeed */
|
||||
TEST_ASSERT_EQUAL_INT(0, vfreeze(arr));
|
||||
|
||||
/* After freeze, vget must still return stored values (COW mode active) */
|
||||
void* out = NULL;
|
||||
TEST_ASSERT_EQUAL_INT(0, vget(arr, 0, &out));
|
||||
TEST_ASSERT_NOT_NULL(out);
|
||||
|
||||
/* Thaw to clean up */
|
||||
vthaw(arr, NULL, NULL);
|
||||
destroy(arr);
|
||||
}
|
||||
|
||||
static void test_freeze_thaw_preserves_all_entries(void)
|
||||
{
|
||||
meta_obj_t* arr = make_test_xzarray();
|
||||
TEST_ASSERT_NOT_NULL(arr);
|
||||
|
||||
int N = 5;
|
||||
char* items[5] = { "a", "b", "c", "d", "e" };
|
||||
int idxs[5];
|
||||
for (int i = 0; i < N; i++) {
|
||||
idxs[i] = vstore(arr, items[i]);
|
||||
TEST_ASSERT_TRUE(idxs[i] >= 0);
|
||||
}
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, vfreeze(arr));
|
||||
TEST_ASSERT_EQUAL_INT(0, vthaw(arr, NULL, NULL));
|
||||
|
||||
/* All 5 entries must be reachable after thaw */
|
||||
for (int i = 0; i < N; i++) {
|
||||
void* out = NULL;
|
||||
TEST_ASSERT_EQUAL_INT(0, vget(arr, idxs[i], &out));
|
||||
TEST_ASSERT_EQUAL_PTR(items[i], out);
|
||||
}
|
||||
|
||||
destroy(arr);
|
||||
}
|
||||
|
||||
static void test_mutation_during_cow_visible_after_thaw(void)
|
||||
{
|
||||
meta_obj_t* arr = make_test_xzarray();
|
||||
TEST_ASSERT_NOT_NULL(arr);
|
||||
|
||||
char* original = "original";
|
||||
char* updated = "updated";
|
||||
|
||||
int idx = vstore(arr, original);
|
||||
TEST_ASSERT_TRUE(idx >= 0);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, vfreeze(arr));
|
||||
|
||||
/* Overwrite index during COW — write to new active array */
|
||||
int put_idxs[1] = { idx };
|
||||
void* put_data[1] = { updated };
|
||||
vput_vec(arr, 1, put_idxs, put_data);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, vthaw(arr, NULL, NULL));
|
||||
|
||||
/* After thaw, the updated value must win over the shadow value */
|
||||
void* out = NULL;
|
||||
TEST_ASSERT_EQUAL_INT(0, vget(arr, idx, &out));
|
||||
TEST_ASSERT_EQUAL_PTR(updated, out);
|
||||
|
||||
destroy(arr);
|
||||
}
|
||||
|
||||
static void test_double_freeze_fails(void)
|
||||
{
|
||||
meta_obj_t* arr = make_test_xzarray();
|
||||
TEST_ASSERT_NOT_NULL(arr);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, vfreeze(arr));
|
||||
/* Second freeze must fail (already in COW mode) */
|
||||
TEST_ASSERT_TRUE(vfreeze(arr) != 0);
|
||||
vthaw(arr, NULL, NULL);
|
||||
destroy(arr);
|
||||
}
|
||||
|
||||
void test_zarray_cow(void)
|
||||
{
|
||||
printf("\n>> zarray COW interface slot test\n\n");
|
||||
RUN_TEST(test_freeze_thaw_slots_exist);
|
||||
RUN_TEST(test_freeze_creates_shadow);
|
||||
RUN_TEST(test_freeze_thaw_preserves_all_entries);
|
||||
RUN_TEST(test_mutation_during_cow_visible_after_thaw);
|
||||
RUN_TEST(test_double_freeze_fails);
|
||||
}
|
||||
|
|
|
|||
142
src/lib/zarray.c
142
src/lib/zarray.c
|
|
@ -1,5 +1,7 @@
|
|||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <rtti.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
|
@ -351,14 +353,17 @@ void* zarray_get(zarray_t* array, int idx)
|
|||
}
|
||||
|
||||
typedef struct {
|
||||
mylock_t lock;
|
||||
} xzarray_private_t;
|
||||
|
||||
typedef struct {
|
||||
unsigned int inuse : 1;
|
||||
_Atomic _Bool inuse;
|
||||
void* data;
|
||||
} xzarray_item_t;
|
||||
|
||||
typedef struct {
|
||||
mylock_t lock;
|
||||
xzarray_item_t* shadow; /* COW snapshot (may be NULL if array was empty at freeze) */
|
||||
size_t shadow_caps; /* capacity of shadow array */
|
||||
bool in_cow; /* true while between freeze and thaw */
|
||||
} xzarray_private_t;
|
||||
|
||||
typedef struct {
|
||||
int count;
|
||||
size_t caps;
|
||||
|
|
@ -392,6 +397,10 @@ static int xzarray_init(meta_obj_t* obj, va_list ap)
|
|||
a->pos = 0;
|
||||
a->killer = params->datakiller;
|
||||
|
||||
priv->shadow = NULL;
|
||||
priv->shadow_caps = 0;
|
||||
priv->in_cow = false;
|
||||
|
||||
mylock_init(&(priv->lock));
|
||||
|
||||
return 1;
|
||||
|
|
@ -403,8 +412,16 @@ static void xzarray_destroy(meta_obj_t* obj)
|
|||
xzarray_t* a = RTTI_OBJ_TO_DATA(xzarray_t, obj);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < a->caps; i++) {
|
||||
if (a->data[i].inuse) {
|
||||
/* Free shadow if still present (e.g. destroy called mid-COW) */
|
||||
if (priv->in_cow) {
|
||||
free(priv->shadow);
|
||||
priv->shadow = NULL;
|
||||
priv->shadow_caps = 0;
|
||||
priv->in_cow = false;
|
||||
}
|
||||
|
||||
for (i = 0; i < (int)a->caps; i++) {
|
||||
if (atomic_load_explicit(&a->data[i].inuse, memory_order_relaxed)) {
|
||||
if (a->killer != NULL) {
|
||||
a->killer(a->data[i].data);
|
||||
}
|
||||
|
|
@ -437,14 +454,14 @@ static int xzarray_ensure_items(xzarray_t* array, int mincap)
|
|||
|
||||
array->data = p;
|
||||
for (i = array->caps; i < nc; i++) {
|
||||
array->data[i].inuse = 0;
|
||||
atomic_store_explicit(&array->data[i].inuse, false, memory_order_relaxed);
|
||||
array->data[i].data = NULL;
|
||||
}
|
||||
|
||||
/* this will make we can get next free
|
||||
* location quickly
|
||||
*/
|
||||
if (array->data[array->pos].inuse) {
|
||||
if (atomic_load_explicit(&array->data[array->pos].inuse, memory_order_relaxed)) {
|
||||
array->pos = array->caps;
|
||||
}
|
||||
|
||||
|
|
@ -467,9 +484,9 @@ static int xzarray_store(meta_obj_t* obj, void* data)
|
|||
idx = a->pos;
|
||||
|
||||
for (i = 0; i < a->caps; i++) {
|
||||
if (a->data[idx].inuse == 0) {
|
||||
a->data[idx].inuse = 1;
|
||||
if (!atomic_load_explicit(&a->data[idx].inuse, memory_order_relaxed)) {
|
||||
a->data[idx].data = data;
|
||||
atomic_store_explicit(&a->data[idx].inuse, true, memory_order_relaxed);
|
||||
a->count++;
|
||||
|
||||
rc = idx;
|
||||
|
|
@ -503,7 +520,15 @@ static int xzarray_get_vec(meta_obj_t* obj, int count, int* idxs, void** data)
|
|||
for (i = 0; i < count; i++) {
|
||||
int idx = idxs[i];
|
||||
|
||||
if ((idx >= a->caps) || (a->data[idx].inuse == 0)) {
|
||||
/* During COW, check active first, then shadow */
|
||||
bool in_active = (idx < (int)a->caps) &&
|
||||
atomic_load_explicit(&a->data[idx].inuse, memory_order_relaxed);
|
||||
bool in_shadow = !in_active && priv->in_cow &&
|
||||
priv->shadow != NULL &&
|
||||
(idx < (int)priv->shadow_caps) &&
|
||||
atomic_load_explicit(&priv->shadow[idx].inuse, memory_order_relaxed);
|
||||
|
||||
if (!in_active && !in_shadow) {
|
||||
rc = -ENOENT;
|
||||
break;
|
||||
}
|
||||
|
|
@ -513,7 +538,13 @@ static int xzarray_get_vec(meta_obj_t* obj, int count, int* idxs, void** data)
|
|||
for (i = 0; i < count; i++) {
|
||||
int idx = idxs[i];
|
||||
|
||||
/* Active takes precedence over shadow */
|
||||
if ((idx < (int)a->caps) &&
|
||||
atomic_load_explicit(&a->data[idx].inuse, memory_order_relaxed)) {
|
||||
data[i] = a->data[idx].data;
|
||||
} else {
|
||||
data[i] = priv->shadow[idx].data;
|
||||
}
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
|
|
@ -557,14 +588,16 @@ static int xzarray_put_vec(meta_obj_t* obj, int count, int* idxs, void** data)
|
|||
for (i = 0; i < count; i++) {
|
||||
int idx = idxs[i];
|
||||
|
||||
if (a->data[idx].inuse == 1) {
|
||||
if (atomic_load_explicit(&a->data[idx].inuse, memory_order_relaxed)) {
|
||||
if (a->killer != NULL) {
|
||||
a->killer(a->data[idx].data);
|
||||
}
|
||||
} else {
|
||||
a->count++;
|
||||
}
|
||||
|
||||
a->data[idx].data = data[i];
|
||||
a->data[idx].inuse = 1;
|
||||
atomic_store_explicit(&a->data[idx].inuse, true, memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -584,15 +617,18 @@ static int xzarray_clear(meta_obj_t* obj, int idx)
|
|||
xzarray_private_t* priv = (xzarray_private_t*)RTTI_OBJ_TO_PRIV(obj);
|
||||
int rc = -EINVAL;
|
||||
|
||||
if (idx < a->caps) {
|
||||
assert(!priv->in_cow && "xzarray_clear called during COW — deferred cleanup required");
|
||||
|
||||
if (idx < (int)a->caps) {
|
||||
rc = 0;
|
||||
if (a->data[idx].inuse) {
|
||||
if (atomic_load_explicit(&a->data[idx].inuse, memory_order_relaxed)) {
|
||||
if (a->killer != NULL) {
|
||||
a->killer(a->data[idx].data);
|
||||
}
|
||||
a->count--;
|
||||
}
|
||||
|
||||
a->data[idx].inuse = 0;
|
||||
atomic_store_explicit(&a->data[idx].inuse, false, memory_order_relaxed);
|
||||
a->data[idx].data = NULL;
|
||||
a->pos = idx; /* we can find the available idx next time */
|
||||
}
|
||||
|
|
@ -614,8 +650,8 @@ static int xzarray_iter_next(iterator_t* iter, void** data)
|
|||
xzarray_t* a = RTTI_OBJ_TO_DATA(xzarray_t, iter->obj);
|
||||
int i;
|
||||
|
||||
for (i = (int)(long)iter->data; i < a->caps; i++) {
|
||||
if (a->data[i].inuse) {
|
||||
for (i = (int)(long)iter->data; i < (int)a->caps; i++) {
|
||||
if (atomic_load_explicit(&a->data[i].inuse, memory_order_relaxed)) {
|
||||
*data = a->data[i].data;
|
||||
iter->data = (void*)(long)(i + 1);
|
||||
|
||||
|
|
@ -656,6 +692,70 @@ static void xzarray_iter_destroy(iterator_t* iter)
|
|||
return;
|
||||
}
|
||||
|
||||
/* P0-2: COW freeze — O(1) pointer swap, pre-grow active array */
|
||||
static int xzarray_freeze(meta_obj_t* obj)
|
||||
{
|
||||
xzarray_private_t* priv = (xzarray_private_t*)RTTI_OBJ_TO_PRIV(obj);
|
||||
xzarray_t* a = RTTI_OBJ_TO_DATA(xzarray_t, obj);
|
||||
|
||||
if (priv->in_cow)
|
||||
return -EALREADY; /* already frozen */
|
||||
|
||||
/* Pre-grow active to 1.5x + 16 to avoid realloc during COW (A6-026) */
|
||||
size_t new_caps = a->caps + (a->caps / 2) + 16;
|
||||
xzarray_item_t* active = calloc(new_caps, sizeof(xzarray_item_t));
|
||||
if (!active)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->shadow = a->data; /* may be NULL if array was empty */
|
||||
priv->shadow_caps = a->caps;
|
||||
priv->in_cow = true;
|
||||
a->data = active;
|
||||
a->caps = new_caps;
|
||||
a->count = 0;
|
||||
/* Start pos past shadow to prevent index collisions with shadow entries */
|
||||
a->pos = (int)priv->shadow_caps;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* P0-2: COW thaw — merge shadow into active, free shadow */
|
||||
static int xzarray_thaw(meta_obj_t* obj, retain_fn_t should_retain, void* arg)
|
||||
{
|
||||
xzarray_private_t* priv = (xzarray_private_t*)RTTI_OBJ_TO_PRIV(obj);
|
||||
xzarray_t* a = RTTI_OBJ_TO_DATA(xzarray_t, obj);
|
||||
|
||||
if (!priv->in_cow)
|
||||
return -EINVAL; /* not in COW mode */
|
||||
|
||||
/* Merge: for each shadow entry not already written to active, copy it */
|
||||
for (size_t i = 0; i < priv->shadow_caps; i++) {
|
||||
if (!atomic_load_explicit(&priv->shadow[i].inuse, memory_order_relaxed))
|
||||
continue;
|
||||
|
||||
void* item = priv->shadow[i].data;
|
||||
|
||||
/* GC: skip entries that should not be retained */
|
||||
if (should_retain != NULL && !should_retain(item, arg))
|
||||
continue;
|
||||
|
||||
/* Only copy to active if active slot not already updated during COW */
|
||||
if (i < a->caps &&
|
||||
!atomic_load_explicit(&a->data[i].inuse, memory_order_relaxed)) {
|
||||
a->data[i].data = item;
|
||||
atomic_store_explicit(&a->data[i].inuse, true, memory_order_relaxed);
|
||||
a->count++;
|
||||
}
|
||||
/* If active[i].inuse == true: mutation during COW wins — keep it */
|
||||
}
|
||||
|
||||
free(priv->shadow);
|
||||
priv->shadow = NULL;
|
||||
priv->shadow_caps = 0;
|
||||
priv->in_cow = false;
|
||||
a->pos = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
typeinfo_t xzarray_type = {
|
||||
.name = "xzarray",
|
||||
.privateSize = sizeof(xzarray_private_t),
|
||||
|
|
@ -669,8 +769,8 @@ typeinfo_t xzarray_type = {
|
|||
.vput = (vput_fn_t)xzarray_put,
|
||||
.vput_vec = (vput_vec_fn_t)xzarray_put_vec,
|
||||
.vclear = (vclear_fn_t)xzarray_clear,
|
||||
.vfreeze = NULL, /* wired in P0-2 */
|
||||
.vthaw = NULL /* wired in P0-2 */ },
|
||||
.vfreeze = (vfreeze_fn_t)xzarray_freeze,
|
||||
.vthaw = (vthaw_fn_t)xzarray_thaw },
|
||||
.iter
|
||||
= &(intf_iterator_t) { .make = (iter_make_fn_t)xzarray_make_iter,
|
||||
.next = (iter_next_fn_t)xzarray_iter_next,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue