#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

Reply via email to