diff --git a/src/core/address.h b/src/core/address.h index 6fdf6852..30357755 100644 --- a/src/core/address.h +++ b/src/core/address.h @@ -83,5 +83,7 @@ evpl_address_get_address( sin6 = (struct sockaddr_in6 *) sa; inet_ntop(AF_INET6, &sin6->sin6_addr, addr_str, sizeof(addr_str)); snprintf(str, len, "[%s]:%d", addr_str, ntohs(sin6->sin6_port)); + } else { + snprintf(str, len, "(unspecified)"); } } /* evpl_bind_get_local_address */ diff --git a/src/core/xlio/common.h b/src/core/xlio/common.h index e77a8fb6..ebdb7281 100644 --- a/src/core/xlio/common.h +++ b/src/core/xlio/common.h @@ -112,12 +112,15 @@ struct evpl_xlio { struct xlio_api_t *extra; struct evpl_xlio_api *api; struct evpl_poll *poll; + struct evpl_timer idle_timer; xlio_poll_group_t poll_group; struct evpl_xlio_socket **active_sockets; struct evpl_xlio_buffer *free_xlio_buffers; struct evpl_xlio_zc *free_zc; int num_active_sockets; int max_active_sockets; + int num_connections; + int idle_timer_active; }; struct evpl_xlio_accepted_socket { @@ -216,6 +219,17 @@ evpl_xlio_pending_close( #endif // if 0 s->socket = 0; + + if (!s->listen) { + --xlio->num_connections; + if (xlio->num_connections == 0) { + /* Last active connection gone — release the event loop so + * it can drain spin_ns of idleness and park in + * evpl_core_wait until the idle_timer (or some other + * source) wakes it. */ + evpl->force_poll_mode = 0; + } + } } } /* evpl_xlio_pending_close */ diff --git a/src/core/xlio/tcp.c b/src/core/xlio/tcp.c index ef4488ca..ea60c057 100644 --- a/src/core/xlio/tcp.c +++ b/src/core/xlio/tcp.c @@ -330,6 +330,19 @@ evpl_xlio_tcp_attach( bind->local->addrlen = sslen; memcpy(bind->local->addr, &ss, sslen); + /* Now that the socket is attached to this thread's poll group, resolve + * the peer address here. evpl_xlio_socket_accept (the listener-thread + * callback) deliberately leaves remote NULL because calling + * getpeername() in that context races XLIO's connection state machine + * for short-lived connections. */ + sslen = sizeof(ss); + rc = xlio->extra->xlio_socket_getpeername(sock, (struct sockaddr *) &ss, &sslen); + evpl_xlio_abort_if(rc < 0, "xlio_socket_getpeername failed"); + + bind->remote = evpl_address_alloc(); + bind->remote->addrlen = sslen; + memcpy(bind->remote->addr, &ss, sslen); + rc = xlio->extra->xlio_socket_update(s->socket, 0, (uintptr_t) s); evpl_xlio_abort_if(rc, "Failed to update socket"); diff --git a/src/core/xlio/xlio.c b/src/core/xlio/xlio.c index 0a971b1c..0b7216e8 100644 --- a/src/core/xlio/xlio.c +++ b/src/core/xlio/xlio.c @@ -207,7 +207,6 @@ evpl_xlio_socket_accept( struct evpl_xlio *xlio; struct evpl_bind *listen_bind; struct evpl_xlio_socket *ls; - struct evpl_address *srcaddr; struct evpl_xlio_accepted_socket *accepted_socket; int rc; @@ -223,15 +222,16 @@ evpl_xlio_socket_accept( xlio = evpl_framework_private(evpl, EVPL_FRAMEWORK_XLIO); - srcaddr = evpl_address_alloc(); - - xlio->extra->xlio_socket_getpeername(sock, srcaddr->addr, &srcaddr->addrlen); - rc = xlio->extra->xlio_socket_detach_group(sock); evpl_xlio_abort_if(rc, "Failed to detach socket from group"); - listen_bind->accept_callback(evpl, listen_bind, srcaddr, accepted_socket, listen_bind->private_data); + /* The peer address is resolved on the worker thread inside + * evpl_xlio_tcp_attach, after the socket has been re-attached to that + * thread's poll group. Calling getpeername() from this listener-thread + * context races XLIO's internal connection state machine and can return + * stale or empty data when sockets churn quickly. */ + listen_bind->accept_callback(evpl, listen_bind, NULL, accepted_socket, listen_bind->private_data); } /* evpl_xlio_socket_accept */ @@ -265,6 +265,17 @@ evpl_xlio_socket_rx( evpl_xlio_socket_check_active(xlio, s); } /* evpl_xlio_socket_rx */ +static void +evpl_xlio_idle_timer( + struct evpl *evpl, + struct evpl_timer *timer) +{ + struct evpl_xlio *xlio = container_of(timer, struct evpl_xlio, idle_timer); + + xlio->extra->xlio_poll_group_poll(xlio->poll_group); + xlio->extra->xlio_poll_group_flush(xlio->poll_group); +} /* evpl_xlio_idle_timer */ + static void evpl_xlio_poll( struct evpl *evpl, @@ -275,6 +286,14 @@ evpl_xlio_poll( struct evpl_bind *bind; int i, res; + /* When this thread has no established XLIO connections, skip the entire + * poll. The idle_timer wakes us every 10ms to service listen sockets and + * detect new incoming work; once any connection exists, the regular poll + * path takes over again. */ + if (xlio->num_connections == 0) { + return; + } + xlio->extra->xlio_poll_group_poll(xlio->poll_group); if (xlio->num_active_sockets) { @@ -376,6 +395,9 @@ evpl_xlio_destroy( if (xlio->poll) { evpl_remove_poll(evpl, xlio->poll); + if (xlio->idle_timer_active) { + evpl_remove_timer(evpl, &xlio->idle_timer); + } evpl->force_poll_mode = 0; } @@ -461,8 +483,26 @@ evpl_xlio_socket_init( } if (!xlio->poll) { - xlio->poll = evpl_add_poll(evpl, NULL, NULL, evpl_xlio_poll, xlio); - evpl->force_poll_mode = 1; + xlio->poll = evpl_add_poll(evpl, NULL, NULL, evpl_xlio_poll, xlio); + } + + if (listen) { + /* Listen sockets need a periodic poll to catch incoming accepts + * even when num_connections == 0 (poll callback short-circuits). + * Worker threads never hit this branch and therefore never pay + * for the timer: when a worker has connections the regular poll + * runs, and when it doesn't there's nothing for XLIO to service. */ + if (!xlio->idle_timer_active) { + evpl_add_timer(evpl, &xlio->idle_timer, evpl_xlio_idle_timer, 10000UL); + xlio->idle_timer_active = 1; + } + } else { + if (xlio->num_connections == 0) { + /* First active connection on this thread — pin the event loop + * into poll mode so we get per-iteration polling latency. */ + evpl->force_poll_mode = 1; + } + ++xlio->num_connections; } s->readable = 0;