How Windows GUI Message Pump Integrates with Fiber Scheduling in libfiber
libfiber seamlessly weaves the Windows GUI message pump into its fiber scheduling mechanism by using a hidden window that receives socket notifications via WSAAsyncSelect, translating Windows messages into fiber readiness events through the EVENT abstraction layer.
The iqiyi/libfiber library abstracts I/O multiplexing behind a portable event-engine interface (EVENT). On Windows, the framework optionally builds its scheduling core atop the native GUI message pump rather than traditional polling mechanisms, enabling fibers to suspend and resume within message-driven Windows applications without blocking the main thread.
The Event Engine Abstraction on Windows
libfiber decouples its scheduler from platform-specific I/O mechanisms through the EVENT interface. On Windows, when the HAS_WMSG preprocessor macro is defined, the factory function event_create() instantiates the Windows message-pump implementation via event_wmsg_create() in c/src/event/event_wmsg.c instead of using select-based or I/O completion port engines.
This abstraction allows the scheduler to call generic methods like event->add_read(), event->add_write(), and event->event_wait() without knowledge of the underlying Windows messaging infrastructure.
Hidden Window Creation and Socket Mapping
The Windows-specific engine initializes by creating a hidden window to receive asynchronous socket notifications. In event_wmsg_create(), the function registers a window class via InitApplication and creates a message-only window using CreateSockWindow that listens for the custom WM_SOCKET_NOTIFY message.
To associate socket descriptors with their corresponding fiber I/O contexts, the engine maintains a hash table (ev->tbl) that maps SOCKET handles to FILE_EVENT structures. The helper functions wmsg_fdmap_set(), wmsg_fdmap_get(), and wmsg_fdmap_del() manage these mappings in c/src/event/event_wmsg.c, enabling O(1) lookup when Windows messages arrive.
Asynchronous Socket Event Registration
When a fiber yields waiting for I/O, the scheduler invokes event->add_read() or event->add_write(), which dispatch to wmsg_add_read() and wmsg_add_write() in the Win-msg engine. These functions call WSAAsyncSelect() to register the socket with the hidden window, requesting Windows message notifications for specific network events.
For read operations, wmsg_add_read() sets interest in FD_READ and FD_CLOSE flags (and FD_ACCEPT for listening sockets). For write operations, wmsg_add_write() sets FD_WRITE and FD_CLOSE flags (and FD_CONNECT for outgoing connections). This registration ensures that when network data arrives, Windows posts a WM_SOCKET_NOTIFY message to the hidden window rather than requiring the scheduler to poll socket states.
Message Pump Loop Implementation
The engine implements the blocking event_wait callback through wmsg_wait(), which serves as the heart of the fiber scheduler's event loop on Windows. This function creates a timer to honor the timeout parameter, then enters a blocking GetMessage() call to retrieve the next Windows message from the queue.
After dispatching the message via DispatchMessage(), the loop drains any remaining queued messages using PeekMessage() to ensure timely processing of multiple socket events. The core sequence—SetTimer, GetMessage, DispatchMessage, PeekMessage—transforms the standard Windows message pump into a fiber-aware event source that wakes only when sockets are ready or timers expire.
Dispatching Notifications to Resumable Fibers
The window procedure WndProc in c/src/event/event_wmsg.c handles WM_SOCKET_NOTIFY messages by extracting the socket handle from wParam and the event code using WSAGETSELECTEVENT(lParam). It looks up the corresponding FILE_EVENT structure in the hash table, then invokes the appropriate callback—onRead(), onWrite(), onAccept(), or onClose()—based on the notification type.
These callbacks execute the fiber's registered I/O handlers (fe->r_proc for reads, fe->w_proc for writes), which mark the waiting fiber as ready to run and trigger a context switch back into the scheduler. This mechanism bridges the Windows message-driven architecture with libfiber's cooperative multitasking model.
Scheduler Integration Flow
The integration completes within the main scheduler loop defined in acl_fiber_schedule_with(). The scheduler obtains an EVENT object through event_create(), which selects the event_wmsg_create() implementation on Windows builds configured with HAS_WMSG.
When a fiber calls acl_fiber_add_read() or acl_fiber_add_write() and then yields, the underlying engine registers the socket for asynchronous notification. The subsequent call to event->event_wait() (implemented as wmsg_wait()) blocks the scheduler thread on GetMessage() until the Windows message pump delivers a socket-ready notification. At that point, the scheduler resumes the corresponding fiber, creating a seamless cooperative multitasking environment that respects Windows' message-based architecture.
Practical Code Example
The following example demonstrates registering write interest and yielding until the socket becomes writable, leveraging the Windows message pump integration internally:
/* compile on Windows with libfiber sources */
#include "fiber/libfiber.h"
static void echo_client(ACL_FIBER *fiber, void *ctx)
{
SOCKET fd = (SOCKET)ctx;
const char *msg = "hello\r\n";
/* Register interest in write readiness – triggers WSAAsyncSelect internally */
acl_fiber_add_write(fd);
acl_fiber_yield(); /* Suspends until WM_SOCKET_NOTIFY arrives */
acl_fiber_send(fd, msg, strlen(msg), 0);
acl_fiber_close(fd);
}
int main(void)
{
socket_init();
acl_fiber_create(
[](ACL_FIBER *f, void *){
SOCKET s = socket_connect("127.0.0.1", 9001);
echo_client(f, (void *)s);
},
NULL, 128000);
/* Blocks in wmsg_wait() using the Windows message pump */
acl_fiber_schedule();
return 0;
}
In this example, acl_fiber_add_write() invokes the Win-msg engine's registration logic, acl_fiber_yield() suspends the fiber, and acl_fiber_schedule() enters the message pump loop that resumes the fiber when the socket becomes writable.
Summary
- libfiber uses a hidden window and the
WM_SOCKET_NOTIFYmessage to receive socket events on Windows, implemented inc/src/event/event_wmsg.c. - The hash table in
event_wmsg_create()maps socket descriptors toFILE_EVENTstructures for O(1) lookup during message dispatch. - WSAAsyncSelect bridges sockets to the GUI message pump, with
wmsg_add_read()andwmsg_add_write()managing event registration forFD_READ,FD_WRITE, andFD_CLOSEconditions. - The message pump loop in
wmsg_wait()replaces traditional polling, usingGetMessage()andDispatchMessage()to block until I/O events occur. - The scheduler factory selects the Windows message-pump engine when
HAS_WMSGis defined, integrating seamlessly withacl_fiber_schedule()to resume fibers viafe->r_procandfe->w_proccallbacks.
Frequently Asked Questions
How does libfiber avoid blocking the GUI thread while waiting for socket I/O?
The library delegates blocking to the Windows message pump itself. By using WSAAsyncSelect() to request WM_SOCKET_NOTIFY messages and then calling GetMessage() in wmsg_wait(), the scheduler yields control to Windows' message dispatcher. This allows the thread to process GUI messages while simultaneously waiting for network events, eliminating the need for a separate polling thread.
What is the purpose of the hidden window created by event_wmsg_create()?
The hidden window serves as the recipient for asynchronous socket notifications generated by WSAAsyncSelect(). Since Windows requires a window handle (HWND) to target messages, event_wmsg_create() creates a message-only window that receives WM_SOCKET_NOTIFY events. The WndProc window procedure then maps these messages back to the appropriate fiber I/O contexts using the internal hash table.
Can the Win-msg engine handle both GUI messages and fiber scheduling simultaneously?
Yes, because wmsg_wait() uses the standard GetMessage() and DispatchMessage() loop, it naturally processes all Windows messages in the thread's queue. This design allows a single thread to run both a traditional Windows GUI message pump and the libfiber scheduler without conflict, making it ideal for applications that embed fiber-based networking code within graphical interfaces.
How does the scheduler know which fiber to resume when a socket becomes ready?
When a WM_SOCKET_NOTIFY message arrives, the WndProc extracts the socket handle from wParam and looks up the associated FILE_EVENT structure in the hash table via wmsg_fdmap_get(). This structure contains function pointers to the fiber's I/O callbacks (fe->r_proc for reads, fe->w_proc for writes). Invoking these callbacks marks the specific suspended fiber as runnable, allowing the scheduler to resume it on the next iteration.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →