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 Vistasupport- 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
EventsandMonitor lockswith much faster and efficientConditional VariablesandSlimReaderWriterLocks. - 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
100nsbut theelapsedpart has100msmaking them still unusable for the resolution the RTS wants for profiling. I wonder if I can chain multiple timers together till theelapsedresolution. - 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 PrimOpand implementAtomic Ptrexchanges. - Flush event logs eagerly as to not rely on finalizers running.
- A lot of refactoring and more use of
hsc2hsto share constants - Control aborts
Ctrl+Cshould be a bit more reliable. - Add a new
IOPortprimitive that should be only used for these I/O operations. Essentially anIOPortis based on anMVarwith 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
networklibrary. - 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
IOPortto re-use as much fromMVaras 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).