Duncan Coutts pushed to branch wip/dcoutts/io-manager-io-primop-exceptions at Glasgow Haskell Compiler / GHC
Commits:
-
7defd56e
by Duncan Coutts at 2026-09-01T11:15:41+01:00
-
67846132
by Duncan Coutts at 2026-09-01T11:15:42+01:00
-
dc074de3
by Duncan Coutts at 2026-09-01T11:15:42+01:00
-
f4e12d5e
by Duncan Coutts at 2026-09-01T11:15:42+01:00
-
c34dcf3a
by Duncan Coutts at 2026-09-01T11:15:42+01:00
-
c10ec943
by Duncan Coutts at 2026-09-01T11:15:42+01:00
14 changed files:
- compiler/GHC/Builtin/primops.txt.pp
- libraries/ghc-internal/include/RtsIfaceSymbols.h
- libraries/ghc-internal/src/GHC/Internal/Event/Thread.hs
- rts/HeapStackCheck.cmm
- rts/IOManager.c
- rts/IOManager.h
- rts/IOManagerInternals.h
- rts/Prelude.h
- rts/PrimOps.cmm
- rts/include/rts/RtsToHsIface.h
- rts/include/stg/MiscClosures.h
- rts/posix/Poll.c
- rts/posix/Poll.h
- rts/posix/Select.c
Changes:
| ... | ... | @@ -3305,21 +3305,28 @@ section "Delay/wait operations" |
| 3305 | 3305 | |
| 3306 | 3306 | primop DelayOp "delay#" GenPrimOp
|
| 3307 | 3307 | Int# -> State# s -> State# s
|
| 3308 | - {Sleep specified number of microseconds.}
|
|
| 3308 | + {Suspend the calling thread for a specified number of microseconds. This
|
|
| 3309 | + operation is /interruptible/ by async exceptions.}
|
|
| 3309 | 3310 | with
|
| 3310 | 3311 | effect = ReadWriteEffect
|
| 3311 | 3312 | out_of_line = True
|
| 3312 | 3313 | |
| 3313 | 3314 | primop WaitReadOp "waitRead#" GenPrimOp
|
| 3314 | 3315 | Int# -> State# s -> State# s
|
| 3315 | - {Block until input is available on specified file descriptor.}
|
|
| 3316 | + {Suspend the calling thread until input is available (or an I/O error
|
|
| 3317 | + occurs) on the given file descriptor. This operation can throw exceptions
|
|
| 3318 | + of type 'IOException'. The operation is also /interruptible/ by async
|
|
| 3319 | + exceptions.}
|
|
| 3316 | 3320 | with
|
| 3317 | 3321 | effect = ReadWriteEffect
|
| 3318 | 3322 | out_of_line = True
|
| 3319 | 3323 | |
| 3320 | 3324 | primop WaitWriteOp "waitWrite#" GenPrimOp
|
| 3321 | 3325 | Int# -> State# s -> State# s
|
| 3322 | - {Block until output is possible on specified file descriptor.}
|
|
| 3326 | + {Suspend the calling thread until output is possible (or an I/O error
|
|
| 3327 | + occurs) on the given file descriptor. This operation can throw exceptions
|
|
| 3328 | + of type 'IOException'. The operation is also /interruptible/ by async
|
|
| 3329 | + exceptions.}
|
|
| 3323 | 3330 | with
|
| 3324 | 3331 | effect = ReadWriteEffect
|
| 3325 | 3332 | out_of_line = True
|
| ... | ... | @@ -23,11 +23,6 @@ CLOSURE(GHCziInternalziIOziException, cannotCompactMutable_closure) |
| 23 | 23 | CLOSURE(GHCziInternalziControlziExceptionziBase, nonTermination_closure)
|
| 24 | 24 | CLOSURE(GHCziInternalziControlziExceptionziBase, nestedAtomically_closure)
|
| 25 | 25 | CLOSURE(GHCziInternalziControlziExceptionziBase, noMatchingContinuationPrompt_closure)
|
| 26 | -#if defined(mingw32_HOST_OS)
|
|
| 27 | -UNDEF_CLOSURE(GHCziInternalziEventziThread, blockedOnBadFD_closure)
|
|
| 28 | -#else
|
|
| 29 | -CLOSURE(GHCziInternalziEventziThread, blockedOnBadFD_closure)
|
|
| 30 | -#endif
|
|
| 31 | 26 | CLOSURE(GHCziInternalziConcziSync, runSparks_closure)
|
| 32 | 27 | CLOSURE(GHCziInternalziConcziIO, ensureIOManagerIsRunning_closure)
|
| 33 | 28 | CLOSURE(GHCziInternalziConcziIO, interruptIOManager_closure)
|
| ... | ... | @@ -23,14 +23,13 @@ import GHC.Internal.Types () |
| 23 | 23 | , closeFdWith
|
| 24 | 24 | , threadDelay
|
| 25 | 25 | , registerDelay
|
| 26 | - , blockedOnBadFD -- used by RTS
|
|
| 27 | 26 | ) where
|
| 28 | 27 | |
| 29 | 28 | |
| 30 | 29 | -- TODO: Use new Windows I/O manager
|
| 31 | 30 | import qualified GHC.Internal.Stack.Types as Rebindable
|
| 32 | 31 | import GHC.Internal.Base
|
| 33 | -import GHC.Internal.Control.Exception (finally, SomeException, toException)
|
|
| 32 | +import GHC.Internal.Control.Exception (finally)
|
|
| 34 | 33 | import GHC.Internal.Data.Foldable (forM_, mapM_, sequence_)
|
| 35 | 34 | import GHC.Internal.Data.IORef (IORef, newIORef, readIORef, writeIORef, atomicWriteIORef)
|
| 36 | 35 | import GHC.Internal.Data.Maybe (fromMaybe)
|
| ... | ... | @@ -185,10 +184,6 @@ threadWait evt fd = mask_ $ do |
| 185 | 184 | then ioError $ errnoToIOError "threadWait" eBADF Nothing Nothing
|
| 186 | 185 | else return ()
|
| 187 | 186 | |
| 188 | --- used at least by RTS in 'select()' IO manager backend
|
|
| 189 | -blockedOnBadFD :: SomeException
|
|
| 190 | -blockedOnBadFD = toException $ errnoToIOError "awaitEvent" eBADF Nothing Nothing
|
|
| 191 | - |
|
| 192 | 187 | threadWaitSTM :: Event -> Fd -> IO (STM (), IO ())
|
| 193 | 188 | threadWaitSTM evt fd = mask_ $ do
|
| 194 | 189 | m <- newTVarIO Nothing
|
| ... | ... | @@ -40,6 +40,8 @@ import CLOSURE stg_ret_l_info; |
| 40 | 40 | import CLOSURE stg_ret_n_info;
|
| 41 | 41 | import CLOSURE stg_ret_p_info;
|
| 42 | 42 | import CLOSURE stg_stack_save_entries;
|
| 43 | +import CLOSURE stg_block_io_unit_info;
|
|
| 44 | +import CLOSURE stg_block_io_int_info;
|
|
| 43 | 45 | #endif
|
| 44 | 46 | |
| 45 | 47 | /* Stack/Heap Check Failure
|
| ... | ... | @@ -791,3 +793,169 @@ stg_block_async |
| 791 | 793 | }
|
| 792 | 794 | #endif
|
| 793 | 795 | |
| 796 | +/* Note [Thread blocking for new I/O primops]
|
|
| 797 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 798 | + |
|
| 799 | +The classic I/O primops are:
|
|
| 800 | + |
|
| 801 | + waitRead#, waitWrite# :: Int# -> State# s -> State# s
|
|
| 802 | + |
|
| 803 | +Note that they have no result, with wrapper types returning IO (). Each one
|
|
| 804 | +simply suspends the calling thread until a condition occurs (I/O or time).
|
|
| 805 | + |
|
| 806 | +Despite this external simplicity, internally there is more complexity with a
|
|
| 807 | +number of cases to handle:
|
|
| 808 | + |
|
| 809 | +At submission time:
|
|
| 810 | + - synchronous failure, e.g. bad fd
|
|
| 811 | + - synchronous success, e.g. waiting on a regular file, which is always ready
|
|
| 812 | + - proceed asynchronously: block thread, return to scheduler
|
|
| 813 | + |
|
| 814 | +Later, at I/O completion, I/O error, or async exception time:
|
|
| 815 | + - reported failure, e.g. stale fd
|
|
| 816 | + - success
|
|
| 817 | + |
|
| 818 | +The waitRead/Write# also have non-reported failures, such as I/O errors,
|
|
| 819 | +but these are treated as the success case since these primops are defined to
|
|
| 820 | +wait for I/O or an error. We only report failures, via exceptions, for cases
|
|
| 821 | +where it would be unsafe to proceed with a subsequent I/O operation on the
|
|
| 822 | +same fd. In particular this includes when the fd was closed while a thread was
|
|
| 823 | +waiting on the fd (since it's very unsafe to read/write an fd that is closed or
|
|
| 824 | +worse: has been recycled to refer to a different file).
|
|
| 825 | + |
|
| 826 | +The synchronous success and failure cases are handled by the initiating
|
|
| 827 | +primops. In the blocking case, it relies on one of these stg_block_ functions,
|
|
| 828 | +stack frames, and stack frame info table entry code. The key bits of the
|
|
| 829 | +mechanism are:
|
|
| 830 | + |
|
| 831 | + - The stg_block_ cmm function sets up the stack frame for the blocked thread.
|
|
| 832 | + - The stack frame entries can be overwritten by the I/O manager, e.g. upon I/O
|
|
| 833 | + completion or error.
|
|
| 834 | + - The stack frame's info table entry code is invoked when the thread is
|
|
| 835 | + resumed.
|
|
| 836 | + - The entries in the stack frame become arguments to the info table entry code.
|
|
| 837 | + - The result(s) returned by the info table entry code _are_ the result(s) of
|
|
| 838 | + the I/O primop.
|
|
| 839 | + |
|
| 840 | +It is important to keep clear the distinction between the stack frame entries
|
|
| 841 | +and the final primop result(s): the stack frame's info table entry code
|
|
| 842 | +transforms the former into the latter.
|
|
| 843 | + |
|
| 844 | +The convention we follow is that the stack frame contains two fields:
|
|
| 845 | + - an outcome, using IOOpOutcome values: in-flight, success, failed, cancelled.
|
|
| 846 | + - a result or error code. For outcome success it is a result. For failed it is
|
|
| 847 | + the error code (using posix errno codes), while for cancelled and in-flight
|
|
| 848 | + outcomes there is no result.
|
|
| 849 | + |
|
| 850 | +This outcome+result/failure encoding is exactly the same as the corresponding
|
|
| 851 | +fields in the StgAsyncIOOp. One day we might just use the StgAsyncIOOp itself,
|
|
| 852 | +once all I/O managers use them.
|
|
| 853 | + |
|
| 854 | +The waitRead/waitWrite# primops do not use a result, but later I/O primops will
|
|
| 855 | +do. In particular read/write on files return a length, and other I/O primitives
|
|
| 856 | +also have a single result.
|
|
| 857 | + |
|
| 858 | +So this gives us two block functions and corresponding info tables:
|
|
| 859 | + |
|
| 860 | + 1. stg_block_io_unit: with a stack frame containing outcome+result/failure.
|
|
| 861 | + On resume, throw exception on failure and otherwise return no args.
|
|
| 862 | + This is for primops with result IO ().
|
|
| 863 | + |
|
| 864 | + 2. stg_block_io_int: with a stack frame containing outcome+result/failure.
|
|
| 865 | + On resume, throw exception on failure and otherwise return result.
|
|
| 866 | + This is for primops with result IO Int or IO Word.
|
|
| 867 | + |
|
| 868 | + 3. stg_block_io_aiop: in future if we add async I/O primitives that return an
|
|
| 869 | + StgAsyncIOOp we would also need versions that return that (since even
|
|
| 870 | + submitting async I/O can block if there's too much I/O in flight).
|
|
| 871 | + |
|
| 872 | +The stack frame layout used is:
|
|
| 873 | + Sp[1] stg_block_io_unit_info or stg_block_io_int_info
|
|
| 874 | + Sp[1] outcome
|
|
| 875 | + Sp[2] result or error
|
|
| 876 | + |
|
| 877 | +C code should use setTsoIOOpOutcome to set this. But update this if the layout
|
|
| 878 | +changes!
|
|
| 879 | + |
|
| 880 | + |
|
| 881 | +Note [Calling convention for raisePrimIOException]
|
|
| 882 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 883 | + |
|
| 884 | +In cases 1 & 2 we check the outcome and, on failure, we throw an exception. Our
|
|
| 885 | +approach is to use a helper function implemeted in generated Haskell code, in
|
|
| 886 | +ghc-internal. We need to perform a tail call to this helper function:
|
|
| 887 | + |
|
| 888 | + raisePrimIOException :: Int# -> State# s -> (# State# s, a #)
|
|
| 889 | + |
|
| 890 | +The calling convention for top-level generated Haskell function like this uses
|
|
| 891 | +R2 because it has two arguments -- the Int# and State# -- but the State#
|
|
| 892 | +argument has a zero-width repesentation. The cmm call uses R1, R2, ... etc so
|
|
| 893 | +to pass in R2 we have to use a dummy first argument which will be passed in R1
|
|
| 894 | +(and ignored).
|
|
| 895 | +*/
|
|
| 896 | + |
|
| 897 | +/* stg_block_io_unit_info : the return info table for stg_block_io_unit */
|
|
| 898 | +INFO_TABLE_RET ( stg_block_io_unit, RET_SMALL, W_ info_ptr,
|
|
| 899 | + W_ outcome, W_ result )
|
|
| 900 | + return ()
|
|
| 901 | +{
|
|
| 902 | + if (outcome == 2 :: W_ /*IOOpOutcomeFailed*/) (likely: False) {
|
|
| 903 | + /* See Note [Calling convention for raisePrimIOException] */
|
|
| 904 | + W_ dummyR1;
|
|
| 905 | + dummyR1 = 0;
|
|
| 906 | + jump %ENTRY_CODE(HsIface_raisePrimIOException_info(W_[ghc_hs_iface]))
|
|
| 907 | + (dummyR1, result);
|
|
| 908 | + } else {
|
|
| 909 | + /* success or cancelled outcomes */
|
|
| 910 | + return ();
|
|
| 911 | + }
|
|
| 912 | +}
|
|
| 913 | + |
|
| 914 | +/* Blocking for I/O primops with result IO ().
|
|
| 915 | + * See Note [Thread blocking for new I/O primops].
|
|
| 916 | + */
|
|
| 917 | +stg_block_io_unit
|
|
| 918 | +{
|
|
| 919 | + /* Fill out the stack frame with dummy values. The IO manager will
|
|
| 920 | + * overwrite these with the actual results when the I/O operation
|
|
| 921 | + * completes, fails, or is cancelled.
|
|
| 922 | + */
|
|
| 923 | + Sp_adj(-3);
|
|
| 924 | + Sp(0) = stg_block_io_unit_info;
|
|
| 925 | + Sp(1) = 0; // outcome: 0 indicates IOOpOutcomeInFlight
|
|
| 926 | + Sp(2) = 0; // result/errno
|
|
| 927 | + BLOCK_GENERIC;
|
|
| 928 | +}
|
|
| 929 | + |
|
| 930 | +/* stg_block_io_int_info : the return info table for stg_block_io_int */
|
|
| 931 | +INFO_TABLE_RET ( stg_block_io_int, RET_SMALL, W_ info_ptr,
|
|
| 932 | + W_ outcome, W_ result )
|
|
| 933 | + return ()
|
|
| 934 | +{
|
|
| 935 | + if (outcome == 2 :: W_ /*IOOpOutcomeFailed*/) (likely: False) {
|
|
| 936 | + /* See Note [Calling convention for raisePrimIOException] */
|
|
| 937 | + W_ dummyR1;
|
|
| 938 | + dummyR1 = 0;
|
|
| 939 | + jump %ENTRY_CODE(HsIface_raisePrimIOException_info(W_[ghc_hs_iface]))
|
|
| 940 | + (dummyR1, result);
|
|
| 941 | + } else {
|
|
| 942 | + /* success or cancelled outcomes */
|
|
| 943 | + return (result);
|
|
| 944 | + }
|
|
| 945 | +}
|
|
| 946 | + |
|
| 947 | +/* Blocking for I/O primops with result IO Int or Word (or rather Int#/Word#).
|
|
| 948 | + * See Note [Thread blocking for new I/O primops].
|
|
| 949 | + */
|
|
| 950 | +stg_block_io_int
|
|
| 951 | +{
|
|
| 952 | + /* Fill out the stack frame with dummy values. The IO manager will
|
|
| 953 | + * overwrite these with the actual results when the I/O operation
|
|
| 954 | + * completes, fails, or is cancelled.
|
|
| 955 | + */
|
|
| 956 | + Sp_adj(-3);
|
|
| 957 | + Sp(0) = stg_block_io_int_info;
|
|
| 958 | + Sp(1) = 0; // outcome: 0 indicates IOOpOutcomeInFlight
|
|
| 959 | + Sp(2) = 0; // result/errno
|
|
| 960 | + BLOCK_GENERIC;
|
|
| 961 | +} |
| ... | ... | @@ -399,21 +399,6 @@ void startIOManager(void) |
| 399 | 399 | |
| 400 | 400 | switch (iomgr_type) {
|
| 401 | 401 | |
| 402 | -#if defined(IOMGR_ENABLED_SELECT) || defined(IOMGR_ENABLED_POLL)
|
|
| 403 | -#if defined(IOMGR_ENABLED_SELECT)
|
|
| 404 | - case IO_MANAGER_SELECT:
|
|
| 405 | -#endif
|
|
| 406 | -#if defined(IOMGR_ENABLED_POLL)
|
|
| 407 | - case IO_MANAGER_POLL:
|
|
| 408 | -#endif
|
|
| 409 | - /* Make the exception CAF a GC root. See initBuiltinGcRoots for
|
|
| 410 | - * similar examples. We throw this exception if a thread tries to
|
|
| 411 | - * wait on an invalid FD.
|
|
| 412 | - */
|
|
| 413 | - getStablePtr((StgPtr)blockedOnBadFD_closure);
|
|
| 414 | - break;
|
|
| 415 | -#endif
|
|
| 416 | - |
|
| 417 | 402 | #if defined(IOMGR_ENABLED_MIO_POSIX)
|
| 418 | 403 | case IO_MANAGER_MIO_POSIX:
|
| 419 | 404 | /* Posix implementation in posix/Signals.c
|
| ... | ... | @@ -776,10 +761,10 @@ void interruptIOManager(CapIOManager *iomgr) |
| 776 | 761 | |
| 777 | 762 | |
| 778 | 763 | /* CMM primop. Result is true on success, or false on allocation failure. */
|
| 779 | -bool syncIOWaitReady(CapIOManager *iomgr,
|
|
| 780 | - StgTSO *tso,
|
|
| 781 | - IOReadOrWrite rw,
|
|
| 782 | - HsInt fd)
|
|
| 764 | +IOSubmitResult syncIOWaitReady(CapIOManager *iomgr,
|
|
| 765 | + StgTSO *tso,
|
|
| 766 | + IOReadOrWrite rw,
|
|
| 767 | + HsInt fd)
|
|
| 783 | 768 | {
|
| 784 | 769 | debugTrace(DEBUG_iomanager,
|
| 785 | 770 | "thread %ld waiting for %s I/O readiness on fd %d",
|
| ... | ... | @@ -795,7 +780,7 @@ bool syncIOWaitReady(CapIOManager *iomgr, |
| 795 | 780 | tso->block_info.fd = fd;
|
| 796 | 781 | appendToIOBlockedQueue(iomgr, tso);
|
| 797 | 782 | RELEASE_STORE(&tso->why_blocked, why_blocked);
|
| 798 | - return true;
|
|
| 783 | + return IOSubmitResultAsyncContinue;
|
|
| 799 | 784 | }
|
| 800 | 785 | #endif
|
| 801 | 786 | #if defined(IOMGR_ENABLED_POLL)
|
| ... | ... | @@ -325,8 +325,33 @@ typedef enum { IORead = 0, IOWrite = 1 } IOReadOrWrite; |
| 325 | 325 | * false on heap allocation failure.
|
| 326 | 326 | */
|
| 327 | 327 | |
| 328 | +/* Note [Encoding of result of I/O manager operations]
|
|
| 329 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 330 | +We have quite a bit of information that to return from the I/O manager calls
|
|
| 331 | +back to the I/O primops. It (somewhat uncomfortably) fits into a C int.
|
|
| 332 | + |
|
| 333 | +We use the following encoding of the result:
|
|
| 334 | + |
|
| 335 | + * negative: synchronous failure, with -errno as the result.
|
|
| 336 | + * 0: ok, result successful, proceed to async: suspend the TSO.
|
|
| 337 | + * 1: synchronous success, return without suspending the TSO. This can occur
|
|
| 338 | + for example when waiting on readiness of a regular file (which is always
|
|
| 339 | + ready).
|
|
| 340 | + * 2: heap overflow
|
|
| 341 | + * >2: unallocated status codes
|
|
| 342 | +*/
|
|
| 343 | +typedef int IOSubmitResult;
|
|
| 344 | + |
|
| 345 | +/* Provide constants for IOSubmitResult */
|
|
| 346 | +enum IOSubmitResultCodes {
|
|
| 347 | + /* negative numbers are -errno error codes */
|
|
| 348 | + IOSubmitResultAsyncContinue = 0,
|
|
| 349 | + IOSubmitResultSyncSuccess = 1,
|
|
| 350 | + IOSubmitResultHeapOverflow = 2
|
|
| 351 | +};
|
|
| 352 | + |
|
| 328 | 353 | /* Called from CMM primop */
|
| 329 | -bool syncIOWaitReady(CapIOManager *iomgr, StgTSO *tso, IOReadOrWrite rw, HsInt fd);
|
|
| 354 | +IOSubmitResult syncIOWaitReady(CapIOManager *iomgr, StgTSO *tso, IOReadOrWrite rw, HsInt fd);
|
|
| 330 | 355 | |
| 331 | 356 | void syncIOCancel(CapIOManager *iomgr, StgTSO *tso);
|
| 332 | 357 |
| ... | ... | @@ -81,5 +81,25 @@ struct _CapIOManager { |
| 81 | 81 | |
| 82 | 82 | };
|
| 83 | 83 | |
| 84 | +/* Fill in the outcome and result/error on the TSO's stack frame.
|
|
| 85 | + * The TSO must be blocked using one of the stg_block_io_* blocking actions.
|
|
| 86 | + *
|
|
| 87 | + * Once the thread is resumed (by the scheduler) the code for stg_block_*_info
|
|
| 88 | + * will pick up the info from the stack frame and use it to see if there's been
|
|
| 89 | + * a failure, and if so to raise a PrimIOException.
|
|
| 90 | + *
|
|
| 91 | + * See Note [Thread blocking for new I/O primops].
|
|
| 92 | + */
|
|
| 93 | +INLINE_HEADER void setTsoIOOpOutcome (StgTSO *tso,
|
|
| 94 | + enum IOOpOutcome outcome,
|
|
| 95 | + uint32_t result)
|
|
| 96 | +{
|
|
| 97 | + ASSERT((StgPtr *)tso->stackobj->sp[0] == (StgPtr *)&stg_block_io_unit_info
|
|
| 98 | + || (StgPtr *)tso->stackobj->sp[0] == (StgPtr *)&stg_block_io_int_info);
|
|
| 99 | + |
|
| 100 | + tso->stackobj->sp[1] = (W_)outcome;
|
|
| 101 | + tso->stackobj->sp[2] = (W_)result;
|
|
| 102 | +}
|
|
| 103 | + |
|
| 84 | 104 | #include "EndPrivate.h"
|
| 85 | 105 |
| ... | ... | @@ -65,8 +65,6 @@ extern StgClosure ZCMain_main_closure; |
| 65 | 65 | #define overflowException_closure ghc_hs_iface->overflowException_closure
|
| 66 | 66 | #define divZeroException_closure ghc_hs_iface->divZZeroException_closure
|
| 67 | 67 | |
| 68 | -#define blockedOnBadFD_closure ghc_hs_iface->blockedOnBadFD_closure
|
|
| 69 | - |
|
| 70 | 68 | #define Czh_con_info ghc_hs_iface->Czh_con_info
|
| 71 | 69 | #define Izh_con_info ghc_hs_iface->Izh_con_info
|
| 72 | 70 | #define Fzh_con_info ghc_hs_iface->Fzh_con_info
|
| ... | ... | @@ -2297,32 +2297,53 @@ stg_whereFromzh (P_ clos, W_ buf) |
| 2297 | 2297 | Thread I/O blocking primitives
|
| 2298 | 2298 | -------------------------------------------------------------------------- */
|
| 2299 | 2299 | |
| 2300 | -stg_waitReadzh ( W_ fd )
|
|
| 2300 | +stg_waitReadyFd ( W_ fd, CInt rw )
|
|
| 2301 | 2301 | {
|
| 2302 | - CBool ok; /* Ok, or heap alloc failure. */
|
|
| 2302 | + CInt result;
|
|
| 2303 | 2303 | |
| 2304 | - (ok) = ccall syncIOWaitReady(Capability_iomgr(MyCapability()) "ptr",
|
|
| 2305 | - CurrentTSO "ptr",
|
|
| 2306 | - /* IORead */ 0::CInt, fd);
|
|
| 2307 | - if (ok != 0::CBool) (likely: True) {
|
|
| 2308 | - jump stg_block_noregs();
|
|
| 2309 | - } else {
|
|
| 2304 | + (result) = ccall syncIOWaitReady(Capability_iomgr(MyCapability()) "ptr",
|
|
| 2305 | + CurrentTSO "ptr", rw, fd);
|
|
| 2306 | + |
|
| 2307 | + /* See Note [Encoding of result of I/O manager operations] */
|
|
| 2308 | + |
|
| 2309 | + /* case IOSubmitResultAsyncContinue */
|
|
| 2310 | + if (result == 0::CInt) (likely: True) {
|
|
| 2311 | + /* See Note [Thread blocking for new I/O primops] */
|
|
| 2312 | + jump stg_block_io_unit();
|
|
| 2313 | + }
|
|
| 2314 | + |
|
| 2315 | + /* case IOSubmitResultSyncSuccess*/
|
|
| 2316 | + if (result == 1::CInt) {
|
|
| 2317 | + /* Success, don't even go via scheduler. */
|
|
| 2318 | + return ();
|
|
| 2319 | + }
|
|
| 2320 | + |
|
| 2321 | + /* negative numbers are -errno error codes */
|
|
| 2322 | + if (result < 0::CInt) {
|
|
| 2323 | + /* See Note [Calling convention for raisePrimIOException] */
|
|
| 2324 | + W_ dummyR1; W_ errno;
|
|
| 2325 | + dummyR1 = 0;
|
|
| 2326 | + errno = TO_W_(-result);
|
|
| 2327 | + jump %ENTRY_CODE(HsIface_raisePrimIOException_info(W_[ghc_hs_iface]))
|
|
| 2328 | + (dummyR1, errno);
|
|
| 2329 | + }
|
|
| 2330 | + |
|
| 2331 | + /* case IOSubmitResultHeapOverflow */
|
|
| 2332 | + if (result == 2::CInt) {
|
|
| 2310 | 2333 | jump stg_raisezh(HsIface_heapOverflow_closure(W_[ghc_hs_iface]));
|
| 2334 | + |
|
| 2311 | 2335 | }
|
| 2336 | + ccall sbarf("syncIOWaitReady result encoding error") never returns;
|
|
| 2312 | 2337 | }
|
| 2313 | 2338 | |
| 2314 | -stg_waitWritezh ( W_ fd )
|
|
| 2339 | +stg_waitReadzh ( W_ fd )
|
|
| 2315 | 2340 | {
|
| 2316 | - CBool ok; /* Ok, or heap alloc failure. */
|
|
| 2341 | + jump stg_waitReadyFd(fd, /* IORead */ 0::CInt);
|
|
| 2342 | +}
|
|
| 2317 | 2343 | |
| 2318 | - (ok) = ccall syncIOWaitReady(Capability_iomgr(MyCapability()) "ptr",
|
|
| 2319 | - CurrentTSO "ptr",
|
|
| 2320 | - /* IOWrite */ 1::CInt, fd);
|
|
| 2321 | - if (ok != 0::CBool) (likely: True) {
|
|
| 2322 | - jump stg_block_noregs();
|
|
| 2323 | - } else {
|
|
| 2324 | - jump stg_raisezh(HsIface_heapOverflow_closure(W_[ghc_hs_iface]));
|
|
| 2325 | - }
|
|
| 2344 | +stg_waitWritezh ( W_ fd )
|
|
| 2345 | +{
|
|
| 2346 | + jump stg_waitReadyFd(fd, /* IOWrite */ 1::CInt);
|
|
| 2326 | 2347 | }
|
| 2327 | 2348 | |
| 2328 | 2349 | stg_delayzh ( W_ us_delay )
|
| ... | ... | @@ -28,7 +28,6 @@ typedef struct { |
| 28 | 28 | StgClosure *nonTermination_closure; // GHC.Internal.Control.Exception.Base.nonTermination_closure
|
| 29 | 29 | StgClosure *nestedAtomically_closure; // GHC.Internal.Control.Exception.Base.nestedAtomically_closure
|
| 30 | 30 | StgClosure *noMatchingContinuationPrompt_closure; // GHC.Internal.Control.Exception.Base.noMatchingContinuationPrompt_closure
|
| 31 | - StgClosure *blockedOnBadFD_closure; // GHC.Internal.Event.Thread.blockedOnBadFD_closure
|
|
| 32 | 31 | StgClosure *raisePrimIOException_info; // GHC.Internal.Conc.IO.raisePrimIOException_info
|
| 33 | 32 | StgClosure *runSparks_closure; // GHC.Internal.Conc.Sync.runSparks_closure
|
| 34 | 33 | StgClosure *ensureIOManagerIsRunning_closure; // GHC.Internal.Conc.IO.ensureIOManagerIsRunning_closure
|
| ... | ... | @@ -386,6 +386,10 @@ RTS_FUN_DECL(stg_block_throwto); |
| 386 | 386 | RTS_RET(stg_block_throwto);
|
| 387 | 387 | |
| 388 | 388 | /* Blocking for I/O primops */
|
| 389 | +RTS_FUN_DECL(stg_block_io_unit);
|
|
| 390 | +RTS_RET(stg_block_io_unit);
|
|
| 391 | +RTS_FUN_DECL(stg_block_io_int);
|
|
| 392 | +RTS_RET(stg_block_io_int);
|
|
| 389 | 393 | #if defined(mingw32_HOST_OS)
|
| 390 | 394 | RTS_FUN_DECL(stg_block_async);
|
| 391 | 395 | RTS_RET(stg_block_async);
|
| ... | ... | @@ -20,7 +20,6 @@ |
| 20 | 20 | #include "Prelude.h"
|
| 21 | 21 | #include "RtsUtils.h"
|
| 22 | 22 | #include "rts/Time.h"
|
| 23 | -#include "RaiseAsync.h"
|
|
| 24 | 23 | #include "Trace.h"
|
| 25 | 24 | |
| 26 | 25 | #include "Poll.h"
|
| ... | ... | @@ -171,14 +170,13 @@ void freeCapabilityIOManagerPoll(CapIOManager *iomgr) |
| 171 | 170 | }
|
| 172 | 171 | |
| 173 | 172 | |
| 174 | -/* Used to implement syncIOWaitReady.
|
|
| 175 | - * Result is true on success, or false on allocation failure. */
|
|
| 176 | -bool syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso,
|
|
| 177 | - IOReadOrWrite rw, HsInt fd)
|
|
| 173 | +/* Used to implement syncIOWaitReady. */
|
|
| 174 | +IOSubmitResult syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso,
|
|
| 175 | + IOReadOrWrite rw, HsInt fd)
|
|
| 178 | 176 | {
|
| 179 | 177 | StgAsyncIOOp *aiop;
|
| 180 | 178 | aiop = (StgAsyncIOOp *)allocateMightFail(iomgr->cap, sizeofW(StgAsyncIOOp));
|
| 181 | - if (RTS_UNLIKELY(aiop == NULL)) return false;
|
|
| 179 | + if (RTS_UNLIKELY(aiop == NULL)) return IOSubmitResultHeapOverflow;
|
|
| 182 | 180 | SET_HDR(aiop, &stg_ASYNCIOOP_info, iomgr->cap->r.rCCCS);
|
| 183 | 181 | aiop->notify.tso = tso;
|
| 184 | 182 | aiop->notify_type = NotifyTSO;
|
| ... | ... | @@ -189,13 +187,12 @@ bool syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso, |
| 189 | 187 | return asyncIOWaitReadyPoll(iomgr, aiop, rw, fd);
|
| 190 | 188 | }
|
| 191 | 189 | |
| 192 | -/* Result is true on success, or false on allocation failure. */
|
|
| 193 | -bool asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop,
|
|
| 194 | - IOReadOrWrite rw, int fd)
|
|
| 190 | +IOSubmitResult asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop,
|
|
| 191 | + IOReadOrWrite rw, int fd)
|
|
| 195 | 192 | {
|
| 196 | 193 | if (RTS_UNLIKELY(isFullClosureTable(&iomgr->aiop_table))) {
|
| 197 | 194 | bool ok = enlargeTables(iomgr);
|
| 198 | - if (RTS_UNLIKELY(!ok)) return false;
|
|
| 195 | + if (RTS_UNLIKELY(!ok)) return IOSubmitResultHeapOverflow;
|
|
| 199 | 196 | }
|
| 200 | 197 | |
| 201 | 198 | int ix = insertClosureTable(iomgr->cap, &iomgr->aiop_table, aiop);
|
| ... | ... | @@ -216,7 +213,7 @@ bool asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop, |
| 216 | 213 | .events = rw == IORead ? POLLIN : POLLOUT,
|
| 217 | 214 | .revents = 0
|
| 218 | 215 | };
|
| 219 | - return true;
|
|
| 216 | + return IOSubmitResultAsyncContinue;
|
|
| 220 | 217 | }
|
| 221 | 218 | |
| 222 | 219 | |
| ... | ... | @@ -226,6 +223,7 @@ void syncIOCancelPoll(CapIOManager *iomgr, StgTSO *tso) |
| 226 | 223 | ASSERT(aiop->notify_type == NotifyTSO);
|
| 227 | 224 | ASSERT(indexClosureTable(&iomgr->aiop_table, aiop->index) == aiop);
|
| 228 | 225 | ioCancel(iomgr, aiop);
|
| 226 | + setTsoIOOpOutcome(tso, aiop->outcome, aiop->result);
|
|
| 229 | 227 | /* We cannot use the normal notifyIOCompletion here. We are in the context
|
| 230 | 228 | * of throwTo, interrupting a thread blocked on IO via an async exception.
|
| 231 | 229 | * We don't put the TSO back on the run queue or change the why_blocked
|
| ... | ... | @@ -284,26 +282,18 @@ static void notifyIOCompletion(CapIOManager *iomgr, StgAsyncIOOp *aiop) |
| 284 | 282 | switch (aiop->notify_type) {
|
| 285 | 283 | case NotifyTSO:
|
| 286 | 284 | {
|
| 287 | - if (aiop->outcome == IOOpOutcomeFailed && aiop->error == EBADF) {
|
|
| 288 | - /* The fd is invalid: raise an IOError exception in the blocked
|
|
| 289 | - * thread. (See bug #4934 for what happens without this.)
|
|
| 290 | - */
|
|
| 291 | - StgTSO *tso = aiop->notify.tso;
|
|
| 292 | - debugTrace(DEBUG_iomanager,
|
|
| 293 | - "Raising exception in thread %" FMT_StgThreadID
|
|
| 294 | - " blocked on an invalid fd", tso->id);
|
|
| 295 | - raiseAsync(iomgr->cap, tso,
|
|
| 296 | - (StgClosure *)blockedOnBadFD_closure,
|
|
| 297 | - false, NULL);
|
|
| 298 | - } else {
|
|
| 299 | - /* We should be guaranteed that the tso is still on the same
|
|
| 300 | - * cap because the tso was not on the run queue of any cap and
|
|
| 301 | - * so is not subject to thread migration.
|
|
| 302 | - */
|
|
| 303 | - StgTSO *tso = aiop->notify.tso;
|
|
| 304 | - pushOnRunQueue(iomgr->cap, tso);
|
|
| 305 | - RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 306 | - }
|
|
| 285 | + /* We should be guaranteed that the tso is still on the same
|
|
| 286 | + * cap because the tso was not on the run queue of any cap and
|
|
| 287 | + * so is not subject to thread migration.
|
|
| 288 | + */
|
|
| 289 | + StgTSO *tso = aiop->notify.tso;
|
|
| 290 | + ASSERT(tso->cap == iomgr->cap);
|
|
| 291 | + |
|
| 292 | + /* Fill in the outcome and result/error on the TSO's stack frame */
|
|
| 293 | + setTsoIOOpOutcome(tso, aiop->outcome, aiop->result);
|
|
| 294 | + pushOnRunQueue(iomgr->cap, tso);
|
|
| 295 | + RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 296 | + |
|
| 307 | 297 | /* For the TSO case, the aiop was only reachable from the TSO
|
| 308 | 298 | * itself, and thus it is now no longer be reachable at all.
|
| 309 | 299 | */
|
| ... | ... | @@ -20,13 +20,13 @@ void initCapabilityIOManagerPoll(CapIOManager *iomgr); |
| 20 | 20 | void freeCapabilityIOManagerPoll(CapIOManager *iomgr);
|
| 21 | 21 | |
| 22 | 22 | /* Synchronous I/O and timer operations */
|
| 23 | -bool syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso,
|
|
| 24 | - IOReadOrWrite rw, HsInt fd);
|
|
| 23 | +IOSubmitResult syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso,
|
|
| 24 | + IOReadOrWrite rw, HsInt fd);
|
|
| 25 | 25 | void syncIOCancelPoll(CapIOManager *iomgr, StgTSO *tso);
|
| 26 | 26 | |
| 27 | 27 | /* Asynchronous operations */
|
| 28 | -bool asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop,
|
|
| 29 | - IOReadOrWrite rw, int fd);
|
|
| 28 | +IOSubmitResult asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop,
|
|
| 29 | + IOReadOrWrite rw, int fd);
|
|
| 30 | 30 | void asyncIOCancelPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop);
|
| 31 | 31 | |
| 32 | 32 | /* Scheduler operations */
|
| ... | ... | @@ -15,7 +15,6 @@ |
| 15 | 15 | #include "Signals.h"
|
| 16 | 16 | #include "Schedule.h"
|
| 17 | 17 | #include "Prelude.h"
|
| 18 | -#include "RaiseAsync.h"
|
|
| 19 | 18 | #include "RtsUtils.h"
|
| 20 | 19 | #include "Capability.h"
|
| 21 | 20 | #include "Select.h"
|
| ... | ... | @@ -480,13 +479,18 @@ awaitCompletedTimeoutsOrIOSelect(CapIOManager *iomgr, bool wait) |
| 480 | 479 | IF_DEBUG(scheduler,
|
| 481 | 480 | debugBelch("Killing blocked thread %" FMT_StgThreadID
|
| 482 | 481 | " on bad fd=%i\n", tso->id, fd));
|
| 483 | - raiseAsync(iomgr->cap, tso,
|
|
| 484 | - (StgClosure *)blockedOnBadFD_closure, false, NULL);
|
|
| 482 | + |
|
| 483 | + /* Fill in the outcome and error on the TSO's stack frame */
|
|
| 484 | + setTsoIOOpOutcome(tso, IOOpOutcomeFailed, EBADF);
|
|
| 485 | + pushOnRunQueue(iomgr->cap,tso);
|
|
| 486 | + RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
|
| 485 | 487 | break;
|
| 486 | 488 | case RTS_FD_IS_READY:
|
| 487 | 489 | IF_DEBUG(scheduler,
|
| 488 | 490 | debugBelch("Waking up blocked thread %" FMT_StgThreadID "\n",
|
| 489 | 491 | tso->id));
|
| 492 | + /* Fill in the outcome and result on the TSO's stack frame */
|
|
| 493 | + setTsoIOOpOutcome(tso, IOOpOutcomeSuccess, 0);
|
|
| 490 | 494 | pushOnRunQueue(iomgr->cap,tso);
|
| 491 | 495 | RELEASE_STORE(&tso->why_blocked, NotBlocked);
|
| 492 | 496 | break;
|