#7353: Make system IO interruptible on Windows ---------------------------------+------------------------------------------ Reporter: joeyadams | Owner: Type: bug | Status: new Priority: normal | Milestone: 7.8.1 Component: libraries/base | Version: 7.6.1 Keywords: | Os: Windows Architecture: Unknown/Multiple | Failure: Incorrect result at runtime Difficulty: Unknown | Testcase: Blockedby: | Blocking: Related: | ---------------------------------+------------------------------------------
Comment(by joeyadams): Good point. Indeed, [http://hackage.haskell.org/packages/archive/base/latest/doc/html/System- IO.html#v:hWaitForInput hWaitForInput] uses a blocking FFI call even on Unix. I think we should still provide a way to wait for sockets on Windows that actually works, but that can be a separate ticket. Thanks for pointing that out. IOCP it is. I should elaborate more on the thread sensitivity issue. Based on what I've read, overlapped IO is canceled on [http://msdn.microsoft.com/en- us/library/ms682659.aspx thread exit]. Moreover, to cancel pending IO ourselves, we need to call [http://msdn.microsoft.com/en- us/library/windows/desktop/aa363791%28v=vs.85%29.aspx CancelIo] from the same OS thread that initiated the IO, to avoid canceling unrelated IO. The latter restriction can be avoided by using [http://msdn.microsoft.com /en-us/library/windows/desktop/aa363792%28v=vs.85%29.aspx CancelIoEx], but it requires Windows Vista or later. Therefore, we enforce this rule: for each OS thread, no more than one pending IO request per HANDLE. Thus, one thread may serve a large number of HANDLEs, but multiple concurrent requests on the same HANDLE will require separate threads. The IO manager would provide the following wrapper: {{{ -- | Do something that supports overlapped I/O, and wait for it to complete. withIOCP :: HANDLE -- ^ Device to operate on. Must be associated -- with the IO manager using 'registerHandle'. -> (Ptr OVERLAPPED -> IO ()) -- ^ Callback which starts the IO -> IO Completion -- | Associate a device handle with the IO manager's completion port. -- It will be unassociated automatically when the handle is closed. registerHandle :: HANDLE -> IO () }}} It does the following sequence: - Allocate and zero an [http://msdn.microsoft.com/en- us/library/ms684342%28v=vs.85%29.aspx OVERLAPPED] structure, paired with an MVar wrapped in a !StablePtr used for signaling completion. - Acquire an OS thread that isn't waiting for a completion on the same HANDLE. Both the callback and !CancelIo need to be called from the same OS thread, and this thread must not go away until we're done. - Run the callback, passing it the new OVERLAPPED structure. On exception, free our OVERLAPPED and !StablePtr. Note that if the IO completes immediately ([http://stackoverflow.com/questions/9888885/what-happens-with-win32-io- completion-port-and-synchronous-appearing-io which can happen]), the completion port will still receive a completion. Possible optimization: skip waiting for the IO manager and read the OVERLAPPED structure right away; I don't know if this is safe, though. - Wait for the IO manager to fill our MVar. On exception, issue !CancelIo. We do not need to unregister the OVERLAPPED or free it, since !CancelIo will cause a completion to be delivered, meaning we can free it from the IO manager. - Return the completion (either an error code, or the number of bytes transferred). So how do we do the "acquire an OS thread" step? Well, one way would be to manage a thread pool using [http://hackage.haskell.org/packages/archive/base/latest/doc/html/Control- Concurrent.html#v:forkOS forkOS], and pass IO jobs to it. But that means handing every IO job off to an OS thread. Suppose Control.Concurrent had a wrapper that lets us simply use the current thread, then use that same thread later on. The problem is, what if the current OS thread, after starting IO for one green thread, makes a blocking FFI call for another green thread? We'd have to wait for that to complete before doing our !CancelIo. I think the hand-off is unavoidable, unless: * The caller is already running a bound thread. * [http://msdn.microsoft.com/en- us/library/windows/desktop/aa363792%28v=vs.85%29.aspx CancelIoEx] is available. This can be tested at run-time, as I [https://github.com/joeyadams/haskell-system-time- monotonic/blob/master/cbits/dll.c did for GetTickCount64] in my [http://hackage.haskell.org/package/system-time-monotonic system-time- monotonic package]. To implement both of these optimizations, we would need a couple extensions to Control.Concurrent: * A variant of [http://hackage.haskell.org/packages/archive/base/latest/doc/html/Control- Concurrent.html#v:isCurrentThreadBound isCurrentThreadBound] that also returns an identifier for the current OS thread. * Reference counting to keep an OS thread alive until we say we're done with it, e.g.: {{{ withPinnedThread :: IO a -> IO (a, IO ()) }}} This increments the current OS thread's reference count, runs the callback entirely within that thread, and returns an action that decrements the thread's reference count. These would be nice to have, but the Windows IO manager should work just fine without them. Thanks for the guidance, especially at this critical stage. I'll see if I can implement this. Thanks to Felix Martini's [http://hackage.haskell.org/package/winio winio package], much of the work is already done. -- Ticket URL: <http://hackage.haskell.org/trac/ghc/ticket/7353#comment:4> GHC <http://www.haskell.org/ghc/> The Glasgow Haskell Compiler _______________________________________________ Glasgow-haskell-bugs mailing list Glasgow-haskell-bugs@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-bugs