[GHC] #7353: Make system IO interruptible on Windows
GHC
cvs-ghc at haskell.org
Fri Oct 26 08:45:29 CEST 2012
#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
More information about the Glasgow-haskell-bugs
mailing list