Skip to content

pollfd: route the fds-removal io stop through _lws_event_loop_ops_io() - #3650

Closed
saghul wants to merge 1 commit into
warmcat:mainfrom
saghul:fix-he-racer-io-routing
Closed

pollfd: route the fds-removal io stop through _lws_event_loop_ops_io()#3650
saghul wants to merge 1 commit into
warmcat:mainfrom
saghul:fix-he-racer-io-routing

Conversation

@saghul

@saghul saghul commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Third one found while implementing the parallel-connect event lib ops (d31f2d830 "event-loop: HE races") in a custom event lib driving lws off a libuv loop.

__remove_wsi_socket_from_fds() calls event_loop_ops->io() directly, bypassing the parallel routing that _lws_event_loop_ops_io() performs:

	if (context->event_loop_ops->io)
		context->event_loop_ops->io(wsi, LWS_EV_STOP | LWS_EV_READ |
							       LWS_EV_WRITE);

lws_remove_parallel_fd_safely() points wsi->desc and position_in_fds_table at the racer it is removing and then calls __remove_wsi_socket_from_fds(), so this raw call reaches the event lib as "stop everything on this wsi". Event libs resolve their watcher from the wsi — lws's own libuv elops_io_uv() uses wsi_to_priv_uv(wsi)->w_read, i.e. the primary — so it stops the primary socket's watcher, when the racer's is the one being torn down. The lws_plat_delete_socket_from_fds() call right after does go through _lws_event_loop_ops_io() and correctly stops the racer, so the net effect is: racer stopped (intended) and primary stopped (not intended).

The primary is typically still connecting when a racer is removed (e.g. an unroutable AAAA racer failing first), and nothing re-enables its POLLOUT afterwards — _lws_change_pollfd() short-circuits on pa->prev_events == pa->events, and from lws's point of view the primary's fds entry never changed — so the connect attempt hangs until it times out. Symptom on our side was Timed out waiting SSL on hosts with more than one address.

This routes it the same way the two lws_plat_{insert,delete}_socket_into_fds() call sites were routed by 'event-loop: HE races', so the stop lands on the socket wsi->desc actually refers to.

Note this one is not needed by an event lib that resolves its watcher by fd rather than by wsi (which is what we ended up doing), but it does affect the in-tree libuv/libev/libevent/glib/sdevent/uloop libs, which all key off the wsi.

__remove_wsi_socket_from_fds() calls event_loop_ops->io() directly, bypassing
the parallel routing that _lws_event_loop_ops_io() does.

That matters for the racing (parallel) connect teardown:
lws_remove_parallel_fd_safely() points wsi->desc and position_in_fds_table at
the racer it is removing and then calls __remove_wsi_socket_from_fds(), so the
raw ->io() reaches the event lib as "stop everything on this wsi".  Event libs
key their watcher off the wsi (lws's own libuv elops_io_uv() uses
wsi_to_priv_uv(wsi)->w_read), so it stops the *primary* socket's watcher, while
the racer's watcher is the one meant to be stopped.  The primary is still
connecting at that point and nothing re-enables its POLLOUT, so the connect
attempt hangs until it times out.

Route it like the two lws_plat_{insert,delete}_socket_into_fds() call sites
already do after 'event-loop: HE races', so the stop lands on the socket
wsi->desc actually refers to.
@saghul

saghul commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

Related happy-eyeballs / parallel-connect fixes from the same investigation, all independent of each other: #3648 (racing fd gets the POLLOUT), #3649 (primary retired when a racer wins), #3650 (fds-removal io stop routed through the parallel ops).

@sonarqubecloud

Copy link
Copy Markdown

@saghul saghul closed this Aug 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant