Skip to content

New native Windows I/O manager WinIO (Windows I/O)

Tamar Christina requested to merge Phyx/ghc:io-manager-simple-split into master

This introduces a new I/O manager for Windows called WinIO. WinIO is completely asynchronous and designed to scale. It is designed to work best with the threaded runtime but the non-threaded one should show benefits as well.

Windows's IO subsystem is by design fully asynchronous, however there are multiple ways and interfaces to the async methods.

The chosen Async interface for this implementation is using Completion Ports. The I/O manager uses a new interface added in Windows Vista called GetQueuedCompletionStatusEx which allows us to service multiple requests in one go.

The majority of documentation is in the relevant files directly, see e.g libraries\base\GHC\Event\Windows.hsc.

Overall the major things changed in this MR are

  • Drops Windows Vista support
    • Vista is out of extended support as of 2017. The new minimum is Windows 7. This allows us to use much more efficient OS provided abstractions.
  • Replace Events and Monitor locks with much faster and efficient Conditional Variables and SlimReaderWriterLocks.
  • Change GHC's Buffer and I/O structs to support asynchronous operation by not relying on the OS managing File Offset.
  • Experiment with high precision timers, The initial tick has a precision of 100ns but the elapsed part has 100ms making them still unusable for the resolution the RTS wants for profiling. I wonder if I can chain multiple timers together till the elapsed resolution.
  • Implement a new commandline flag +RTS --io-manager=[native|posix] to control which I/O manager is used.
  • Implement a new Console I/O interface supporting much faster reads/writes and unicode output correctly. Also supports things like cooked input etc.
  • In new I/O manager if the user still has their codepage set to OEM, then we use UTF-8 by default.
  • Changes to GCC driver to try and isolate it more against outside libraries preventing it from working, e.g. different version of pthreads.
  • Add Atomic Exchange PrimOp and implement Atomic Ptr exchanges.
  • Flush event logs eagerly as to not rely on finalizers running.
  • A lot of refactoring and more use of hsc2hs to share constants
  • Control aborts Ctrl+C should be a bit more reliable.
  • Add a new IOPort primitive that should be only used for these I/O operations. Essentially an IOPort is based on an MVar with the following major differences:
    • Does not allow multiple pending writes. If the port is full a second write is just discarded.
    • There is no deadlock avoidance guarantee. If you block on an IOPort and your Haskell application does not have any work left to do the whole application is stalled. In the threaded RTS we just continue idling, in the non-threaded rts the scheduler is blocked.
  • Support various optimizations in the Windows I/O manager such as skipping I/O Completion is request finished synchronously etc.
  • The I/O manager is now agnostic to the handle type. i.e. There are no socket specific code in the manager. This is now all pushed to the network library.
  • Eventually for Windows when this I/O manager is the default GHC should use UTF-16 internally rather than UTF-32. This prevents having to translate console calls. GHC Already has code for this but it is turned off. Supporting this as a runtime configuration thing turned out to be impossible without changing external user visible types such as Handle__.
  • Unified threaded and non-threaded I/O code. The only major difference is where event loop is driven from and that the non-threaded rts will always use a single OS thread to service requests. We cannot use more as there are no rts locks to make concurrent modifications safe.

This fixes issues tagged with https://gitlab.haskell.org/ghc/ghc/issues?label_name%5B%5D=I%2FO+manager

Also I will most likely drop the ability to suspend the I/O manager. This is significantly hard to do with multiple worker threads as the threaded version uses. The multiple workers are required to efficiently scale to the millions of connections/s. I believe this won't be an issue as the I/O manager overhead when idle is practically zero as it's not a polling based implementation.

Original I/O manager skeleton based on work from @23Skidoo and @Joseph Adams.

TODO:

  • Fix threadDelay non-threaded rts (I think this is done.. not sure)
  • Finish thread manager threaded rts
  • More documentation #18385 (closed)
  • Changelog entries #18385 (closed)
  • End user documentation #18385 (closed)
  • Performance tweaking
  • Decide what to do with high precision timer experiment.
  • Remove debug statements (or maybe hide behind a new flag)
  • Turn it on by default in testsuite runs, or make configurable.
  • Check if event notifications need/can be supported (likely not needed as we'll change network)
  • Refactor IOPort to re-use as much from MVar as possible.
  • Change boot libraries such as process. Anything that uses the FD interface has to change to using native Handles.
    • Process (needs work on createPipe but not blocker for merge)
    • Win32 updated (committed)
    • Cabal updated (committed)
    • hsc2hs updated (pr sent)
    • haskeline updated (committed)
  • Fix double free error (need to determine where it's happening) #18381 (closed)
  • Fix issued with NamedPipes and blocking calls (after merge) #18382 (closed)
  • Check if GHCi still works correctly #18386 (closed)
  • Change std handles to utf8 on startup of winio. (after merge) #18383 (closed)
  • restore console CP on program exit. (after merge) #18384 (closed)

There's a couple of reasons we can't support FDs as they are now:

  • The handle associated with them may not have been created with the right async flags.
  • The handle associated with them would not have been assigned to the correct IOCP port.
  • The handle associated with them wouldn't have the right optimization flag set.
  • If the handle points to a socket the I/O manager can't handle it correctly.
  • FDs are synthetic on Windows. as in, each process contains it's own unique FD mapping, you can't pass one from one process to another.
  • They're inefficient, we'd have to constantly cast to handle to use them.

So this requires a wholesale migration from FDs to HANDLE. 99% of Haskell packages won't have an issue here as they use the GHC provided Handle abstraction. But libraries that use the internal GHC. interfaces do need some changes.

Because of this we have to keep the old I/O manager around while things catch up. But you can't use the old and new managers together at the same time. This will cause a segfault as one won't be running.

-- Bugs list --

Fixes #18307 (closed), #17035 (closed), #16917 (closed), #15366 (closed), #14530 (closed), #13516 (closed), #13396 (closed), #13359 (closed), #12873 (closed), #12869 (closed), #11394 (closed), #10542 (closed), #10484 (closed), #10477 (closed), #9940 (closed), #7593 (closed), #7353 (closed), #5797 (closed), #5305 (closed), #4471 (closed), #3937 (closed), #3081 (closed)

I believe also fixes #12117 (closed) but not sure what the exact behavior should be. timers elapse and program no longer hangs but console is written to until key is pressed and getLine returns. Not sure what expected behavior is but hang is gone. May help #12714 (closed). Fixes #10956 (closed), #2189 (closed) but only on native windows terminals (since msys terminals are not tty). Closes #2408 (closed) because that interface can't be properly supported. hWaitForInput should work though. Fixes #806 (closed) (hGetBufNonBlocking is now supported).

Edited by Tamar Christina

Merge request reports