Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

`getErrno` and threads

The documentation for getErrno reads:

Get the current value of errno in the current thread.

It is unclear to me whether this means the current OS thread. In particular, does the (threaded) runtime fetch and stash away errno whenever a Haskell thread is migrated from one OS thread to another?

This question seems related, but it is unclear to me whether what is said there pertains to OS or Haskell threads.

like image 933
gspr Avatar asked Jan 27 '13 18:01

gspr


People also ask

Is errno per thread?

errno is thread-local; setting it in one thread does not affect its value in any other thread.

Should I set errno?

Indeed we should only check errno in the case where an error occurred. This is because if no error occurred, then it is still possible that errno will contain a non-zero value (e.g. if an error occurred during the execution of a library call but the error was recovered).

Does Read set errno?

Upon successful completion, read(), pread() and readv() return a non-negative integer indicating the number of bytes actually read. Otherwise, the functions return -1 and set errno to indicate the error.


1 Answers

For recent versions of GHC at least, the runtime saves errno in the Thread Storage Object (TSO) and takes care of managing it when it migrates a runtime thread to another OS thread. That means it should be safe to rely on errno in both unbound (forkIO) and bound (forkOS) threads. Here's a test for Linux:

err.hs

import Control.Concurrent
import Control.Exception
import Foreign.C

foreign import ccall safe get_ostid :: IO CUInt
foreign import ccall safe sleep :: CUInt -> IO CUInt
foreign import ccall unsafe get_errno :: IO CInt
foreign import ccall unsafe set_errno :: CInt -> IO ()

forkIO' f = do
  done <- newEmptyMVar
  forkIO (f `finally` putMVar done ())
  return (takeMVar done)

prefix = do
  id <- get_ostid
  return $ show id ++ ": "

main = do
  wait <- forkIO' $ do -- spawn a lightweight thread
    errno <- get_errno
    (putStr =<< prefix) >> putStrLn ("outer errno is " ++ show errno)
    (putStr =<< prefix) >> putStrLn "Setting outer errno to 3"
    set_errno 3
    wait' <- forkIO' $ do -- spawn another lightweight thread
      errno <- get_errno
      (putStr =<< prefix) >> putStrLn ("inner errno is " ++ show errno)
      (putStr =<< prefix) >> putStrLn "Setting inner errno to 2"
      set_errno 2
      sleep 2 -- force this lightweight thread to tie up the OS thread
      errno <- get_errno
      (putStr =<< prefix) >> putStrLn ("inner errno is " ++ show errno)
    threadDelay 1000000 -- wait a second
    -- By now, we should be in another OS thread.
    errno <- get_errno
    (putStr =<< prefix) >> putStrLn ("outer errno is " ++ show errno)
    wait'
  wait

err.c

#include <errno.h>
#include <sys/syscall.h>
#include <unistd.h>

int get_errno(void) { return errno; }
void set_errno(int e) { errno = e; }

unsigned get_ostid(void) {
  return syscall(SYS_gettid);
}

Compile with:

ghc -o err -threaded err.hs err.c

And the result should look something like:

12282: outer errno is 0
12282: Setting outer errno to 3
12282: inner errno is 0
12282: Setting inner errno to 2
12283: outer errno is 3
12282: inner errno is 2

The OS thread IDs are printed at the beginning of each line. Note that the errno of 3 was migrated to the second OS thread (12283).

like image 100
jekor Avatar answered Sep 28 '22 15:09

jekor