perf: add zero-copy forwarding using splice syscall on linux
refactor: simplify config hot reload by removing stale config list fix: improve connection event handling and buffering logic fix: prevent multiple upstream connections by closing existing pairs
This commit is contained in:
parent
3abeb0e226
commit
5f60edd4ce
@ -14,6 +14,14 @@
|
||||
|
||||
|
||||
|
||||
|
||||
## Version 0.14.0 - 2026-01-27
|
||||
|
||||
add zero-copy forwarding using splice syscall on linux
|
||||
|
||||
**Changes:** 4 files, 105 lines
|
||||
**Languages:** C (105 lines)
|
||||
|
||||
## Version 0.13.0 - 2026-01-27
|
||||
|
||||
extract base64 decoding into separate module
|
||||
|
||||
28
src/config.c
28
src/config.c
@ -14,7 +14,6 @@
|
||||
static time_t config_file_mtime = 0;
|
||||
|
||||
app_config_t *config = NULL;
|
||||
static app_config_t *stale_configs_head = NULL;
|
||||
|
||||
static app_config_t *config_create_from_json(cJSON *root) {
|
||||
app_config_t *new_config = calloc(1, sizeof(app_config_t));
|
||||
@ -105,6 +104,11 @@ int config_load(const char *filename) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
if (stat(filename, &st) == 0) {
|
||||
config_file_mtime = st.st_mtime;
|
||||
}
|
||||
|
||||
if (config) {
|
||||
config_ref_dec(config);
|
||||
}
|
||||
@ -137,14 +141,6 @@ void config_free(void) {
|
||||
config_ref_dec(config);
|
||||
config = NULL;
|
||||
}
|
||||
|
||||
app_config_t *current = stale_configs_head;
|
||||
while (current) {
|
||||
app_config_t *next = current->next;
|
||||
config_ref_dec(current);
|
||||
current = next;
|
||||
}
|
||||
stale_configs_head = NULL;
|
||||
}
|
||||
|
||||
void config_create_default(const char *filename) {
|
||||
@ -240,14 +236,16 @@ int config_hot_reload(const char *filename) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
app_config_t *old_config = config;
|
||||
config = new_config;
|
||||
|
||||
if (old_config) {
|
||||
old_config->next = stale_configs_head;
|
||||
stale_configs_head = old_config;
|
||||
struct stat st;
|
||||
if (stat(filename, &st) == 0) {
|
||||
config_file_mtime = st.st_mtime;
|
||||
}
|
||||
|
||||
if (config) {
|
||||
config_ref_dec(config);
|
||||
}
|
||||
config = new_config;
|
||||
|
||||
log_info("Hot-reload complete: %d routes loaded", new_config->route_count);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -153,7 +153,8 @@ void connection_close(int fd) {
|
||||
|
||||
size_t buffered = pair->read_buf.tail - pair->read_buf.head;
|
||||
if (buffered > 0) {
|
||||
epoll_utils_modify(pair->fd, EPOLLIN | EPOLLOUT);
|
||||
log_debug("Processing %zu buffered bytes from client fd %d after upstream fd %d closed", buffered, pair->fd, fd);
|
||||
client_handle_read(pair);
|
||||
}
|
||||
} else if (conn->type == CONN_TYPE_CLIENT && pair->type == CONN_TYPE_UPSTREAM) {
|
||||
pair->pair = NULL;
|
||||
@ -355,7 +356,7 @@ static void handle_client_event(connection_t *conn, uint32_t events) {
|
||||
int n = connection_do_read(conn);
|
||||
if (n > 0) {
|
||||
forwarding_handle(conn, conn->pair, 0);
|
||||
} else if (n < 0) {
|
||||
} else if (n <= 0) {
|
||||
conn->state = CLIENT_STATE_CLOSING;
|
||||
}
|
||||
}
|
||||
@ -401,10 +402,10 @@ static void handle_client_event(connection_t *conn, uint32_t events) {
|
||||
conn->state = CLIENT_STATE_CLOSING;
|
||||
} else {
|
||||
conn->state = CLIENT_STATE_READING_HEADERS;
|
||||
conn->read_buf.head = 0;
|
||||
conn->read_buf.tail = 0;
|
||||
memset(&conn->request, 0, sizeof(conn->request));
|
||||
epoll_utils_modify(conn->fd, EPOLLIN);
|
||||
if (buffer_available_read(&conn->read_buf) > 0) {
|
||||
client_handle_read(conn);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
epoll_utils_modify(conn->fd, EPOLLIN);
|
||||
|
||||
@ -52,15 +52,77 @@ static ssize_t forwarding_write_all(connection_t *dst, const char *data, size_t
|
||||
return (ssize_t)total_written;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
int forwarding_try_splice(connection_t *src, connection_t *dst) {
|
||||
if (src->splice_pipe[0] < 0 || src->splice_pipe[1] < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t bytes_to_pipe = splice(src->fd, NULL, src->splice_pipe[1], NULL,
|
||||
CHUNK_SIZE, SPLICE_F_NONBLOCK | SPLICE_F_MOVE);
|
||||
|
||||
if (bytes_to_pipe <= 0) {
|
||||
if (bytes_to_pipe == 0) return 0;
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) return -1;
|
||||
return -2;
|
||||
}
|
||||
|
||||
ssize_t bytes_from_pipe = splice(src->splice_pipe[0], NULL, dst->fd, NULL,
|
||||
(size_t)bytes_to_pipe, SPLICE_F_NONBLOCK | SPLICE_F_MOVE);
|
||||
|
||||
if (bytes_from_pipe < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
char discard[CHUNK_SIZE];
|
||||
if (read(src->splice_pipe[0], discard, (size_t)bytes_to_pipe) < 0) {
|
||||
// Ignore
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
return -2;
|
||||
}
|
||||
|
||||
src->last_activity = cached_time;
|
||||
dst->last_activity = cached_time;
|
||||
|
||||
if (src->vhost_stats) {
|
||||
monitor_record_bytes(src->vhost_stats, bytes_from_pipe, 0);
|
||||
monitor_record_splice_transfer(src->vhost_stats, (size_t)bytes_from_pipe);
|
||||
}
|
||||
|
||||
return (int)bytes_from_pipe;
|
||||
}
|
||||
#else
|
||||
int forwarding_try_splice(connection_t *src, connection_t *dst) {
|
||||
(void)src;
|
||||
(void)dst;
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
void forwarding_handle(connection_t *conn, connection_t *pair, int direction) {
|
||||
if (!conn || !pair) return;
|
||||
|
||||
#ifdef __linux__
|
||||
int is_response = (direction == 1);
|
||||
if (conn->can_splice &&
|
||||
buffer_available_read(&conn->read_buf) == 0 &&
|
||||
buffer_available_read(&pair->write_buf) == 0 &&
|
||||
(!is_response || conn->response_headers_parsed)) {
|
||||
|
||||
int splice_result = forwarding_try_splice(conn, pair);
|
||||
if (splice_result > 0) return;
|
||||
if (splice_result == 0) {
|
||||
conn->state = CLIENT_STATE_CLOSING;
|
||||
return;
|
||||
}
|
||||
if (splice_result == -2) {
|
||||
conn->state = CLIENT_STATE_CLOSING;
|
||||
pair->state = CLIENT_STATE_CLOSING;
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
size_t available = conn->read_buf.tail - conn->read_buf.head;
|
||||
if (available == 0) return;
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
#include "socket_utils.h"
|
||||
#include "epoll_utils.h"
|
||||
#include "patch.h"
|
||||
#include "connection.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@ -176,6 +177,11 @@ void upstream_connect(connection_t *client, const char *data, size_t data_len) {
|
||||
}
|
||||
}
|
||||
|
||||
if (client->pair) {
|
||||
log_debug("Closing existing upstream fd %d before new connection for client fd %d", client->pair->fd, client->fd);
|
||||
connection_close(client->pair->fd);
|
||||
}
|
||||
|
||||
epoll_utils_add(up_fd, EPOLLIN | EPOLLOUT);
|
||||
|
||||
connection_t *up = &connections[up_fd];
|
||||
|
||||
Loading…
Reference in New Issue
Block a user