New native Windows I/O manager WinIO (Windows I/O)
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 isWindows 7
. This allows us to use much more efficient OS provided abstractions.
- Vista is out of extended support as of
- Replace
Events
andMonitor locks
with much faster and efficientConditional Variables
andSlimReaderWriterLocks
. - 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 theelapsed
part has100ms
making them still unusable for the resolution the RTS wants for profiling. I wonder if I can chain multiple timers together till theelapsed
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 implementAtomic 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 anIOPort
is based on anMVar
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 fromMVar
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).