The GHC Commentary - Non-blocking I/O on Win32

This note discusses the implementation of non-blocking I/O on Win32 platforms. It is not implemented yet (Apr 2002), but it seems worth capturing the ideas. Thanks to Sigbjorn for writing them.

Background

GHC has provided non-blocking I/O support for Concurrent Haskell threads on platforms that provide 'UNIX-style' non-blocking I/O for quite a while. That is, platforms that let you alter the property of a file descriptor to instead of having a thread block performing an I/O operation that cannot be immediately satisfied, the operation returns back a special error code (EWOULDBLOCK.) When that happens, the CH thread that made the blocking I/O request is put into a blocked-on-IO state (see Foreign.C.Error.throwErrnoIfRetryMayBlock). The RTS will in a timely fashion check to see whether I/O is again possible (via a call to select()), and if it is, unblock the thread & have it re-try the I/O operation. The result is that other Concurrent Haskell threads won't be affected, but can continue operating while a thread is blocked on I/O.

Non-blocking I/O hasn't been supported by GHC on Win32 platforms, for the simple reason that it doesn't provide the OS facilities described above.

Win32 non-blocking I/O, attempt 1

Win32 does provide something select()-like, namely the WaitForMultipleObjects() API. It takes an array of kernel object handles plus a timeout interval, and waits for either one (or all) of them to become 'signalled'. A handle representing an open file (for reading) becomes signalled once there is input available.

So, it is possible to observe that I/O is possible using this function, but not whether there's "enough" to satisfy the I/O request. So, if we were to mimic select() usage with WaitForMultipleObjects(), we'd correctly avoid blocking initially, but a thread may very well block waiting for their I/O requests to be satisified once the file handle has become signalled. [There is a fix for this -- only read and write one byte at a the time -- but I'm not advocating that.]

Win32 non-blocking I/O, attempt 2

Asynchronous I/O on Win32 is supported via 'overlapped I/O'; that is, asynchronous read and write requests can be made via the ReadFile() / WriteFile () APIs, specifying position and length of the operation. If the I/O requests cannot be handled right away, the APIs won't block, but return immediately (and report ERROR_IO_PENDING as their status code.)

The completion of the request can be reported in a number of ways:

The use of I/O completion port looks the most interesting to GHC, as it provides a central point where all I/O requests are reported.

Note: asynchronous I/O is only fully supported by OSes based on the NT codebase, i.e., Win9x don't permit async I/O on files and pipes. However, Win9x does support async socket operations, and I'm currently guessing here, console I/O. In my view, it would be acceptable to provide non-blocking I/O support for NT-based OSes only.

Here's the design I currently have in mind:

I might do the communication between the RTS helper thread and the main RTS thread differently though: rather than have the RTS helper thread manipluate thread queues itself, thus requiring careful locking, just have it change a bit on the relevant TSO, which the main RTS thread can check at regular intervals (in some analog of awaitEvent(), for example).

Last modified: Wed Aug 8 19:30:18 EST 2001