Preserve proxy TCP streams across connect, backpressure and FIN

This commit is contained in:
evgeny committed 2026-09-29 04:00:34 +03:00
1 parent 09bc8c35c5
commit e1946b208d
13 files changed
+831 -458

No files matched your search

+13 -1
View File
@@ -282,6 +282,8 @@ static int flush_write_buf(struct tcp_conn* tc) {
return 0;
}
// Последний send может опустошить очередь при уже выключенном POLLOUT.
// Уведомляем потребителя здесь, не рассчитывая на ещё один write event.
static void notify_flushed(struct tcp_conn* tc) {
if (tc->destroyed || tc->write_buf || tc->write_queue->head || !tc->on_flushed) return;
void (*cb)(struct tcp_conn*, void*) = tc->on_flushed;
@@ -316,7 +318,7 @@ static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
if (!e->dgram) {
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: CLOSE fd=%d", (int)tc->sock);
queue_entry_free(e); queue_resume_callback(q);
uasync_remove_socket_t(tc->ua, tc->sock);
if (tc->socket_id) uasync_remove_socket_t(tc->ua, tc->sock);
socket_close_wrapper(tc->sock);
tc->socket_id = NULL; tc->sock = SOCKET_INVALID; tc->closed = 1;
if (tc->on_closed) tc->on_closed(tc, tc->arg);
@@ -394,6 +396,8 @@ static void write_cb(socket_t sock, void* arg) {
tc->connected = 1;
if (tc->connect_timer) { uasync_cancel_timeout(tc->ua, tc->connect_timer); tc->connect_timer = NULL; }
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: connect ok fd=%d", (int)tc->sock);
if (tc->on_connected) tc->on_connected(tc, tc->arg);
if (tc->destroyed) return;
} else {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: connect fail fd=%d err=%d", (int)tc->sock, err);
tcp_conn_handle_error(tc, err ? err : -1);
@@ -426,6 +430,14 @@ static void error_cb(socket_t sock, void* arg) {
// Peer полностью закрыл свой сокет (EPOLLHUP) после того, как FIN уже прочитан (fin_remote=1).
// EPOLLHUP — level-triggered и «always reported»: если оставить сокет в epoll, каждая итерация
// epoll_wait мгновенно вернёт HUP и error_cb зациклится на 100% CPU. Закрываем сокет.
if (tc->fin_remote && tc->fin_local) {
// Обе TCP-половины закрыты, но владелец ещё может передавать ранее прочитанные данные.
// Убираем level-triggered HUP из poll; CLOSE остаётся решением владельца.
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: both FINs fd=%d — detach poll, await owner drain", (int)tc->sock);
if (tc->socket_id) uasync_remove_socket_t(tc->ua, tc->sock);
tc->socket_id = NULL; tc->write_monitor = 0;
return;
}
if (tc->fin_remote) {
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: async HUP (graceful) fd=%d — peer fully closed after FIN, closing",
(int)tc->sock);
+1
View File
@@ -70,6 +70,7 @@ struct tcp_conn {
struct queue_waiter_handle read_waiter;
// Коллбэки
void (*on_connected)(struct tcp_conn* tc, void* arg); // исходящий connect завершён
void (*on_fin)(struct tcp_conn* tc, void* arg); // FIN получен от удалённой стороны (fin_remote=1)
void (*on_fin_sent)(struct tcp_conn* tc, void* arg); // FIN отправлен удалённой стороне (fin_local=1)
void (*on_error)(struct tcp_conn* tc, int err, void* arg); // фатальная ошибка (вызывается после close сокета; обязан вызвать tcp_conn_destroy)