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 Add new blocking functions for I/O primops See the long Note. The point is, it will allow us to report synchronous exceptions from I/O primops, and do so much more flexibly. Previously the I/O managers could only report async exceptions and only nullary exceptions. This was OK historically, but no good as we add more I/O managers and expand the range of I/O operations we support. - - - - - 67846132 by Duncan Coutts at 2026-09-01T11:15:42+01:00 Change the encoding of results from the I/O manager to I/O primops Previously we just had async continue or heap overflow. We now extend what we can report with synchronous success, and synchronous failure with an errno. See Note [Encoding of result of I/O manager operations] We don't use these two new cases yet, but we will. In particular an epoll I/O manager needs to be able to report synchronous success or failure for waitRead#/waitWrite#. - - - - - dc074de3 by Duncan Coutts at 2026-09-01T11:15:42+01:00 Switch waitRead/Write# to use new blocking return frames and update the I/O managers to set the result before resuming the blocked threads. This makes it possible for I/O managers to report synchronous exceptions from the I/O primops, but that will be done in a subsequent commit. - - - - - f4e12d5e by Duncan Coutts at 2026-09-01T11:15:42+01:00 Switch Poll and Select I/O managers to report sync exceptions rather than using raiseAsync with blockedOnBadFD_closure. This uses the new mechanism in the blocking frame return code to report synchronous exceptions. - - - - - c34dcf3a by Duncan Coutts at 2026-09-01T11:15:42+01:00 Remove now-unused blockedOnBadFD It was previously thrown by the select and poll I/O managers, but now they use raisePrimIOException (with an EBADF errno). - - - - - c10ec943 by Duncan Coutts at 2026-09-01T11:15:42+01:00 Improve the docs for delay# waitRead# and waitWrite# Document that the waitRead/Write# can throw exceptions (this was true before too), and that all of them are async exception cancellation points. - - - - - 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: ===================================== compiler/GHC/Builtin/primops.txt.pp ===================================== @@ -3305,21 +3305,28 @@ section "Delay/wait operations" primop DelayOp "delay#" GenPrimOp Int# -> State# s -> State# s - {Sleep specified number of microseconds.} + {Suspend the calling thread for a specified number of microseconds. This + operation is /interruptible/ by async exceptions.} with effect = ReadWriteEffect out_of_line = True primop WaitReadOp "waitRead#" GenPrimOp Int# -> State# s -> State# s - {Block until input is available on specified file descriptor.} + {Suspend the calling thread until input is available (or an I/O error + occurs) on the given file descriptor. This operation can throw exceptions + of type 'IOException'. The operation is also /interruptible/ by async + exceptions.} with effect = ReadWriteEffect out_of_line = True primop WaitWriteOp "waitWrite#" GenPrimOp Int# -> State# s -> State# s - {Block until output is possible on specified file descriptor.} + {Suspend the calling thread until output is possible (or an I/O error + occurs) on the given file descriptor. This operation can throw exceptions + of type 'IOException'. The operation is also /interruptible/ by async + exceptions.} with effect = ReadWriteEffect out_of_line = True ===================================== libraries/ghc-internal/include/RtsIfaceSymbols.h ===================================== @@ -23,11 +23,6 @@ CLOSURE(GHCziInternalziIOziException, cannotCompactMutable_closure) CLOSURE(GHCziInternalziControlziExceptionziBase, nonTermination_closure) CLOSURE(GHCziInternalziControlziExceptionziBase, nestedAtomically_closure) CLOSURE(GHCziInternalziControlziExceptionziBase, noMatchingContinuationPrompt_closure) -#if defined(mingw32_HOST_OS) -UNDEF_CLOSURE(GHCziInternalziEventziThread, blockedOnBadFD_closure) -#else -CLOSURE(GHCziInternalziEventziThread, blockedOnBadFD_closure) -#endif CLOSURE(GHCziInternalziConcziSync, runSparks_closure) CLOSURE(GHCziInternalziConcziIO, ensureIOManagerIsRunning_closure) CLOSURE(GHCziInternalziConcziIO, interruptIOManager_closure) ===================================== libraries/ghc-internal/src/GHC/Internal/Event/Thread.hs ===================================== @@ -23,14 +23,13 @@ import GHC.Internal.Types () , closeFdWith , threadDelay , registerDelay - , blockedOnBadFD -- used by RTS ) where -- TODO: Use new Windows I/O manager import qualified GHC.Internal.Stack.Types as Rebindable import GHC.Internal.Base -import GHC.Internal.Control.Exception (finally, SomeException, toException) +import GHC.Internal.Control.Exception (finally) import GHC.Internal.Data.Foldable (forM_, mapM_, sequence_) import GHC.Internal.Data.IORef (IORef, newIORef, readIORef, writeIORef, atomicWriteIORef) import GHC.Internal.Data.Maybe (fromMaybe) @@ -185,10 +184,6 @@ threadWait evt fd = mask_ $ do then ioError $ errnoToIOError "threadWait" eBADF Nothing Nothing else return () --- used at least by RTS in 'select()' IO manager backend -blockedOnBadFD :: SomeException -blockedOnBadFD = toException $ errnoToIOError "awaitEvent" eBADF Nothing Nothing - threadWaitSTM :: Event -> Fd -> IO (STM (), IO ()) threadWaitSTM evt fd = mask_ $ do m <- newTVarIO Nothing ===================================== rts/HeapStackCheck.cmm ===================================== @@ -40,6 +40,8 @@ import CLOSURE stg_ret_l_info; import CLOSURE stg_ret_n_info; import CLOSURE stg_ret_p_info; import CLOSURE stg_stack_save_entries; +import CLOSURE stg_block_io_unit_info; +import CLOSURE stg_block_io_int_info; #endif /* Stack/Heap Check Failure @@ -791,3 +793,169 @@ stg_block_async } #endif +/* Note [Thread blocking for new I/O primops] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The classic I/O primops are: + + waitRead#, waitWrite# :: Int# -> State# s -> State# s + +Note that they have no result, with wrapper types returning IO (). Each one +simply suspends the calling thread until a condition occurs (I/O or time). + +Despite this external simplicity, internally there is more complexity with a +number of cases to handle: + +At submission time: + - synchronous failure, e.g. bad fd + - synchronous success, e.g. waiting on a regular file, which is always ready + - proceed asynchronously: block thread, return to scheduler + +Later, at I/O completion, I/O error, or async exception time: + - reported failure, e.g. stale fd + - success + +The waitRead/Write# also have non-reported failures, such as I/O errors, +but these are treated as the success case since these primops are defined to +wait for I/O or an error. We only report failures, via exceptions, for cases +where it would be unsafe to proceed with a subsequent I/O operation on the +same fd. In particular this includes when the fd was closed while a thread was +waiting on the fd (since it's very unsafe to read/write an fd that is closed or +worse: has been recycled to refer to a different file). + +The synchronous success and failure cases are handled by the initiating +primops. In the blocking case, it relies on one of these stg_block_ functions, +stack frames, and stack frame info table entry code. The key bits of the +mechanism are: + + - The stg_block_ cmm function sets up the stack frame for the blocked thread. + - The stack frame entries can be overwritten by the I/O manager, e.g. upon I/O + completion or error. + - The stack frame's info table entry code is invoked when the thread is + resumed. + - The entries in the stack frame become arguments to the info table entry code. + - The result(s) returned by the info table entry code _are_ the result(s) of + the I/O primop. + +It is important to keep clear the distinction between the stack frame entries +and the final primop result(s): the stack frame's info table entry code +transforms the former into the latter. + +The convention we follow is that the stack frame contains two fields: + - an outcome, using IOOpOutcome values: in-flight, success, failed, cancelled. + - a result or error code. For outcome success it is a result. For failed it is + the error code (using posix errno codes), while for cancelled and in-flight + outcomes there is no result. + +This outcome+result/failure encoding is exactly the same as the corresponding +fields in the StgAsyncIOOp. One day we might just use the StgAsyncIOOp itself, +once all I/O managers use them. + +The waitRead/waitWrite# primops do not use a result, but later I/O primops will +do. In particular read/write on files return a length, and other I/O primitives +also have a single result. + +So this gives us two block functions and corresponding info tables: + + 1. stg_block_io_unit: with a stack frame containing outcome+result/failure. + On resume, throw exception on failure and otherwise return no args. + This is for primops with result IO (). + + 2. stg_block_io_int: with a stack frame containing outcome+result/failure. + On resume, throw exception on failure and otherwise return result. + This is for primops with result IO Int or IO Word. + + 3. stg_block_io_aiop: in future if we add async I/O primitives that return an + StgAsyncIOOp we would also need versions that return that (since even + submitting async I/O can block if there's too much I/O in flight). + +The stack frame layout used is: + Sp[1] stg_block_io_unit_info or stg_block_io_int_info + Sp[1] outcome + Sp[2] result or error + +C code should use setTsoIOOpOutcome to set this. But update this if the layout +changes! + + +Note [Calling convention for raisePrimIOException] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In cases 1 & 2 we check the outcome and, on failure, we throw an exception. Our +approach is to use a helper function implemeted in generated Haskell code, in +ghc-internal. We need to perform a tail call to this helper function: + + raisePrimIOException :: Int# -> State# s -> (# State# s, a #) + +The calling convention for top-level generated Haskell function like this uses +R2 because it has two arguments -- the Int# and State# -- but the State# +argument has a zero-width repesentation. The cmm call uses R1, R2, ... etc so +to pass in R2 we have to use a dummy first argument which will be passed in R1 +(and ignored). +*/ + +/* stg_block_io_unit_info : the return info table for stg_block_io_unit */ +INFO_TABLE_RET ( stg_block_io_unit, RET_SMALL, W_ info_ptr, + W_ outcome, W_ result ) + return () +{ + if (outcome == 2 :: W_ /*IOOpOutcomeFailed*/) (likely: False) { + /* See Note [Calling convention for raisePrimIOException] */ + W_ dummyR1; + dummyR1 = 0; + jump %ENTRY_CODE(HsIface_raisePrimIOException_info(W_[ghc_hs_iface])) + (dummyR1, result); + } else { + /* success or cancelled outcomes */ + return (); + } +} + +/* Blocking for I/O primops with result IO (). + * See Note [Thread blocking for new I/O primops]. + */ +stg_block_io_unit +{ + /* Fill out the stack frame with dummy values. The IO manager will + * overwrite these with the actual results when the I/O operation + * completes, fails, or is cancelled. + */ + Sp_adj(-3); + Sp(0) = stg_block_io_unit_info; + Sp(1) = 0; // outcome: 0 indicates IOOpOutcomeInFlight + Sp(2) = 0; // result/errno + BLOCK_GENERIC; +} + +/* stg_block_io_int_info : the return info table for stg_block_io_int */ +INFO_TABLE_RET ( stg_block_io_int, RET_SMALL, W_ info_ptr, + W_ outcome, W_ result ) + return () +{ + if (outcome == 2 :: W_ /*IOOpOutcomeFailed*/) (likely: False) { + /* See Note [Calling convention for raisePrimIOException] */ + W_ dummyR1; + dummyR1 = 0; + jump %ENTRY_CODE(HsIface_raisePrimIOException_info(W_[ghc_hs_iface])) + (dummyR1, result); + } else { + /* success or cancelled outcomes */ + return (result); + } +} + +/* Blocking for I/O primops with result IO Int or Word (or rather Int#/Word#). + * See Note [Thread blocking for new I/O primops]. + */ +stg_block_io_int +{ + /* Fill out the stack frame with dummy values. The IO manager will + * overwrite these with the actual results when the I/O operation + * completes, fails, or is cancelled. + */ + Sp_adj(-3); + Sp(0) = stg_block_io_int_info; + Sp(1) = 0; // outcome: 0 indicates IOOpOutcomeInFlight + Sp(2) = 0; // result/errno + BLOCK_GENERIC; +} ===================================== rts/IOManager.c ===================================== @@ -399,21 +399,6 @@ void startIOManager(void) switch (iomgr_type) { -#if defined(IOMGR_ENABLED_SELECT) || defined(IOMGR_ENABLED_POLL) -#if defined(IOMGR_ENABLED_SELECT) - case IO_MANAGER_SELECT: -#endif -#if defined(IOMGR_ENABLED_POLL) - case IO_MANAGER_POLL: -#endif - /* Make the exception CAF a GC root. See initBuiltinGcRoots for - * similar examples. We throw this exception if a thread tries to - * wait on an invalid FD. - */ - getStablePtr((StgPtr)blockedOnBadFD_closure); - break; -#endif - #if defined(IOMGR_ENABLED_MIO_POSIX) case IO_MANAGER_MIO_POSIX: /* Posix implementation in posix/Signals.c @@ -776,10 +761,10 @@ void interruptIOManager(CapIOManager *iomgr) /* CMM primop. Result is true on success, or false on allocation failure. */ -bool syncIOWaitReady(CapIOManager *iomgr, - StgTSO *tso, - IOReadOrWrite rw, - HsInt fd) +IOSubmitResult syncIOWaitReady(CapIOManager *iomgr, + StgTSO *tso, + IOReadOrWrite rw, + HsInt fd) { debugTrace(DEBUG_iomanager, "thread %ld waiting for %s I/O readiness on fd %d", @@ -795,7 +780,7 @@ bool syncIOWaitReady(CapIOManager *iomgr, tso->block_info.fd = fd; appendToIOBlockedQueue(iomgr, tso); RELEASE_STORE(&tso->why_blocked, why_blocked); - return true; + return IOSubmitResultAsyncContinue; } #endif #if defined(IOMGR_ENABLED_POLL) ===================================== rts/IOManager.h ===================================== @@ -325,8 +325,33 @@ typedef enum { IORead = 0, IOWrite = 1 } IOReadOrWrite; * false on heap allocation failure. */ +/* Note [Encoding of result of I/O manager operations] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +We have quite a bit of information that to return from the I/O manager calls +back to the I/O primops. It (somewhat uncomfortably) fits into a C int. + +We use the following encoding of the result: + + * negative: synchronous failure, with -errno as the result. + * 0: ok, result successful, proceed to async: suspend the TSO. + * 1: synchronous success, return without suspending the TSO. This can occur + for example when waiting on readiness of a regular file (which is always + ready). + * 2: heap overflow + * >2: unallocated status codes +*/ +typedef int IOSubmitResult; + +/* Provide constants for IOSubmitResult */ +enum IOSubmitResultCodes { + /* negative numbers are -errno error codes */ + IOSubmitResultAsyncContinue = 0, + IOSubmitResultSyncSuccess = 1, + IOSubmitResultHeapOverflow = 2 +}; + /* Called from CMM primop */ -bool syncIOWaitReady(CapIOManager *iomgr, StgTSO *tso, IOReadOrWrite rw, HsInt fd); +IOSubmitResult syncIOWaitReady(CapIOManager *iomgr, StgTSO *tso, IOReadOrWrite rw, HsInt fd); void syncIOCancel(CapIOManager *iomgr, StgTSO *tso); ===================================== rts/IOManagerInternals.h ===================================== @@ -81,5 +81,25 @@ struct _CapIOManager { }; +/* Fill in the outcome and result/error on the TSO's stack frame. + * The TSO must be blocked using one of the stg_block_io_* blocking actions. + * + * Once the thread is resumed (by the scheduler) the code for stg_block_*_info + * will pick up the info from the stack frame and use it to see if there's been + * a failure, and if so to raise a PrimIOException. + * + * See Note [Thread blocking for new I/O primops]. + */ +INLINE_HEADER void setTsoIOOpOutcome (StgTSO *tso, + enum IOOpOutcome outcome, + uint32_t result) +{ + ASSERT((StgPtr *)tso->stackobj->sp[0] == (StgPtr *)&stg_block_io_unit_info + || (StgPtr *)tso->stackobj->sp[0] == (StgPtr *)&stg_block_io_int_info); + + tso->stackobj->sp[1] = (W_)outcome; + tso->stackobj->sp[2] = (W_)result; +} + #include "EndPrivate.h" ===================================== rts/Prelude.h ===================================== @@ -65,8 +65,6 @@ extern StgClosure ZCMain_main_closure; #define overflowException_closure ghc_hs_iface->overflowException_closure #define divZeroException_closure ghc_hs_iface->divZZeroException_closure -#define blockedOnBadFD_closure ghc_hs_iface->blockedOnBadFD_closure - #define Czh_con_info ghc_hs_iface->Czh_con_info #define Izh_con_info ghc_hs_iface->Izh_con_info #define Fzh_con_info ghc_hs_iface->Fzh_con_info ===================================== rts/PrimOps.cmm ===================================== @@ -2297,32 +2297,53 @@ stg_whereFromzh (P_ clos, W_ buf) Thread I/O blocking primitives -------------------------------------------------------------------------- */ -stg_waitReadzh ( W_ fd ) +stg_waitReadyFd ( W_ fd, CInt rw ) { - CBool ok; /* Ok, or heap alloc failure. */ + CInt result; - (ok) = ccall syncIOWaitReady(Capability_iomgr(MyCapability()) "ptr", - CurrentTSO "ptr", - /* IORead */ 0::CInt, fd); - if (ok != 0::CBool) (likely: True) { - jump stg_block_noregs(); - } else { + (result) = ccall syncIOWaitReady(Capability_iomgr(MyCapability()) "ptr", + CurrentTSO "ptr", rw, fd); + + /* See Note [Encoding of result of I/O manager operations] */ + + /* case IOSubmitResultAsyncContinue */ + if (result == 0::CInt) (likely: True) { + /* See Note [Thread blocking for new I/O primops] */ + jump stg_block_io_unit(); + } + + /* case IOSubmitResultSyncSuccess*/ + if (result == 1::CInt) { + /* Success, don't even go via scheduler. */ + return (); + } + + /* negative numbers are -errno error codes */ + if (result < 0::CInt) { + /* See Note [Calling convention for raisePrimIOException] */ + W_ dummyR1; W_ errno; + dummyR1 = 0; + errno = TO_W_(-result); + jump %ENTRY_CODE(HsIface_raisePrimIOException_info(W_[ghc_hs_iface])) + (dummyR1, errno); + } + + /* case IOSubmitResultHeapOverflow */ + if (result == 2::CInt) { jump stg_raisezh(HsIface_heapOverflow_closure(W_[ghc_hs_iface])); + } + ccall sbarf("syncIOWaitReady result encoding error") never returns; } -stg_waitWritezh ( W_ fd ) +stg_waitReadzh ( W_ fd ) { - CBool ok; /* Ok, or heap alloc failure. */ + jump stg_waitReadyFd(fd, /* IORead */ 0::CInt); +} - (ok) = ccall syncIOWaitReady(Capability_iomgr(MyCapability()) "ptr", - CurrentTSO "ptr", - /* IOWrite */ 1::CInt, fd); - if (ok != 0::CBool) (likely: True) { - jump stg_block_noregs(); - } else { - jump stg_raisezh(HsIface_heapOverflow_closure(W_[ghc_hs_iface])); - } +stg_waitWritezh ( W_ fd ) +{ + jump stg_waitReadyFd(fd, /* IOWrite */ 1::CInt); } stg_delayzh ( W_ us_delay ) ===================================== rts/include/rts/RtsToHsIface.h ===================================== @@ -28,7 +28,6 @@ typedef struct { StgClosure *nonTermination_closure; // GHC.Internal.Control.Exception.Base.nonTermination_closure StgClosure *nestedAtomically_closure; // GHC.Internal.Control.Exception.Base.nestedAtomically_closure StgClosure *noMatchingContinuationPrompt_closure; // GHC.Internal.Control.Exception.Base.noMatchingContinuationPrompt_closure - StgClosure *blockedOnBadFD_closure; // GHC.Internal.Event.Thread.blockedOnBadFD_closure StgClosure *raisePrimIOException_info; // GHC.Internal.Conc.IO.raisePrimIOException_info StgClosure *runSparks_closure; // GHC.Internal.Conc.Sync.runSparks_closure StgClosure *ensureIOManagerIsRunning_closure; // GHC.Internal.Conc.IO.ensureIOManagerIsRunning_closure ===================================== rts/include/stg/MiscClosures.h ===================================== @@ -386,6 +386,10 @@ RTS_FUN_DECL(stg_block_throwto); RTS_RET(stg_block_throwto); /* Blocking for I/O primops */ +RTS_FUN_DECL(stg_block_io_unit); +RTS_RET(stg_block_io_unit); +RTS_FUN_DECL(stg_block_io_int); +RTS_RET(stg_block_io_int); #if defined(mingw32_HOST_OS) RTS_FUN_DECL(stg_block_async); RTS_RET(stg_block_async); ===================================== rts/posix/Poll.c ===================================== @@ -20,7 +20,6 @@ #include "Prelude.h" #include "RtsUtils.h" #include "rts/Time.h" -#include "RaiseAsync.h" #include "Trace.h" #include "Poll.h" @@ -171,14 +170,13 @@ void freeCapabilityIOManagerPoll(CapIOManager *iomgr) } -/* Used to implement syncIOWaitReady. - * Result is true on success, or false on allocation failure. */ -bool syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso, - IOReadOrWrite rw, HsInt fd) +/* Used to implement syncIOWaitReady. */ +IOSubmitResult syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso, + IOReadOrWrite rw, HsInt fd) { StgAsyncIOOp *aiop; aiop = (StgAsyncIOOp *)allocateMightFail(iomgr->cap, sizeofW(StgAsyncIOOp)); - if (RTS_UNLIKELY(aiop == NULL)) return false; + if (RTS_UNLIKELY(aiop == NULL)) return IOSubmitResultHeapOverflow; SET_HDR(aiop, &stg_ASYNCIOOP_info, iomgr->cap->r.rCCCS); aiop->notify.tso = tso; aiop->notify_type = NotifyTSO; @@ -189,13 +187,12 @@ bool syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso, return asyncIOWaitReadyPoll(iomgr, aiop, rw, fd); } -/* Result is true on success, or false on allocation failure. */ -bool asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop, - IOReadOrWrite rw, int fd) +IOSubmitResult asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop, + IOReadOrWrite rw, int fd) { if (RTS_UNLIKELY(isFullClosureTable(&iomgr->aiop_table))) { bool ok = enlargeTables(iomgr); - if (RTS_UNLIKELY(!ok)) return false; + if (RTS_UNLIKELY(!ok)) return IOSubmitResultHeapOverflow; } int ix = insertClosureTable(iomgr->cap, &iomgr->aiop_table, aiop); @@ -216,7 +213,7 @@ bool asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop, .events = rw == IORead ? POLLIN : POLLOUT, .revents = 0 }; - return true; + return IOSubmitResultAsyncContinue; } @@ -226,6 +223,7 @@ void syncIOCancelPoll(CapIOManager *iomgr, StgTSO *tso) ASSERT(aiop->notify_type == NotifyTSO); ASSERT(indexClosureTable(&iomgr->aiop_table, aiop->index) == aiop); ioCancel(iomgr, aiop); + setTsoIOOpOutcome(tso, aiop->outcome, aiop->result); /* We cannot use the normal notifyIOCompletion here. We are in the context * of throwTo, interrupting a thread blocked on IO via an async exception. * 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) switch (aiop->notify_type) { case NotifyTSO: { - if (aiop->outcome == IOOpOutcomeFailed && aiop->error == EBADF) { - /* The fd is invalid: raise an IOError exception in the blocked - * thread. (See bug #4934 for what happens without this.) - */ - StgTSO *tso = aiop->notify.tso; - debugTrace(DEBUG_iomanager, - "Raising exception in thread %" FMT_StgThreadID - " blocked on an invalid fd", tso->id); - raiseAsync(iomgr->cap, tso, - (StgClosure *)blockedOnBadFD_closure, - false, NULL); - } else { - /* We should be guaranteed that the tso is still on the same - * cap because the tso was not on the run queue of any cap and - * so is not subject to thread migration. - */ - StgTSO *tso = aiop->notify.tso; - pushOnRunQueue(iomgr->cap, tso); - RELEASE_STORE(&tso->why_blocked, NotBlocked); - } + /* We should be guaranteed that the tso is still on the same + * cap because the tso was not on the run queue of any cap and + * so is not subject to thread migration. + */ + StgTSO *tso = aiop->notify.tso; + ASSERT(tso->cap == iomgr->cap); + + /* Fill in the outcome and result/error on the TSO's stack frame */ + setTsoIOOpOutcome(tso, aiop->outcome, aiop->result); + pushOnRunQueue(iomgr->cap, tso); + RELEASE_STORE(&tso->why_blocked, NotBlocked); + /* For the TSO case, the aiop was only reachable from the TSO * itself, and thus it is now no longer be reachable at all. */ ===================================== rts/posix/Poll.h ===================================== @@ -20,13 +20,13 @@ void initCapabilityIOManagerPoll(CapIOManager *iomgr); void freeCapabilityIOManagerPoll(CapIOManager *iomgr); /* Synchronous I/O and timer operations */ -bool syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso, - IOReadOrWrite rw, HsInt fd); +IOSubmitResult syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso, + IOReadOrWrite rw, HsInt fd); void syncIOCancelPoll(CapIOManager *iomgr, StgTSO *tso); /* Asynchronous operations */ -bool asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop, - IOReadOrWrite rw, int fd); +IOSubmitResult asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop, + IOReadOrWrite rw, int fd); void asyncIOCancelPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop); /* Scheduler operations */ ===================================== rts/posix/Select.c ===================================== @@ -15,7 +15,6 @@ #include "Signals.h" #include "Schedule.h" #include "Prelude.h" -#include "RaiseAsync.h" #include "RtsUtils.h" #include "Capability.h" #include "Select.h" @@ -480,13 +479,18 @@ awaitCompletedTimeoutsOrIOSelect(CapIOManager *iomgr, bool wait) IF_DEBUG(scheduler, debugBelch("Killing blocked thread %" FMT_StgThreadID " on bad fd=%i\n", tso->id, fd)); - raiseAsync(iomgr->cap, tso, - (StgClosure *)blockedOnBadFD_closure, false, NULL); + + /* Fill in the outcome and error on the TSO's stack frame */ + setTsoIOOpOutcome(tso, IOOpOutcomeFailed, EBADF); + pushOnRunQueue(iomgr->cap,tso); + RELEASE_STORE(&tso->why_blocked, NotBlocked); break; case RTS_FD_IS_READY: IF_DEBUG(scheduler, debugBelch("Waking up blocked thread %" FMT_StgThreadID "\n", tso->id)); + /* Fill in the outcome and result on the TSO's stack frame */ + setTsoIOOpOutcome(tso, IOOpOutcomeSuccess, 0); pushOnRunQueue(iomgr->cap,tso); RELEASE_STORE(&tso->why_blocked, NotBlocked); break; View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/54810171fe3167719d4ccd32ad4bf01... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/54810171fe3167719d4ccd32ad4bf01... You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help