Duncan Coutts pushed to branch wip/dcoutts/io-manager-io-primop-exceptions at Glasgow Haskell Compiler / GHC
Commits:
-
06eee015
by Luite Stegeman at 2026-09-08T06:03:06+02:00
-
f8b2bd8f
by Alan Zimmerman at 2026-09-08T09:39:12-04:00
-
430967ab
by Duncan Coutts at 2026-09-09T11:07:01+01:00
-
344080fd
by Duncan Coutts at 2026-09-09T11:07:01+01:00
-
cfdb3399
by Duncan Coutts at 2026-09-09T11:07:01+01:00
-
f09c09d8
by Duncan Coutts at 2026-09-09T11:07:01+01:00
-
30f074d9
by Duncan Coutts at 2026-09-09T11:07:01+01:00
-
a8786ced
by Duncan Coutts at 2026-09-09T11:07:01+01:00
-
7a9df79a
by Duncan Coutts at 2026-09-09T11:07:01+01:00
-
6b0424e3
by Duncan Coutts at 2026-09-09T11:07:01+01:00
24 changed files:
- + changelog.d/fix-parallel-gc-selector-race-27477
- compiler/GHC/Builtin/primops.txt.pp
- compiler/GHC/Hs/Doc.hs
- compiler/GHC/Hs/Extension/Pass.hs
- libraries/ghc-internal/include/RtsIfaceSymbols.h
- libraries/ghc-internal/src/GHC/Internal/Conc/IO.hs
- 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
- rts/sm/Evac.c
- + testsuite/tests/rts/T27477.hs
- + testsuite/tests/rts/T27477.stdout
- testsuite/tests/rts/all.T
- utils/check-exact/ExactPrint.hs
- utils/deriveConstants/Main.hs
Changes:
| 1 | +section: rts
|
|
| 2 | +synopsis: Fix a parallel GC race on weakly-ordered architectures (AArch64) that could crash or silently corrupt the heap
|
|
| 3 | +description:
|
|
| 4 | + On weakly-ordered architectures such as AArch64 programs using
|
|
| 5 | + could crash during parallel garbage collection (segmentation fault
|
|
| 6 | + or ``internal error: eval_thunk_selector: strange selectee <n>``) or
|
|
| 7 | + continue running with silently corrupted heap data, due to a missing
|
|
| 8 | + memory barrier in the parallel GC.
|
|
| 9 | +mrs: !16488
|
|
| 10 | +issues: #27477 |
| ... | ... | @@ -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
|
| ... | ... | @@ -58,6 +58,7 @@ import Language.Haskell.Syntax.Doc |
| 58 | 58 | import Language.Haskell.Syntax.Extension
|
| 59 | 59 | import Language.Haskell.Syntax.Module.Name
|
| 60 | 60 | |
| 61 | +-- Exact print annotations ignore LHsDoc, so bare SrcSpan is fine
|
|
| 61 | 62 | type instance Anno (WithHsDocIdentifiers (HsDocString (GhcPass pass)) (GhcPass pass)) = SrcSpan
|
| 62 | 63 | |
| 63 | 64 | deriving instance (Data pass, Data (LIdP pass), Data a) => Data (WithHsDocIdentifiers a pass)
|
| ... | ... | @@ -46,6 +46,7 @@ type instance XRec (GhcPass p) a = XRecGhc a |
| 46 | 46 | -- but pass-independent, source location
|
| 47 | 47 | type XRecGhc a = GenLocated (Anno a) a
|
| 48 | 48 | |
| 49 | +-- Exact print annotations ignore LHsDoc, so bare SrcSpan is fine
|
|
| 49 | 50 | type instance Anno (HsDocString (GhcPass _)) = SrcSpan
|
| 50 | 51 | type instance Anno HsDocStringChunk = SrcSpan
|
| 51 | 52 |
| ... | ... | @@ -23,15 +23,11 @@ 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)
|
| 34 | 29 | CLOSURE(GHCziInternalziConcziIO, ioManagerCapabilitiesChanged_closure)
|
| 30 | +CLOSURE(GHCziInternalziConcziIO, raisePrimIOException_info)
|
|
| 35 | 31 | CLOSURE(GHCziInternalziConcziSignal, runHandlersPtr_closure)
|
| 36 | 32 | CLOSURE(GHCziInternalziTopHandler, flushStdHandles_closure)
|
| 37 | 33 | CLOSURE(GHCziInternalziTopHandler, runMainIO_closure)
|
| ... | ... | @@ -56,13 +56,19 @@ module GHC.Internal.Conc.IO |
| 56 | 56 | , win32ConsoleHandler
|
| 57 | 57 | , toWin32ConsoleEvent
|
| 58 | 58 | #endif
|
| 59 | + , raisePrimIOException -- exported for use within RTS
|
|
| 59 | 60 | ) where
|
| 60 | 61 | |
| 61 | 62 | import GHC.Internal.Base
|
| 62 | 63 | import GHC.Internal.Conc.Sync as Sync
|
| 63 | 64 | import GHC.Internal.Err (errorWithoutStackTrace)
|
| 65 | +import GHC.Internal.Exception (SomeException, Exception(toException))
|
|
| 66 | +import qualified GHC.Internal.Foreign.C.Error as C
|
|
| 67 | +import GHC.Internal.Foreign.C.Types (CInt)
|
|
| 68 | +import GHC.Internal.Maybe (Maybe(Nothing))
|
|
| 64 | 69 | import GHC.Internal.STM as STM
|
| 65 | -import GHC.Internal.Prim (delay#, waitRead#, waitWrite#)
|
|
| 70 | +import GHC.Internal.Prim (Int#, State#, RealWorld,
|
|
| 71 | + delay#, waitRead#, waitWrite#, raiseIO#)
|
|
| 66 | 72 | import GHC.Internal.Real ( fromIntegral )
|
| 67 | 73 | import GHC.Internal.System.Posix.Types
|
| 68 | 74 | |
| ... | ... | @@ -249,3 +255,39 @@ registerDelay _usecs |
| 249 | 255 | #if !defined(javascript_HOST_ARCH)
|
| 250 | 256 | foreign import ccall unsafe "rtsSupportsBoundThreads" threaded :: Bool
|
| 251 | 257 | #endif
|
| 258 | + |
|
| 259 | + |
|
| 260 | +-- ---------------------------------------------------------------------------
|
|
| 261 | +-- PrimIOException
|
|
| 262 | + |
|
| 263 | +-- FIXME: raisePrimIOException throws an IOException, which is not ideal. It is
|
|
| 264 | +-- not in a position to fill in most of the information for an IOException.
|
|
| 265 | +-- Much better would be to have a new PrimIOException exception type, and for
|
|
| 266 | +-- I/O primops to catch that and rethrow with the proper context. In particular
|
|
| 267 | +-- we do not have a proper location, since that's fundamantally something from
|
|
| 268 | +-- a higher level library.
|
|
| 269 | +--
|
|
| 270 | +-- Unfortunately, catching and throwing converts async exceptions to sync ones.
|
|
| 271 | +-- This breaks any thunks that, via unsafePerformIO, use such operations. We
|
|
| 272 | +-- have tests that check for this, e.g. T26341a T26341b. Catching and rethrowing
|
|
| 273 | +-- here would break those tests.
|
|
| 274 | +--
|
|
| 275 | +-- See issue #2558, #24189 and #26368.
|
|
| 276 | + |
|
| 277 | +-- | Internal helper funtion used by the in-RTS I\/O managers for reporting
|
|
| 278 | +-- exceptions. Do not use directly.
|
|
| 279 | +--
|
|
| 280 | +-- Do not change the type signature \/ ABI without updating the Cmm callers!
|
|
| 281 | +-- There is no signature checking for Cmm to Hs calls.
|
|
| 282 | +--
|
|
| 283 | +raisePrimIOException :: Int# -> State# RealWorld -> (# State# RealWorld, a #)
|
|
| 284 | +raisePrimIOException errno# s =
|
|
| 285 | + let exception :: SomeException
|
|
| 286 | + exception = toException
|
|
| 287 | + (C.errnoToIOError "threadWaitRead/Write"
|
|
| 288 | + (toCErrno errno#)
|
|
| 289 | + Nothing Nothing)
|
|
| 290 | + in raiseIO# exception s
|
|
| 291 | + where
|
|
| 292 | + toCErrno :: Int# -> C.Errno
|
|
| 293 | + toCErrno e = C.Errno ((fromIntegral :: Int -> CInt) (I# e)) |
| ... | ... | @@ -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
|
| ... | ... | @@ -20,6 +20,7 @@ import ReleaseSRWLockExclusives; |
| 20 | 20 | #if !defined(UnregisterisedCompiler)
|
| 21 | 21 | import CLOSURE g0;
|
| 22 | 22 | import CLOSURE large_alloc_lim;
|
| 23 | +import CLOSURE ghc_hs_iface;
|
|
| 23 | 24 | import CLOSURE stg_MSG_THROWTO_info;
|
| 24 | 25 | import CLOSURE stg_MVAR_DIRTY_info;
|
| 25 | 26 | import CLOSURE stg_WHITEHOLE_info;
|
| ... | ... | @@ -40,6 +41,8 @@ import CLOSURE stg_ret_l_info; |
| 40 | 41 | import CLOSURE stg_ret_n_info;
|
| 41 | 42 | import CLOSURE stg_ret_p_info;
|
| 42 | 43 | import CLOSURE stg_stack_save_entries;
|
| 44 | +import CLOSURE stg_block_io_unit_info;
|
|
| 45 | +import CLOSURE stg_block_io_int_info;
|
|
| 43 | 46 | #endif
|
| 44 | 47 | |
| 45 | 48 | /* Stack/Heap Check Failure
|
| ... | ... | @@ -694,6 +697,37 @@ stg_block_putmvar (P_ mvar, P_ val) |
| 694 | 697 | }
|
| 695 | 698 | }
|
| 696 | 699 | |
| 700 | + |
|
| 701 | +/* -----------------------------------------------------------------------------
|
|
| 702 | + STM-specific waiting
|
|
| 703 | + -------------------------------------------------------------------------- */
|
|
| 704 | + |
|
| 705 | +stg_block_stmwait
|
|
| 706 | +{
|
|
| 707 | + // When blocking on an MVar we have to be careful to only release
|
|
| 708 | + // the lock on the MVar at the very last moment (using
|
|
| 709 | + // BLOCK_BUT_FIRST()), since when we release the lock another
|
|
| 710 | + // Capability can wake up the thread, which modifies its stack and
|
|
| 711 | + // other state. This is not a problem for STM, because STM
|
|
| 712 | + // wakeups are non-destructive; the waker simply calls
|
|
| 713 | + // tryWakeupThread() which sends a message to the owner
|
|
| 714 | + // Capability. So the moment we release this lock we might start
|
|
| 715 | + // getting wakeup messages, but that's perfectly harmless.
|
|
| 716 | + //
|
|
| 717 | + // Furthermore, we *must* release these locks, just in case an
|
|
| 718 | + // exception is raised in this thread by
|
|
| 719 | + // maybePerformBlockedException() while exiting to the scheduler,
|
|
| 720 | + // which will abort the transaction, which needs to obtain a lock
|
|
| 721 | + // on all the TVars to remove the thread from the queues.
|
|
| 722 | + //
|
|
| 723 | + ccall stmWaitUnlock(MyCapability() "ptr", R3 "ptr");
|
|
| 724 | + BLOCK_GENERIC;
|
|
| 725 | +}
|
|
| 726 | + |
|
| 727 | +/* -----------------------------------------------------------------------------
|
|
| 728 | + Blocks for blackholes and throwto
|
|
| 729 | + -------------------------------------------------------------------------- */
|
|
| 730 | + |
|
| 697 | 731 | stg_block_blackhole (P_ node)
|
| 698 | 732 | {
|
| 699 | 733 | Sp_adj(-2);
|
| ... | ... | @@ -730,6 +764,13 @@ stg_block_throwto (P_ tso, P_ exception) |
| 730 | 764 | }
|
| 731 | 765 | }
|
| 732 | 766 | |
| 767 | +/* -----------------------------------------------------------------------------
|
|
| 768 | + Thread blocking for I/O primops
|
|
| 769 | + -------------------------------------------------------------------------- */
|
|
| 770 | + |
|
| 771 | +/* Thread blocking for primops used only by the win32-legacy I/O manager:
|
|
| 772 | + * asyncRead, asyncWrite and asyncDoProc.
|
|
| 773 | + */
|
|
| 733 | 774 | #if defined(mingw32_HOST_OS)
|
| 734 | 775 | INFO_TABLE_RET ( stg_block_async, RET_SMALL, W_ info_ptr, W_ len, W_ errCode )
|
| 735 | 776 | return ()
|
| ... | ... | @@ -751,32 +792,171 @@ stg_block_async |
| 751 | 792 | Sp(2) = eintr; // errCode: interrupted
|
| 752 | 793 | BLOCK_GENERIC;
|
| 753 | 794 | }
|
| 754 | - |
|
| 755 | 795 | #endif
|
| 756 | 796 | |
| 797 | +/* Note [Thread blocking for new I/O primops]
|
|
| 798 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 757 | 799 | |
| 758 | -/* -----------------------------------------------------------------------------
|
|
| 759 | - STM-specific waiting
|
|
| 760 | - -------------------------------------------------------------------------- */
|
|
| 800 | +The classic I/O primops are:
|
|
| 761 | 801 | |
| 762 | -stg_block_stmwait
|
|
| 802 | + waitRead#, waitWrite# :: Int# -> State# s -> State# s
|
|
| 803 | + |
|
| 804 | +Note that they have no result, with wrapper types returning IO (). Each one
|
|
| 805 | +simply suspends the calling thread until a condition occurs (I/O or time).
|
|
| 806 | + |
|
| 807 | +Despite this external simplicity, internally there is more complexity with a
|
|
| 808 | +number of cases to handle:
|
|
| 809 | + |
|
| 810 | +At submission time:
|
|
| 811 | + - synchronous failure, e.g. bad fd
|
|
| 812 | + - synchronous success, e.g. waiting on a regular file, which is always ready
|
|
| 813 | + - proceed asynchronously: block thread, return to scheduler
|
|
| 814 | + |
|
| 815 | +Later, at I/O completion, I/O error, or async exception time:
|
|
| 816 | + - reported failure, e.g. stale fd
|
|
| 817 | + - success
|
|
| 818 | + |
|
| 819 | +The waitRead/Write# also have non-reported failures, such as I/O errors,
|
|
| 820 | +but these are treated as the success case since these primops are defined to
|
|
| 821 | +wait for I/O or an error. We only report failures, via exceptions, for cases
|
|
| 822 | +where it would be unsafe to proceed with a subsequent I/O operation on the
|
|
| 823 | +same fd. In particular this includes when the fd was closed while a thread was
|
|
| 824 | +waiting on the fd (since it's very unsafe to read/write an fd that is closed or
|
|
| 825 | +worse: has been recycled to refer to a different file).
|
|
| 826 | + |
|
| 827 | +The synchronous success and failure cases are handled by the initiating
|
|
| 828 | +primops. In the blocking case, it relies on one of these stg_block_ functions,
|
|
| 829 | +stack frames, and stack frame info table entry code. The key bits of the
|
|
| 830 | +mechanism are:
|
|
| 831 | + |
|
| 832 | + - The stg_block_ cmm function sets up the stack frame for the blocked thread.
|
|
| 833 | + - The stack frame entries can be overwritten by the I/O manager, e.g. upon I/O
|
|
| 834 | + completion or error.
|
|
| 835 | + - The stack frame's info table entry code is invoked when the thread is
|
|
| 836 | + resumed.
|
|
| 837 | + - The entries in the stack frame become arguments to the info table entry code.
|
|
| 838 | + - The result(s) returned by the info table entry code _are_ the result(s) of
|
|
| 839 | + the I/O primop.
|
|
| 840 | + |
|
| 841 | +It is important to keep clear the distinction between the stack frame entries
|
|
| 842 | +and the final primop result(s): the stack frame's info table entry code
|
|
| 843 | +transforms the former into the latter.
|
|
| 844 | + |
|
| 845 | +The convention we follow is that the stack frame contains two fields:
|
|
| 846 | + - an outcome, using IOOpOutcome values: in-flight, success, failed, cancelled.
|
|
| 847 | + - a result or error code. For outcome success it is a result. For failed it is
|
|
| 848 | + the error code (using posix errno codes), while for cancelled and in-flight
|
|
| 849 | + outcomes there is no result.
|
|
| 850 | + |
|
| 851 | +This outcome+result/failure encoding is exactly the same as the corresponding
|
|
| 852 | +fields in the StgAsyncIOOp. One day we might just use the StgAsyncIOOp itself,
|
|
| 853 | +once all I/O managers use them.
|
|
| 854 | + |
|
| 855 | +The waitRead/waitWrite# primops do not use a result, but later I/O primops will
|
|
| 856 | +do. In particular read/write on files return a length, and other I/O primitives
|
|
| 857 | +also have a single result.
|
|
| 858 | + |
|
| 859 | +So this gives us two block functions and corresponding info tables:
|
|
| 860 | + |
|
| 861 | + 1. stg_block_io_unit: with a stack frame containing outcome+result/failure.
|
|
| 862 | + On resume, throw exception on failure and otherwise return no args.
|
|
| 863 | + This is for primops with result IO ().
|
|
| 864 | + |
|
| 865 | + 2. stg_block_io_int: with a stack frame containing outcome+result/failure.
|
|
| 866 | + On resume, throw exception on failure and otherwise return result.
|
|
| 867 | + This is for primops with result IO Int or IO Word.
|
|
| 868 | + |
|
| 869 | + 3. stg_block_io_aiop: in future if we add async I/O primitives that return an
|
|
| 870 | + StgAsyncIOOp we would also need versions that return that (since even
|
|
| 871 | + submitting async I/O can block if there's too much I/O in flight).
|
|
| 872 | + |
|
| 873 | +The stack frame layout used is:
|
|
| 874 | + Sp[0] stg_block_io_unit_info or stg_block_io_int_info
|
|
| 875 | + Sp[1] outcome
|
|
| 876 | + Sp[2] result or error
|
|
| 877 | + |
|
| 878 | +C code should use setTsoIOOpOutcome to set this. But update this if the layout
|
|
| 879 | +changes!
|
|
| 880 | + |
|
| 881 | + |
|
| 882 | +Note [Calling convention for raisePrimIOException]
|
|
| 883 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 884 | + |
|
| 885 | +In cases 1 & 2 we check the outcome and, on failure, we throw an exception. Our
|
|
| 886 | +approach is to use a helper function implemeted in generated Haskell code, in
|
|
| 887 | +ghc-internal. We need to perform a tail call to this helper function:
|
|
| 888 | + |
|
| 889 | + raisePrimIOException :: Int# -> State# s -> (# State# s, a #)
|
|
| 890 | + |
|
| 891 | +The calling convention for top-level generated Haskell function like this uses
|
|
| 892 | +R2 because it has two arguments -- the Int# and State# -- but the State#
|
|
| 893 | +argument has a zero-width repesentation. The cmm call uses R1, R2, ... etc so
|
|
| 894 | +to pass in R2 we have to use a dummy first argument which will be passed in R1
|
|
| 895 | +(and ignored).
|
|
| 896 | +*/
|
|
| 897 | + |
|
| 898 | +/* stg_block_io_unit_info : the return info table for stg_block_io_unit */
|
|
| 899 | +INFO_TABLE_RET ( stg_block_io_unit, RET_SMALL, W_ info_ptr,
|
|
| 900 | + W_ outcome, W_ result )
|
|
| 901 | + return ()
|
|
| 763 | 902 | {
|
| 764 | - // When blocking on an MVar we have to be careful to only release
|
|
| 765 | - // the lock on the MVar at the very last moment (using
|
|
| 766 | - // BLOCK_BUT_FIRST()), since when we release the lock another
|
|
| 767 | - // Capability can wake up the thread, which modifies its stack and
|
|
| 768 | - // other state. This is not a problem for STM, because STM
|
|
| 769 | - // wakeups are non-destructive; the waker simply calls
|
|
| 770 | - // tryWakeupThread() which sends a message to the owner
|
|
| 771 | - // Capability. So the moment we release this lock we might start
|
|
| 772 | - // getting wakeup messages, but that's perfectly harmless.
|
|
| 773 | - //
|
|
| 774 | - // Furthermore, we *must* release these locks, just in case an
|
|
| 775 | - // exception is raised in this thread by
|
|
| 776 | - // maybePerformBlockedException() while exiting to the scheduler,
|
|
| 777 | - // which will abort the transaction, which needs to obtain a lock
|
|
| 778 | - // on all the TVars to remove the thread from the queues.
|
|
| 779 | - //
|
|
| 780 | - ccall stmWaitUnlock(MyCapability() "ptr", R3 "ptr");
|
|
| 903 | + if (outcome == 2 :: W_ /*IOOpOutcomeFailed*/) (likely: False) {
|
|
| 904 | + /* See Note [Calling convention for raisePrimIOException] */
|
|
| 905 | + W_ dummyR1;
|
|
| 906 | + dummyR1 = 0;
|
|
| 907 | + jump %ENTRY_CODE(HsIface_raisePrimIOException_info(W_[ghc_hs_iface]))
|
|
| 908 | + (dummyR1, result);
|
|
| 909 | + } else {
|
|
| 910 | + /* success or cancelled outcomes */
|
|
| 911 | + return ();
|
|
| 912 | + }
|
|
| 913 | +}
|
|
| 914 | + |
|
| 915 | +/* Blocking for I/O primops with result IO ().
|
|
| 916 | + * See Note [Thread blocking for new I/O primops].
|
|
| 917 | + */
|
|
| 918 | +stg_block_io_unit
|
|
| 919 | +{
|
|
| 920 | + /* Fill out the stack frame with dummy values. The IO manager will
|
|
| 921 | + * overwrite these with the actual results when the I/O operation
|
|
| 922 | + * completes, fails, or is cancelled.
|
|
| 923 | + */
|
|
| 924 | + Sp_adj(-3);
|
|
| 925 | + Sp(0) = stg_block_io_unit_info;
|
|
| 926 | + Sp(1) = 0; // outcome: 0 indicates IOOpOutcomeInFlight
|
|
| 927 | + Sp(2) = 0; // result/errno
|
|
| 928 | + BLOCK_GENERIC;
|
|
| 929 | +}
|
|
| 930 | + |
|
| 931 | +/* stg_block_io_int_info : the return info table for stg_block_io_int */
|
|
| 932 | +INFO_TABLE_RET ( stg_block_io_int, RET_SMALL, W_ info_ptr,
|
|
| 933 | + W_ outcome, W_ result )
|
|
| 934 | + return ()
|
|
| 935 | +{
|
|
| 936 | + if (outcome == 2 :: W_ /*IOOpOutcomeFailed*/) (likely: False) {
|
|
| 937 | + /* See Note [Calling convention for raisePrimIOException] */
|
|
| 938 | + W_ dummyR1;
|
|
| 939 | + dummyR1 = 0;
|
|
| 940 | + jump %ENTRY_CODE(HsIface_raisePrimIOException_info(W_[ghc_hs_iface]))
|
|
| 941 | + (dummyR1, result);
|
|
| 942 | + } else {
|
|
| 943 | + /* success or cancelled outcomes */
|
|
| 944 | + return (result);
|
|
| 945 | + }
|
|
| 946 | +}
|
|
| 947 | + |
|
| 948 | +/* Blocking for I/O primops with result IO Int or Word (or rather Int#/Word#).
|
|
| 949 | + * See Note [Thread blocking for new I/O primops].
|
|
| 950 | + */
|
|
| 951 | +stg_block_io_int
|
|
| 952 | +{
|
|
| 953 | + /* Fill out the stack frame with dummy values. The IO manager will
|
|
| 954 | + * overwrite these with the actual results when the I/O operation
|
|
| 955 | + * completes, fails, or is cancelled.
|
|
| 956 | + */
|
|
| 957 | + Sp_adj(-3);
|
|
| 958 | + Sp(0) = stg_block_io_int_info;
|
|
| 959 | + Sp(1) = 0; // outcome: 0 indicates IOOpOutcomeInFlight
|
|
| 960 | + Sp(2) = 0; // result/errno
|
|
| 781 | 961 | BLOCK_GENERIC;
|
| 782 | 962 | } |
| ... | ... | @@ -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,60 @@ stg_whereFromzh (P_ clos, W_ buf) |
| 2297 | 2297 | Thread I/O blocking primitives
|
| 2298 | 2298 | -------------------------------------------------------------------------- */
|
| 2299 | 2299 | |
| 2300 | -stg_waitReadzh ( W_ fd )
|
|
| 2300 | +stg_handleIOSubmitResult ( CInt result )
|
|
| 2301 | 2301 | {
|
| 2302 | - CBool ok; /* Ok, or heap alloc failure. */
|
|
| 2302 | + /* See Note [Encoding of result of I/O manager operations] */
|
|
| 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 | + /* case IOSubmitResultAsyncContinue */
|
|
| 2305 | + if (result == 0::CInt) (likely: True) {
|
|
| 2306 | + /* See Note [Thread blocking for new I/O primops] */
|
|
| 2307 | + jump stg_block_io_unit();
|
|
| 2308 | + }
|
|
| 2309 | + |
|
| 2310 | + /* case IOSubmitResultSyncSuccess*/
|
|
| 2311 | + if (result == 1::CInt) (likely: True) {
|
|
| 2312 | + /* Success, don't even go via scheduler. */
|
|
| 2313 | + return ();
|
|
| 2314 | + }
|
|
| 2315 | + |
|
| 2316 | + /* negative numbers are -errno error codes */
|
|
| 2317 | + if (result < 0::CInt) {
|
|
| 2318 | + /* See Note [Calling convention for raisePrimIOException] */
|
|
| 2319 | + W_ dummyR1; W_ errno;
|
|
| 2320 | + dummyR1 = 0;
|
|
| 2321 | + errno = TO_W_(-result);
|
|
| 2322 | + jump %ENTRY_CODE(HsIface_raisePrimIOException_info(W_[ghc_hs_iface]))
|
|
| 2323 | + (dummyR1, errno);
|
|
| 2324 | + }
|
|
| 2325 | + |
|
| 2326 | + /* case IOSubmitResultHeapOverflow */
|
|
| 2327 | + if (result == 2::CInt) {
|
|
| 2310 | 2328 | jump stg_raisezh(HsIface_heapOverflow_closure(W_[ghc_hs_iface]));
|
| 2329 | + |
|
| 2311 | 2330 | }
|
| 2331 | + ccall sbarf("syncIOWaitReady result encoding error") never returns;
|
|
| 2332 | +}
|
|
| 2333 | + |
|
| 2334 | +stg_waitReadzh ( W_ fd )
|
|
| 2335 | +{
|
|
| 2336 | + CInt result;
|
|
| 2337 | + |
|
| 2338 | + (result) = ccall syncIOWaitReady(Capability_iomgr(MyCapability()) "ptr",
|
|
| 2339 | + CurrentTSO "ptr",
|
|
| 2340 | + /* IORead */ 0::CInt, fd);
|
|
| 2341 | + |
|
| 2342 | + jump stg_handleIOSubmitResult(result);
|
|
| 2312 | 2343 | }
|
| 2313 | 2344 | |
| 2314 | 2345 | stg_waitWritezh ( W_ fd )
|
| 2315 | 2346 | {
|
| 2316 | - CBool ok; /* Ok, or heap alloc failure. */
|
|
| 2347 | + CInt result;
|
|
| 2317 | 2348 | |
| 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 | - }
|
|
| 2349 | + (result) = ccall syncIOWaitReady(Capability_iomgr(MyCapability()) "ptr",
|
|
| 2350 | + CurrentTSO "ptr",
|
|
| 2351 | + /* IOWrite */ 1::CInt, fd);
|
|
| 2352 | + |
|
| 2353 | + jump stg_handleIOSubmitResult(result);
|
|
| 2326 | 2354 | }
|
| 2327 | 2355 | |
| 2328 | 2356 | stg_delayzh ( W_ us_delay )
|
| ... | ... | @@ -28,7 +28,7 @@ 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
|
|
| 31 | + StgClosure *raisePrimIOException_info; // GHC.Internal.Conc.IO.raisePrimIOException_info
|
|
| 32 | 32 | StgClosure *runSparks_closure; // GHC.Internal.Conc.Sync.runSparks_closure
|
| 33 | 33 | StgClosure *ensureIOManagerIsRunning_closure; // GHC.Internal.Conc.IO.ensureIOManagerIsRunning_closure
|
| 34 | 34 | StgClosure *interruptIOManager_closure; // GHC.Internal.Conc.IO.interruptIOManager_closure
|
| ... | ... | @@ -369,6 +369,7 @@ RTS_FUN_DECL(stg_gc_bpp); |
| 369 | 369 | RTS_RET(stg_gc_fun);
|
| 370 | 370 | RTS_FUN_DECL(__stg_gc_fun);
|
| 371 | 371 | |
| 372 | +/* Blocking for various primops */
|
|
| 372 | 373 | RTS_FUN_DECL(stg_yield_noregs);
|
| 373 | 374 | RTS_FUN_DECL(stg_yield_to_interpreter);
|
| 374 | 375 | RTS_FUN_DECL(stg_block_noregs);
|
| ... | ... | @@ -380,13 +381,19 @@ RTS_RET(stg_block_takemvar); |
| 380 | 381 | RTS_RET(stg_block_readmvar);
|
| 381 | 382 | RTS_FUN_DECL(stg_block_putmvar);
|
| 382 | 383 | RTS_RET(stg_block_putmvar);
|
| 384 | +RTS_FUN_DECL(stg_block_stmwait);
|
|
| 385 | +RTS_FUN_DECL(stg_block_throwto);
|
|
| 386 | +RTS_RET(stg_block_throwto);
|
|
| 387 | + |
|
| 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);
|
|
| 383 | 393 | #if defined(mingw32_HOST_OS)
|
| 384 | 394 | RTS_FUN_DECL(stg_block_async);
|
| 385 | 395 | RTS_RET(stg_block_async);
|
| 386 | 396 | #endif
|
| 387 | -RTS_FUN_DECL(stg_block_stmwait);
|
|
| 388 | -RTS_FUN_DECL(stg_block_throwto);
|
|
| 389 | -RTS_RET(stg_block_throwto);
|
|
| 390 | 397 | |
| 391 | 398 | /* Entry/exit points from StgStartup.cmm */
|
| 392 | 399 |
| ... | ... | @@ -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;
|
| ... | ... | @@ -1361,7 +1361,11 @@ selector_loop: |
| 1361 | 1361 | // from-space during marking, for example. We rely on the property
|
| 1362 | 1362 | // that evacuate() doesn't mind if it gets passed a to-space pointer.
|
| 1363 | 1363 | |
| 1364 | - info = RELAXED_LOAD((StgInfoTable**) &selectee->header.info);
|
|
| 1364 | + // NB. this load must be ordered: the selectee may be updated
|
|
| 1365 | + // concurrently by another GC thread's unchain_thunk_selectors(),
|
|
| 1366 | + // which publishes an indirection by writing the indirectee and
|
|
| 1367 | + // then RELEASE-storing the info pointer. See #27477.
|
|
| 1368 | + info = ACQUIRE_LOAD((StgInfoTable**) &selectee->header.info);
|
|
| 1365 | 1369 | |
| 1366 | 1370 | if (IS_FORWARDING_PTR(info)) {
|
| 1367 | 1371 | // We don't follow pointers into to-space; the constructor
|
| 1 | +{-# LANGUAGE BangPatterns #-}
|
|
| 2 | + |
|
| 3 | +-- Stress test for #27477: missing ACQUIRE in eval_thunk_selector
|
|
| 4 | +-- (rts/sm/Evac.c).
|
|
| 5 | +--
|
|
| 6 | +-- With the bug present this test crashes or reports value mismatches
|
|
| 7 | +-- within a few seconds on AArch64; on strongly-ordered targets
|
|
| 8 | +-- it just passes.
|
|
| 9 | + |
|
| 10 | +module Main (main) where
|
|
| 11 | + |
|
| 12 | +import Control.Concurrent
|
|
| 13 | +import Control.Exception
|
|
| 14 | +import Control.Monad
|
|
| 15 | +import Data.IORef
|
|
| 16 | +import System.Environment
|
|
| 17 | +import System.Exit
|
|
| 18 | +import System.IO
|
|
| 19 | +import System.Mem (performMinorGC)
|
|
| 20 | + |
|
| 21 | +data T = T { nxt :: T, val :: !Int }
|
|
| 22 | + |
|
| 23 | +-- expected values and the cells that should evaluate to them
|
|
| 24 | +type Batch = ([Int], [T])
|
|
| 25 | + |
|
| 26 | +tailD, branchD, branchesPerCell :: Int
|
|
| 27 | +tailD = 12 -- shared tail length per cluster
|
|
| 28 | +branchD = 3 -- private branch length
|
|
| 29 | +branchesPerCell = 2
|
|
| 30 | +-- tailD + branchD must stay below MAX_THUNK_SELECTOR_DEPTH (16)
|
|
| 31 | + |
|
| 32 | +{-# NOINLINE spineEnd #-}
|
|
| 33 | +spineEnd :: T
|
|
| 34 | +spineEnd = errorWithoutStackTrace "spine end reached"
|
|
| 35 | + |
|
| 36 | +-- Fully evaluated spine of constructors; cell i from the head has val i+1.
|
|
| 37 | +buildSpine :: Int -> IO T
|
|
| 38 | +buildSpine len = evaluate (go len (T spineEnd 0))
|
|
| 39 | + where
|
|
| 40 | + go :: Int -> T -> T
|
|
| 41 | + go 0 acc = acc
|
|
| 42 | + go k acc = go (k - 1) (T acc k)
|
|
| 43 | + |
|
| 44 | +-- k selector thunks, shallow-first: cell i selects from cell i-1,
|
|
| 45 | +-- cell 0 selects from t. If t evaluates to spine cell m (val m+1),
|
|
| 46 | +-- cell i evaluates to spine cell m+1+i (val m+2+i).
|
|
| 47 | +{-# NOINLINE selChain #-}
|
|
| 48 | +selChain :: Int -> T -> [T]
|
|
| 49 | +selChain 0 _ = []
|
|
| 50 | +selChain k t = let t' = nxt t in t' : selChain (k - 1) t'
|
|
| 51 | + |
|
| 52 | +lenL :: [a] -> Int
|
|
| 53 | +lenL = go 0
|
|
| 54 | + where
|
|
| 55 | + go !n [] = n
|
|
| 56 | + go !n (_ : xs) = go (n + 1) xs
|
|
| 57 | + |
|
| 58 | +everyN :: Int -> [a] -> [a]
|
|
| 59 | +everyN _ [] = []
|
|
| 60 | +everyN n (x : xs) = x : everyN n (drop (n - 1) xs)
|
|
| 61 | + |
|
| 62 | +-- One branch hanging off tail cell tj (which evaluates to val jval):
|
|
| 63 | +-- branch cell i evaluates to jval+1+i. Returned deepest-first.
|
|
| 64 | +{-# NOINLINE mkBranch #-}
|
|
| 65 | +mkBranch :: Int -> T -> IO Batch
|
|
| 66 | +mkBranch jval tj = do
|
|
| 67 | + let cells = selChain branchD tj
|
|
| 68 | + _ <- evaluate (lenL cells)
|
|
| 69 | + let expected = [ jval + 1 + i | i <- [0 .. branchD - 1] ]
|
|
| 70 | + pure (reverse expected, reverse cells)
|
|
| 71 | + |
|
| 72 | +-- A cluster: one private spine, one shared tail of selectors over it,
|
|
| 73 | +-- branchesPerCell branches of selectors converging on every tail cell.
|
|
| 74 | +-- Neither the spine nor the tail is returned: they must stay reachable
|
|
| 75 | +-- only through the branch selectors.
|
|
| 76 | +buildCluster :: IO [Batch]
|
|
| 77 | +buildCluster = do
|
|
| 78 | + sp <- buildSpine (tailD + branchD + 4)
|
|
| 79 | + let tails = selChain tailD sp
|
|
| 80 | + _ <- evaluate (lenL tails)
|
|
| 81 | + bss <- forM (zip [0 ..] tails) $ \(j, tj) ->
|
|
| 82 | + replicateM branchesPerCell (mkBranch (j + 2) tj)
|
|
| 83 | + pure (concat bss)
|
|
| 84 | + |
|
| 85 | +verifyBatch :: IORef Int -> Int -> Batch -> IO ()
|
|
| 86 | +verifyBatch errs wid (es0, ts0) = go es0 ts0
|
|
| 87 | + where
|
|
| 88 | + go (e : es) (t : ts) = do
|
|
| 89 | + v <- evaluate (val t)
|
|
| 90 | + when (v /= e) $ do
|
|
| 91 | + hPutStrLn stderr $
|
|
| 92 | + "MISMATCH worker=" ++ show wid
|
|
| 93 | + ++ " expected=" ++ show e ++ " got=" ++ show v
|
|
| 94 | + modifyIORef' errs (+ 1)
|
|
| 95 | + go es ts
|
|
| 96 | + go _ _ = pure ()
|
|
| 97 | + |
|
| 98 | +-- simple cyclic barrier
|
|
| 99 | +newtype Barrier = Barrier (MVar (Int, [MVar ()]))
|
|
| 100 | + |
|
| 101 | +newBarrier :: IO Barrier
|
|
| 102 | +newBarrier = Barrier <$> newMVar (0, [])
|
|
| 103 | + |
|
| 104 | +awaitBarrier :: Barrier -> Int -> IO ()
|
|
| 105 | +awaitBarrier (Barrier st) total = do
|
|
| 106 | + w <- newEmptyMVar
|
|
| 107 | + join $ modifyMVar st $ \(k, ws) ->
|
|
| 108 | + if k + 1 == total
|
|
| 109 | + then pure ((0, []), mapM_ (\m -> putMVar m ()) ws)
|
|
| 110 | + else pure ((k + 1, w : ws), takeMVar w)
|
|
| 111 | + |
|
| 112 | +worker :: Int -> Int -> Int -> Barrier -> [IORef [Batch]] -> IORef Int -> Int -> IO ()
|
|
| 113 | +worker rounds clusters nWorkers bar slots errs wid =
|
|
| 114 | + forM_ [1 .. rounds] $ \(_r :: Int) -> do
|
|
| 115 | + -- build fresh selector clusters, publish them
|
|
| 116 | + batches <- concat <$> replicateM clusters buildCluster
|
|
| 117 | + writeIORef (slots !! wid) batches
|
|
| 118 | + awaitBarrier bar nWorkers
|
|
| 119 | + -- root each branch's deepest cell via fresh cons cells in our own
|
|
| 120 | + -- nursery (so our GC thread evacuates the shared cells itself), in
|
|
| 121 | + -- a per-worker interleaved order so that different GC threads
|
|
| 122 | + -- sweep different branches of the same shared tails concurrently
|
|
| 123 | + published <- mapM readIORef slots
|
|
| 124 | + let allBatches = concat published
|
|
| 125 | + ordered
|
|
| 126 | + | nWorkers > 1 =
|
|
| 127 | + concat [ everyN nWorkers (drop ((wid + k) `mod` nWorkers) allBatches)
|
|
| 128 | + | k <- [0 .. nWorkers - 1] ]
|
|
| 129 | + | otherwise = allBatches
|
|
| 130 | + deepTs = [ t | (_, t : _) <- ordered ]
|
|
| 131 | + deepEs = [ e | (e : _, _) <- ordered ]
|
|
| 132 | + _ <- evaluate (lenL deepTs)
|
|
| 133 | + _ <- evaluate (lenL deepEs)
|
|
| 134 | + awaitBarrier bar nWorkers
|
|
| 135 | + -- run a parallel GC with everything live
|
|
| 136 | + when (wid == 0) performMinorGC
|
|
| 137 | + awaitBarrier bar nWorkers
|
|
| 138 | + -- force everything and check the values
|
|
| 139 | + verifyBatch errs wid (deepEs, deepTs)
|
|
| 140 | + awaitBarrier bar nWorkers
|
|
| 141 | + |
|
| 142 | +main :: IO ()
|
|
| 143 | +main = do
|
|
| 144 | + hSetBuffering stderr LineBuffering
|
|
| 145 | + args <- getArgs
|
|
| 146 | + let atDef d i = if length args > i then read (args !! i) else d
|
|
| 147 | + rounds = atDef 600 0
|
|
| 148 | + clusters = atDef 48 1
|
|
| 149 | + n <- getNumCapabilities
|
|
| 150 | + bar <- newBarrier
|
|
| 151 | + slots <- replicateM n (newIORef [])
|
|
| 152 | + errs <- newIORef 0
|
|
| 153 | + dones <- forM [0 .. n - 1] $ \i -> do
|
|
| 154 | + done <- newEmptyMVar
|
|
| 155 | + _ <- forkOn i $
|
|
| 156 | + (worker rounds clusters n bar slots errs i >> putMVar done Nothing)
|
|
| 157 | + `catch` \e -> putMVar done (Just (e :: SomeException))
|
|
| 158 | + pure done
|
|
| 159 | + results <- mapM takeMVar dones
|
|
| 160 | + forM_ results $ \r -> case r of
|
|
| 161 | + Just e -> do
|
|
| 162 | + hPutStrLn stderr ("worker exception: " ++ show e)
|
|
| 163 | + exitWith (ExitFailure 2)
|
|
| 164 | + Nothing -> pure ()
|
|
| 165 | + ec <- readIORef errs
|
|
| 166 | + when (ec > 0) $ do
|
|
| 167 | + hPutStrLn stderr ("FAILED: " ++ show ec ++ " mismatches")
|
|
| 168 | + exitWith (ExitFailure 2)
|
|
| 169 | + putStrLn "OK" |
| 1 | +OK |
| ... | ... | @@ -720,3 +720,12 @@ test('T27585', |
| 720 | 720 | , extra_run_opts('+RTS -i0 -RTS')
|
| 721 | 721 | ],
|
| 722 | 722 | compile_and_run, ['-O -rtsopts'])
|
| 723 | + |
|
| 724 | +test('T27477',
|
|
| 725 | + [ extra_run_opts('600 24 +RTS -N4 -A1m -RTS')
|
|
| 726 | + , req_ghc_smp
|
|
| 727 | + , req_target_smp
|
|
| 728 | + , only_ways(['threaded1', 'threaded2'])
|
|
| 729 | + ],
|
|
| 730 | + compile_and_run, ['-O2'])
|
|
| 731 | + |
| ... | ... | @@ -3404,13 +3404,13 @@ instance ExactPrint (HsCmd GhcPs) where |
| 3404 | 3404 | e' <- markAnnotated e
|
| 3405 | 3405 | return (HsCmdLet (tkLet', tkIn') binds' e')
|
| 3406 | 3406 | |
| 3407 | - exact (HsCmdDo (an0,loc) (L l es)) = do
|
|
| 3407 | + exact (HsCmdDo (an0,loc) es) = do
|
|
| 3408 | 3408 | debugM $ "HsCmdDo"
|
| 3409 | 3409 | loc' <- printStringAtAA loc "do"
|
| 3410 | 3410 | (an1,es') <- markAnnList' an0 $ do
|
| 3411 | - ee <- mapM markAnnotated es
|
|
| 3411 | + ee <- markAnnotated es
|
|
| 3412 | 3412 | return ee
|
| 3413 | - return (HsCmdDo (an1,loc') (L l es'))
|
|
| 3413 | + return (HsCmdDo (an1,loc') es')
|
|
| 3414 | 3414 | |
| 3415 | 3415 | -- ---------------------------------------------------------------------
|
| 3416 | 3416 | |
| ... | ... | @@ -4508,10 +4508,10 @@ instance ExactPrint [LocatedA (StmtLR GhcPs GhcPs (LocatedA (HsCmd GhcPs)))] whe |
| 4508 | 4508 | Just (initStmts, ls@(L _ (LastStmt _ _body _ _))) -> do
|
| 4509 | 4509 | debugM $ "LocatedL [ExprLStmt: snocView"
|
| 4510 | 4510 | ls' <- markAnnotated ls
|
| 4511 | - initStmts' <- markAnnotated initStmts
|
|
| 4511 | + initStmts' <- mapM markAnnotated initStmts
|
|
| 4512 | 4512 | return (initStmts' ++ [ls'])
|
| 4513 | 4513 | _ -> do
|
| 4514 | - stmts' <- markAnnotated stmts
|
|
| 4514 | + stmts' <- mapM markAnnotated stmts
|
|
| 4515 | 4515 | return stmts'
|
| 4516 | 4516 | |
| 4517 | 4517 | instance ExactPrint [Located HsDocStringChunk] where
|
| ... | ... | @@ -644,6 +644,7 @@ wanteds os = concat |
| 644 | 644 | ,structField C "HsIface" "divZZeroException_closure"
|
| 645 | 645 | ,structField C "HsIface" "underflowException_closure"
|
| 646 | 646 | ,structField C "HsIface" "overflowException_closure"
|
| 647 | + ,structField C "HsIface" "raisePrimIOException_info"
|
|
| 647 | 648 | ,structField C "HsIface" "unpackCStringzh_info"
|
| 648 | 649 | ,structField C "HsIface" "unpackCStringUtf8zh_info"
|
| 649 | 650 |