问题
读值现有两条割裂路径,消费者(runtime/layout/扩展)被迫按后端手工分支:
| API |
语义 |
shm |
durable |
kvspaceGet(h,key,out,out_len) |
恒拷贝,调用方 kvspaceBytesFree |
malloc+memcpy(在零拷贝之上再抄一遍) |
malloc+memcpy(传输本身) |
kvspaceXvalueBodyPtr(h,key,resolve,out_head) |
零拷贝借用 body 指针,勿 free |
直指 mmap |
返回 NULL(unsupported) |
后果:
- 想吃 shm 零拷贝的消费者必须写
p=BodyPtr(...); if(!p){ Get(...); …; BytesFree(...);}——两套 free 语义、两条代码路径,易错。
- 反过来,图省事一律
kvspaceGet 的消费者,在 shm 上白白多一次 malloc+memcpy:kvlang#204 端到端实测该拷贝层 ≈35 ns/op,纯属可省。
BodyPtr 只给 body 指针,不给整条 XValue 长度;Get 给整条。返回形状也不统一。
目标
单一读路径:shm 走零拷贝借用、durable 走拷贝,二者同一调用签名 + 同一释放契约。消费者不再按后端分支,owned 标记决定是否 free,无脑调 Release。
设计:ownership-tagged value view
typedef struct {
const uint8_t *data; /* 整条 XValue(head+body);借用=指向 mmap,拥有=malloc buffer */
uint32_t len; /* 整条字节数 */
kvspaceHead_t head; /* 已解码;body = data + head.body_offset,长度 head.body_len */
uint8_t owned; /* 0=借用(勿 free,随槽失效) 1=拥有(须 Release) */
} kvspaceView;
/* 读视图:shm 借用零拷贝 / durable 拷贝,统一 owned 标记。0=成功。 */
int kvspaceGetView(void *h, const char *key, int resolve, kvspaceView *out);
/* owned→free,借用→no-op;调用方永远无脑调用,不看后端。 */
void kvspaceViewRelease(kvspaceView *v);
消费者代码从此后端无关:
kvspaceView v;
if (kvspaceGetView(h, "a", 0, &v) == 0) {
int64_t a = read_int64(v.data + v.head.body_offset);
/* ... use v.head / body ... */
kvspaceViewRelease(&v);
}
关键:纯前端组合,两后端 ABI 不变
GetView 复用现有符号,不新增后端符号——沿用 frontend.c 已有的「BodyPtr = shmget + DecodeHead」组合套路:
int kvspaceGetView(void *h, const char *key, int resolve, kvspaceView *out) {
if (!h || !key || !out) return 1;
kvspace_handle *x = H(h);
memset(out, 0, sizeof *out);
if (x->vt->shmget) { /* shm:借用 */
int32_t len = 0;
uint8_t *d = x->vt->shmget(x->backend, key, resolve, &len);
if (!d || len <= 0) return 1;
out->data = d; out->len = (uint32_t)len; out->owned = 0;
} else { /* durable:拷贝 */
uint8_t *d = NULL; uint32_t len = 0;
if (x->vt->get(x->backend, key, &d, &len) != 0 || !d || len == 0) return 1;
out->data = d; out->len = len; out->owned = 1;
}
return kvspaceDecodeHead(out->data, out->len, &out->head);
}
void kvspaceViewRelease(kvspaceView *v) {
if (v && v->owned && v->data) kvspaceBytesFree((uint8_t *)v->data, v->len);
if (v) { v->data = NULL; v->len = 0; }
}
kvspaceGet / kvspaceXvalueBodyPtr 保留(向后兼容),GetView 是二者的统一上位替代。
生命周期契约(借用 vs 拥有,须显式写死不可隐藏)
- 借用视图(owned=0,shm):有效期至该 key 的下一次
Set/Del/Cp 或 kvspaceClose;不得跨写留存。与 kvspace-c#13 的同尺寸就地覆写一致——后续 Set 可能就地改写这块借用字节,故借用视图对同槽写入非快照稳定。
- 拥有视图(owned=1,durable,或 shm 强制拷贝):有效期至
Release,是私有快照,免疫并发改写。
- 需要跨写稳定快照的调用方:从借用视图
memcpy 出去,或用下面的强制拷贝入口。
可选:shm 上强制拷贝
给需要快照语义的调用方一个旋钮,shm 上也走拷贝、owned=1:
int kvspaceGetViewOwned(void *h, const char *key, int resolve, kvspaceView *out);
/* shm:shmget 后 malloc+memcpy 成私有 buffer;durable:等同 GetView。 */
与约束的一致性
- 零进程内缓存:借用视图零驻留、零拷贝;durable 的拷贝是传输本身(每次调用的瞬态 buffer,随 Release 释放),非跨调用缓存。本设计不引入任何缓存。
- 多进程:借用视图只读共享 mmap;契约已声明「写后失效」,与 kvspace-c#13 的共享内存写语义对齐。
可选 phase 2:可写视图(统一零拷贝写)
对称地统一写路径——shm 就地写、durable 编码回传:
typedef struct { uint8_t *body; uint32_t body_cap; void *_impl; uint8_t owned; } kvspaceMutView;
int kvspaceGetMutView(void *h, const char *key, int resolve, uint32_t want_body_len, kvspaceMutView *out);
int kvspaceMutViewCommit(kvspaceMutView *v); /* shm:no-op(写即持久) durable:encode+Set */
void kvspaceMutViewAbort(kvspaceMutView *v);
shm 返回 mmap 内可写 body 指针(容量足够时就地写,commit 为 no-op);durable 返回临时缓冲,commit 时编码 + Set 落盘。此项较侵入,建议读视图先行、写视图后续单议。
开放点
关联
问题
读值现有两条割裂路径,消费者(runtime/layout/扩展)被迫按后端手工分支:
kvspaceGet(h,key,out,out_len)kvspaceBytesFreekvspaceXvalueBodyPtr(h,key,resolve,out_head)后果:
p=BodyPtr(...); if(!p){ Get(...); …; BytesFree(...);}——两套 free 语义、两条代码路径,易错。kvspaceGet的消费者,在 shm 上白白多一次malloc+memcpy:kvlang#204 端到端实测该拷贝层 ≈35 ns/op,纯属可省。BodyPtr只给 body 指针,不给整条 XValue 长度;Get给整条。返回形状也不统一。目标
单一读路径:shm 走零拷贝借用、durable 走拷贝,二者同一调用签名 + 同一释放契约。消费者不再按后端分支,
owned标记决定是否 free,无脑调 Release。设计:ownership-tagged value view
消费者代码从此后端无关:
关键:纯前端组合,两后端 ABI 不变
GetView复用现有符号,不新增后端符号——沿用frontend.c已有的「BodyPtr = shmget + DecodeHead」组合套路:kvspaceGet/kvspaceXvalueBodyPtr保留(向后兼容),GetView是二者的统一上位替代。生命周期契约(借用 vs 拥有,须显式写死不可隐藏)
Set/Del/Cp或kvspaceClose;不得跨写留存。与 kvspace-c#13 的同尺寸就地覆写一致——后续Set可能就地改写这块借用字节,故借用视图对同槽写入非快照稳定。Release,是私有快照,免疫并发改写。memcpy出去,或用下面的强制拷贝入口。可选:shm 上强制拷贝
给需要快照语义的调用方一个旋钮,shm 上也走拷贝、
owned=1:与约束的一致性
可选 phase 2:可写视图(统一零拷贝写)
对称地统一写路径——shm 就地写、durable 编码回传:
shm 返回 mmap 内可写 body 指针(容量足够时就地写,commit 为 no-op);durable 返回临时缓冲,commit 时编码 +
Set落盘。此项较侵入,建议读视图先行、写视图后续单议。开放点
resolve语义不对称:shmget有resolve参,durablekvspaceGet签名无——需确认 durable 是否恒 resolve,或补 resolve-capable get,否则GetView(resolve=1)在 durable 上语义待定。GetViewOwned是否纳入首版,还是留给需要快照的调用方自行memcpy。want_body_len与 durable 预填当前值的取舍。关联