RTS's (old?) invariant regarding OS blocking

Hi, While trying to gain insights into the RTS, I've noticed the following in the Wiki page [1] on the topic of the scheduler: Invariant: a task that holds a capability is not blocked in the operating system. This makes some parts of the system simpler - for example, we can use spin locks that spin indefinitely, because we can ensure that the spin lock is only held by a currently executing CPU, and will therefore be released in a finite (and short) amount of time. Does it still apply to modern day GHC, or was it addressed by [2]? [1] https://ghc.haskell.org/trac/ghc/wiki/Commentary/Rts/Scheduler#Capabilities [2] https://ghc.haskell.org/trac/ghc/ticket/3553 -- Dan Aloni

On Wed, 2 Mar 2016 16:38:56 +0200
Dan Aloni
Hi,
While trying to gain insights into the RTS, I've noticed the following in the Wiki page [1] on the topic of the scheduler:
Invariant: a task that holds a capability is not blocked in the operating system.
This makes some parts of the system simpler - for example, we can use spin locks that spin indefinitely, because we can ensure that the spin lock is only held by a currently executing CPU, and will therefore be released in a finite (and short) amount of time.
Does it still apply to modern day GHC, or was it addressed by [2]?
It still does apply. Foreign calls are by default 'safe' and executed after Capability is released to a separate OS thread. Capability release is needed as foreign calls can reenter haskell RTS. You can break that invariant and observe the negative effect. For example 'unsafe' foreign call to a blocking function stops all haskell threads happened to be queued on that Capability. Illustration of adverse effect. [1] runs 2 threads: - main thread issues 'safe' and 'unsafe' FFI sleeps - auxiliary thread prints a message on screen every second. 'unsafe_sleep' blocks unrelated haskell thread for 5 seconds while 'safe_sleep' doesn't. We disable SIGVTALARM to not interfere with sleep() system call and use threaded RTS as non-threaded RTS uses SIGVTALARM for thread switching as well. $ ghc --make a.hs -o ./a -rtsopts -threaded && ./a +RTS -V0 -N1 [1 of 1] Compiling Main ( a.hs, a.o ) Linking ./a ... "start unsafe sleep" "thread: enter" "done unsafe sleep" "entering safe sleep" "*** thread: tick" "*** thread: tick" "*** thread: tick" "*** thread: tick" "done safe sleep" "*** thread: tick" "*** thread: tick" "*** thread: tick" "*** thread: tick" "*** thread: tick" "*** thread: tick" "thread: exit" -- [1]: example program a.hs: import Control.Concurrent import Control.Monad import Foreign.C foreign import ccall safe "unistd.h sleep" safe_sleep :: CInt -> IO CInt foreign import ccall unsafe "unistd.h sleep" unsafe_sleep :: CInt -> IO CInt -- sleep for n * 100ms s :: Int -> IO () s slices = threadDelay $ slices * 10^6 main = do t1_lock <- newEmptyMVar t1 <- forkIO $ do print "thread: enter" replicateM_ 10 $ do s 1 print "*** thread: tick" print "thread: exit" putMVar t1_lock () yield -- switch to the ticker print "start unsafe sleep" unsafe_sleep 5 print "done unsafe sleep" print "entering safe sleep" safe_sleep 5 print "done safe sleep" takeMVar t1_lock -- Sergei

On Thu, Mar 03, 2016 at 09:52:07AM +0000, Sergei Trofimovich wrote:
On Wed, 2 Mar 2016 16:38:56 +0200 Dan Aloni
wrote: Hi,
While trying to gain insights into the RTS, I've noticed the following in the Wiki page [1] on the topic of the scheduler:
Invariant: a task that holds a capability is not blocked in the operating system.
This makes some parts of the system simpler - for example, we can use spin locks that spin indefinitely, because we can ensure that the spin lock is only held by a currently executing CPU, and will therefore be released in a finite (and short) amount of time.
Does it still apply to modern day GHC, or was it addressed by [2]?
It still does apply. Foreign calls are by default 'safe' and executed after Capability is released to a separate OS thread. Capability release is needed as foreign calls can reenter haskell RTS.
Thanks for the elborate example - however my concerns extend beyond the safe vs. unsafe FFI. I was concerned about the spin lock, and about hard host kernel scheduling assumptions thereof. I looked into the GHC code now, and the locks in Capability.c on Linux seem to translate to pthread_mutex_lock(), which is a futex. This is a relief. Does the wiki need updating? -- Dan Aloni
participants (2)
-
Dan Aloni
-
Sergei Trofimovich