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
148
src/lib/zarray.c
148
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,13 +353,16 @@ void* zarray_get(zarray_t* array, int idx)
|
|||
}
|
||||
|
||||
typedef struct {
|
||||
mylock_t lock;
|
||||
} xzarray_private_t;
|
||||
_Atomic _Bool inuse;
|
||||
void* data;
|
||||
} xzarray_item_t;
|
||||
|
||||
typedef struct {
|
||||
unsigned int inuse : 1;
|
||||
void* data;
|
||||
} xzarray_item_t;
|
||||
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;
|
||||
|
|
@ -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;
|
||||
a->data[idx].data = data;
|
||||
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];
|
||||
|
||||
data[i] = a->data[idx].data;
|
||||
/* 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;
|
||||
a->data[idx].data = data[i];
|
||||
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