# 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:

- synchronously, by blocking inside Read/WriteFile(). (this is the non-overlapped case, really.)

- as part of the overlapped I/O request, pass a HANDLE to an event object. The I/O system will signal this event once the request completed, which a waiting thread will then be able to see.

- by supplying a pointer to a completion routine, which will be called as an Asynchronous Procedure Call (APC) whenever a thread calls a select bunch of 'alertable' APIs.

- by associating the file handle with an I/O completion port. Once the request completes, the thread servicing the I/O completion port will be notified.

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:

- Upon startup, an RTS helper thread whose only purpose is to service an I/O completion port, is created.

- All files are opened in 'overlapping' mode, and associated with an I/O completion port.

- Overlapped I/O requests are used to implement read() and write().

- If the request cannot be satisified without blocking, the Haskell thread is put on the blocked-on-I/O thread list & a re-schedule is made.

- When the completion of a request is signalled via the I/O completion port, the RTS helper thread will move the associated Haskell thread from the blocked list onto the runnable list. (Clearly, care is required here to have another OS thread mutate internal Scheduler data structures.)

- In the event all Concurrent Haskell threads are blocked waiting on I/O, the main RTS thread blocks waiting on an event synchronisation object, which the helper thread will signal whenever it makes a Haskell thread runnable.

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