chainbuffer fixed

This commit is contained in:
2026-03-04 07:20:09 +00:00
parent 57720a3135
commit a190bdeea5
9 changed files with 1335 additions and 78 deletions

View File

@@ -1,48 +1,245 @@
#include "network/chainbuffer.h"
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/uio.h>
#define CHAINBUFFER_DEFAULT_CHUNK 4096
#define CHAINBUFFER_MAX_IOV 16
#define CHAINBUFFER_DEFAULT_FREE_LIMIT 256
struct chain_buffer_node {
struct chain_buffer_node *next;
size_t start;
size_t end;
size_t cap;
size_t rpos;
size_t wpos;
size_t used;
unsigned refcnt;
uint8_t data[];
};
static chain_buffer_node_t *alloc_node(size_t cap) {
chain_buffer_node_t *node = (chain_buffer_node_t *)malloc(sizeof(*node) + cap);
chain_buffer_node_t *node;
node = (chain_buffer_node_t *)malloc(sizeof(*node) + cap);
if (!node) {
return NULL;
}
node->next = NULL;
node->start = 0;
node->end = 0;
node->cap = cap;
node->rpos = 0;
node->wpos = 0;
node->used = 0;
node->refcnt = 0;
return node;
}
static void reset_node_state(chain_buffer_node_t *node) {
if (!node) {
return;
}
node->next = NULL;
node->rpos = 0;
node->wpos = 0;
node->used = 0;
node->refcnt = 0;
}
static size_t min_size(size_t a, size_t b) {
return a < b ? a : b;
}
static size_t node_read_seg1_len(const chain_buffer_node_t *node) {
size_t tail_len;
if (!node || node->used == 0) {
return 0;
}
tail_len = node->cap - node->rpos;
return min_size(node->used, tail_len);
}
static size_t node_write_seg1_len(const chain_buffer_node_t *node) {
size_t free_total;
size_t tail_len;
if (!node || node->used >= node->cap) {
return 0;
}
free_total = node->cap - node->used;
tail_len = node->cap - node->wpos;
return min_size(free_total, tail_len);
}
static void node_read_segments(const chain_buffer_node_t *node,
uint8_t **p1, size_t *l1,
uint8_t **p2, size_t *l2) {
size_t seg1;
size_t seg2;
seg1 = node_read_seg1_len(node);
seg2 = (node && node->used > seg1) ? (node->used - seg1) : 0;
if (p1) {
*p1 = (seg1 > 0) ? (uint8_t *)(node->data + node->rpos) : NULL;
}
if (l1) {
*l1 = seg1;
}
if (p2) {
*p2 = (seg2 > 0) ? (uint8_t *)node->data : NULL;
}
if (l2) {
*l2 = seg2;
}
}
static void node_write_segments(const chain_buffer_node_t *node,
uint8_t **p1, size_t *l1,
uint8_t **p2, size_t *l2) {
size_t free_total;
size_t seg1;
size_t seg2;
if (!node || node->used >= node->cap) {
if (p1) *p1 = NULL;
if (l1) *l1 = 0;
if (p2) *p2 = NULL;
if (l2) *l2 = 0;
return;
}
free_total = node->cap - node->used;
seg1 = node_write_seg1_len(node);
seg2 = free_total - seg1;
if (p1) {
*p1 = (seg1 > 0) ? (uint8_t *)(node->data + node->wpos) : NULL;
}
if (l1) {
*l1 = seg1;
}
if (p2) {
*p2 = (seg2 > 0) ? (uint8_t *)node->data : NULL;
}
if (l2) {
*l2 = seg2;
}
}
static void node_advance_read(chain_buffer_node_t *node, size_t n) {
if (!node || n == 0) {
return;
}
node->rpos = (node->rpos + n) % node->cap;
node->used -= n;
}
static void node_advance_write(chain_buffer_node_t *node, size_t n) {
if (!node || n == 0) {
return;
}
node->wpos = (node->wpos + n) % node->cap;
node->used += n;
}
static chain_buffer_node_t *acquire_node(chain_buffer_t *buf, size_t cap) {
chain_buffer_node_t *node = NULL;
if (!buf || cap == 0) {
return NULL;
}
if (cap == buf->chunk_size && buf->free_list) {
node = buf->free_list;
buf->free_list = node->next;
buf->free_count--;
reset_node_state(node);
return node;
}
node = alloc_node(cap);
return node;
}
static void recycle_node(chain_buffer_t *buf, chain_buffer_node_t *node) {
if (!node) {
return;
}
if (!buf || node->cap != buf->chunk_size || buf->free_count >= buf->free_limit) {
free(node);
return;
}
reset_node_state(node);
node->next = buf->free_list;
buf->free_list = node;
buf->free_count++;
}
static int append_new_tail(chain_buffer_t *buf, size_t cap) {
chain_buffer_node_t *node;
node = acquire_node(buf, cap);
if (!node) {
return -1;
}
if (!buf->tail) {
buf->head = node;
buf->tail = node;
} else {
buf->tail->next = node;
buf->tail = node;
}
return 0;
}
static void list_append_node(chain_buffer_list_t *list, chain_buffer_node_t *node) {
if (!list || !node) {
return;
}
node->next = NULL;
if (!list->tail) {
list->head = node;
list->tail = node;
} else {
list->tail->next = node;
list->tail = node;
}
}
void chain_buffer_init(chain_buffer_t *buf, size_t chunk_size) {
if (!buf) {
return;
}
memset(buf, 0, sizeof(*buf));
buf->chunk_size = chunk_size ? chunk_size : CHAINBUFFER_DEFAULT_CHUNK;
buf->free_limit = CHAINBUFFER_DEFAULT_FREE_LIMIT;
}
void chain_buffer_reset(chain_buffer_t *buf) {
chain_buffer_node_t *node;
if (!buf) {
return;
}
chain_buffer_node_t *node = buf->head;
node = buf->head;
while (node) {
chain_buffer_node_t *next = node->next;
free(node);
node = next;
}
node = buf->free_list;
while (node) {
chain_buffer_node_t *next = node->next;
free(node);
@@ -50,11 +247,7 @@ void chain_buffer_reset(chain_buffer_t *buf) {
}
free(buf->linear_cache);
buf->linear_cache = NULL;
buf->linear_cap = 0;
buf->head = NULL;
buf->tail = NULL;
buf->total_len = 0;
memset(buf, 0, sizeof(*buf));
}
size_t chain_buffer_len(const chain_buffer_t *buf) {
@@ -62,8 +255,10 @@ size_t chain_buffer_len(const chain_buffer_t *buf) {
}
int chain_buffer_append(chain_buffer_t *buf, const void *data, size_t len) {
const uint8_t *src = (const uint8_t *)data;
if (!buf || (!src && len > 0)) {
const uint8_t *src;
size_t remain;
if (!buf || (!data && len > 0)) {
errno = EINVAL;
return -1;
}
@@ -75,78 +270,97 @@ int chain_buffer_append(chain_buffer_t *buf, const void *data, size_t len) {
return -1;
}
size_t remain = len;
src = (const uint8_t *)data;
remain = len;
while (remain > 0) {
chain_buffer_node_t *tail = buf->tail;
size_t writable = 0;
chain_buffer_node_t *tail;
uint8_t *p1;
uint8_t *p2;
size_t l1;
size_t l2;
size_t writable;
size_t n;
size_t c1;
if (tail && tail->end < tail->cap) {
writable = tail->cap - tail->end;
}
if (writable == 0) {
if (!buf->tail || buf->tail->used == buf->tail->cap) {
size_t cap = remain > buf->chunk_size ? remain : buf->chunk_size;
chain_buffer_node_t *node = alloc_node(cap);
if (!node) {
if (append_new_tail(buf, cap) < 0) {
errno = ENOMEM;
return -1;
}
if (buf->tail) {
buf->tail->next = node;
buf->tail = node;
} else {
buf->head = node;
buf->tail = node;
}
tail = node;
writable = tail->cap;
}
size_t n = remain < writable ? remain : writable;
memcpy(tail->data + tail->end, src, n);
tail->end += n;
tail = buf->tail;
node_write_segments(tail, &p1, &l1, &p2, &l2);
writable = l1 + l2;
if (writable == 0) {
continue;
}
n = min_size(remain, writable);
c1 = min_size(n, l1);
if (c1 > 0) {
memcpy(p1, src, c1);
}
if (n > c1) {
memcpy(p2, src + c1, n - c1);
}
node_advance_write(tail, n);
buf->total_len += n;
src += n;
remain -= n;
buf->total_len += n;
}
return 0;
}
size_t chain_buffer_drain(chain_buffer_t *buf, size_t len) {
size_t remain;
size_t drained;
if (!buf || len == 0 || buf->total_len == 0) {
return 0;
}
size_t remain = len;
size_t drained = 0;
remain = len;
drained = 0;
while (remain > 0 && buf->head) {
chain_buffer_node_t *node = buf->head;
size_t avail = node->end - node->start;
size_t n = min_size(remain, node->used);
if (remain < avail) {
node->start += remain;
buf->total_len -= remain;
drained += remain;
break;
if (n == 0) {
buf->head = node->next;
if (!buf->head) {
buf->tail = NULL;
}
recycle_node(buf, node);
continue;
}
remain -= avail;
drained += avail;
buf->total_len -= avail;
buf->head = node->next;
if (!buf->head) {
buf->tail = NULL;
node_advance_read(node, n);
buf->total_len -= n;
drained += n;
remain -= n;
if (node->used == 0) {
assert(node->refcnt == 0);
buf->head = node->next;
if (!buf->head) {
buf->tail = NULL;
}
recycle_node(buf, node);
}
free(node);
}
return drained;
}
const uint8_t *chain_buffer_linearize(chain_buffer_t *buf, size_t *out_len) {
size_t offset;
if (!buf) {
return NULL;
}
@@ -160,7 +374,13 @@ const uint8_t *chain_buffer_linearize(chain_buffer_t *buf, size_t *out_len) {
}
if (buf->head == buf->tail && buf->head) {
return buf->head->data + buf->head->start;
chain_buffer_node_t *node = buf->head;
if (node->used == 0) {
return NULL;
}
if (node_read_seg1_len(node) == node->used) {
return node->data + node->rpos;
}
}
if (buf->linear_cap < buf->total_len) {
@@ -172,20 +392,33 @@ const uint8_t *chain_buffer_linearize(chain_buffer_t *buf, size_t *out_len) {
buf->linear_cap = buf->total_len;
}
size_t offset = 0;
offset = 0;
for (chain_buffer_node_t *node = buf->head; node; node = node->next) {
size_t avail = node->end - node->start;
if (avail == 0) {
continue;
uint8_t *p1;
uint8_t *p2;
size_t l1;
size_t l2;
node_read_segments(node, &p1, &l1, &p2, &l2);
if (l1 > 0) {
memcpy(buf->linear_cache + offset, p1, l1);
offset += l1;
}
if (l2 > 0) {
memcpy(buf->linear_cache + offset, p2, l2);
offset += l2;
}
memcpy(buf->linear_cache + offset, node->data + node->start, avail);
offset += avail;
}
return buf->linear_cache;
}
ssize_t chain_buffer_send_fd(chain_buffer_t *buf, int fd, int flags) {
struct iovec iov[CHAINBUFFER_MAX_IOV];
struct msghdr msg;
size_t iovcnt;
ssize_t n;
if (!buf) {
errno = EINVAL;
return -1;
@@ -194,34 +427,277 @@ ssize_t chain_buffer_send_fd(chain_buffer_t *buf, int fd, int flags) {
return 0;
}
struct iovec iov[CHAINBUFFER_MAX_IOV];
size_t iovcnt = 0;
iovcnt = 0;
for (chain_buffer_node_t *node = buf->head;
node && iovcnt < CHAINBUFFER_MAX_IOV;
node = node->next) {
size_t avail = node->end - node->start;
if (avail == 0) {
continue;
uint8_t *p1;
uint8_t *p2;
size_t l1;
size_t l2;
node_read_segments(node, &p1, &l1, &p2, &l2);
if (l1 > 0) {
iov[iovcnt].iov_base = p1;
iov[iovcnt].iov_len = l1;
iovcnt++;
if (iovcnt >= CHAINBUFFER_MAX_IOV) {
break;
}
}
if (l2 > 0) {
iov[iovcnt].iov_base = p2;
iov[iovcnt].iov_len = l2;
iovcnt++;
}
iov[iovcnt].iov_base = (void *)(node->data + node->start);
iov[iovcnt].iov_len = avail;
iovcnt++;
}
if (iovcnt == 0) {
return 0;
}
struct msghdr msg;
memset(&msg, 0, sizeof(msg));
msg.msg_iov = iov;
msg.msg_iovlen = iovcnt;
ssize_t n = sendmsg(fd, &msg, flags);
n = sendmsg(fd, &msg, flags);
if (n > 0) {
chain_buffer_drain(buf, (size_t)n);
}
return n;
}
int chain_buffer_prepare_recv_iov(chain_buffer_t *buf, struct iovec *iov, int max_iov) {
chain_buffer_node_t *tail;
uint8_t *p1;
uint8_t *p2;
size_t l1;
size_t l2;
int iovcnt;
if (!buf || !iov || max_iov <= 0) {
errno = EINVAL;
return -1;
}
if (!buf->tail || buf->tail->used == buf->tail->cap) {
if (append_new_tail(buf, buf->chunk_size) < 0) {
errno = ENOMEM;
return -1;
}
}
tail = buf->tail;
node_write_segments(tail, &p1, &l1, &p2, &l2);
iovcnt = 0;
if (l1 > 0) {
iov[iovcnt].iov_base = p1;
iov[iovcnt].iov_len = l1;
iovcnt++;
}
if (l2 > 0 && iovcnt < max_iov) {
iov[iovcnt].iov_base = p2;
iov[iovcnt].iov_len = l2;
iovcnt++;
}
return iovcnt;
}
size_t chain_buffer_commit_recv(chain_buffer_t *buf, size_t len) {
chain_buffer_node_t *tail;
size_t free_total;
if (!buf || len == 0) {
return 0;
}
if (!buf->tail || buf->tail->used == buf->tail->cap) {
return 0;
}
tail = buf->tail;
free_total = tail->cap - tail->used;
if (len > free_total) {
len = free_total;
}
node_advance_write(tail, len);
buf->total_len += len;
return len;
}
void chain_buffer_list_init(chain_buffer_list_t *list) {
if (!list) {
return;
}
memset(list, 0, sizeof(*list));
}
size_t chain_buffer_list_len(const chain_buffer_list_t *list) {
return list ? list->total_len : 0;
}
int chain_buffer_list_iov(const chain_buffer_list_t *list, struct iovec *iov, int max_iov) {
int iovcnt = 0;
if (!list || !iov || max_iov <= 0) {
errno = EINVAL;
return -1;
}
for (chain_buffer_node_t *node = list->head; node && iovcnt < max_iov; node = node->next) {
uint8_t *p1;
uint8_t *p2;
size_t l1;
size_t l2;
node_read_segments(node, &p1, &l1, &p2, &l2);
if (l1 > 0) {
iov[iovcnt].iov_base = p1;
iov[iovcnt].iov_len = l1;
iovcnt++;
if (iovcnt >= max_iov) {
break;
}
}
if (l2 > 0) {
iov[iovcnt].iov_base = p2;
iov[iovcnt].iov_len = l2;
iovcnt++;
}
}
return iovcnt;
}
static int copy_prefix_from_node(chain_buffer_node_t *node, size_t len, uint8_t *dst) {
uint8_t *p1;
uint8_t *p2;
size_t l1;
size_t l2;
size_t c1;
if (!node || !dst || len == 0 || len > node->used) {
return -1;
}
node_read_segments(node, &p1, &l1, &p2, &l2);
c1 = min_size(len, l1);
if (c1 > 0) {
memcpy(dst, p1, c1);
}
if (len > c1) {
memcpy(dst + c1, p2, len - c1);
}
return 0;
}
int chain_buffer_detach_prefix(chain_buffer_t *buf, size_t len, chain_buffer_list_t *out) {
size_t remain;
if (!buf || !out) {
errno = EINVAL;
return -1;
}
chain_buffer_list_init(out);
if (len == 0) {
return 0;
}
if (len > buf->total_len) {
errno = EINVAL;
return -1;
}
remain = len;
while (remain > 0 && buf->head) {
chain_buffer_node_t *node = buf->head;
if (remain >= node->used) {
size_t take = node->used;
buf->head = node->next;
if (!buf->head) {
buf->tail = NULL;
}
node->next = NULL;
node->refcnt = 1;
list_append_node(out, node);
out->total_len += take;
buf->total_len -= take;
remain -= take;
continue;
}
{
chain_buffer_node_t *part = acquire_node(buf, remain);
if (!part) {
chain_buffer_list_release(buf, out);
errno = ENOMEM;
return -1;
}
if (copy_prefix_from_node(node, remain, part->data) < 0) {
recycle_node(buf, part);
chain_buffer_list_release(buf, out);
errno = EINVAL;
return -1;
}
part->used = remain;
part->wpos = remain % part->cap;
part->rpos = 0;
part->refcnt = 1;
part->next = NULL;
list_append_node(out, part);
out->total_len += remain;
node_advance_read(node, remain);
buf->total_len -= remain;
remain = 0;
if (node->used == 0) {
buf->head = node->next;
if (!buf->head) {
buf->tail = NULL;
}
recycle_node(buf, node);
}
}
}
return 0;
}
void chain_buffer_list_release(chain_buffer_t *owner, chain_buffer_list_t *list) {
chain_buffer_node_t *node;
if (!list) {
return;
}
node = list->head;
while (node) {
chain_buffer_node_t *next = node->next;
if (node->refcnt > 0) {
node->refcnt--;
}
if (node->refcnt == 0) {
if (owner) {
recycle_node(owner, node);
} else {
free(node);
}
}
node = next;
}
chain_buffer_list_init(list);
}

View File

@@ -4,6 +4,7 @@
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/uio.h>
typedef struct chain_buffer_node chain_buffer_node_t;
@@ -12,10 +13,21 @@ typedef struct chain_buffer_s {
chain_buffer_node_t *tail;
size_t total_len;
size_t chunk_size;
chain_buffer_node_t *free_list;
size_t free_count;
size_t free_limit;
uint8_t *linear_cache;
size_t linear_cap;
} chain_buffer_t;
typedef struct chain_buffer_list_s {
chain_buffer_node_t *head;
chain_buffer_node_t *tail;
size_t total_len;
} chain_buffer_list_t;
void chain_buffer_init(chain_buffer_t *buf, size_t chunk_size);
void chain_buffer_reset(chain_buffer_t *buf);
@@ -26,4 +38,15 @@ size_t chain_buffer_drain(chain_buffer_t *buf, size_t len);
const uint8_t *chain_buffer_linearize(chain_buffer_t *buf, size_t *out_len);
ssize_t chain_buffer_send_fd(chain_buffer_t *buf, int fd, int flags);
/* readv zero-copy recv helpers */
int chain_buffer_prepare_recv_iov(chain_buffer_t *buf, struct iovec *iov, int max_iov);
size_t chain_buffer_commit_recv(chain_buffer_t *buf, size_t len);
/* ownership transfer helpers */
void chain_buffer_list_init(chain_buffer_list_t *list);
size_t chain_buffer_list_len(const chain_buffer_list_t *list);
int chain_buffer_list_iov(const chain_buffer_list_t *list, struct iovec *iov, int max_iov);
int chain_buffer_detach_prefix(chain_buffer_t *buf, size_t len, chain_buffer_list_t *out);
void chain_buffer_list_release(chain_buffer_t *owner, chain_buffer_list_t *list);
#endif