fix: always remove socket from epoll in tcp_conn_handle_error

tcp_conn_handle_error гвардил uasync_remove_socket_t по tc->socket_id,
пропуская повторный вызов. В одном epoll_wait batch два события
(EPOLLERR + EPOLLOUT) на один fd: первое делает remove (socket_id=NULL),
второе проходит guard и dispatch'ит write_cb на освобождённом tc.

Фикс: всегда вызывать uasync_remove_socket_t + epoll_ctl(DEL),
независимо от socket_id.
This commit is contained in:
Evgeny committed 2026-07-02 00:16:05 +03:00
1 parent ccdbab95a8
commit aff3b2d00f
1 file changed
+7 -4
+7 -4
View File
@@ -32,7 +32,7 @@ struct tcp_conn* tcp_conn_create(
void (*on_error)(struct tcp_conn* tc, int err, void* arg),
void* arg)
{
if (!ua || sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: invalid args ua=%p", ua); return NULL; }
if (!ua || sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: invalid args ua=%p", (void*)ua); return NULL; }
struct tcp_conn* tc = u_calloc(1, sizeof(struct tcp_conn));
if (!tc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: u_calloc failed"); return NULL; }
@@ -157,11 +157,14 @@ static void tcp_conn_handle_error(struct tcp_conn* tc, int err)
}
}
if (tc->socket_id) {
if (tc->sock != SOCKET_INVALID) {
// Гарантированно удаляем из epoll (uasync_remove_socket_t может пропустить DEL если нода уже inactive)
uasync_remove_socket_t(tc->ua, tc->sock);
tc->socket_id = NULL;
}
if (tc->sock != SOCKET_INVALID) {
#if HAS_EPOLL
if (tc->ua && tc->ua->use_epoll && tc->ua->epoll_fd >= 0)
epoll_ctl(tc->ua->epoll_fd, EPOLL_CTL_DEL, (int)tc->sock, NULL);
#endif
socket_close_wrapper(tc->sock);
tc->sock = SOCKET_INVALID;
}