17
votes

Why does c_sleep return immediately in the following code?

{-# LANGUAGE ForeignFunctionInterface #-}
import Foreign.C.Types
import Data.Time.Clock
import Control.Concurrent

foreign import ccall unsafe "unistd.h sleep" 
    c_sleep :: CUInt -> IO CUInt

main :: IO ()
main = do
    getCurrentTime >>= print . utctDayTime
    c_sleep 10     >>= print                -- this doesn't sleep
    getCurrentTime >>= print . utctDayTime
    threadDelay $ 10 * 1000 * 1000          -- this does sleep
    getCurrentTime >>= print . utctDayTime
$ ghc --make Sleep.hs && ./Sleep
[1 of 1] Compiling Main             ( Sleep.hs, Sleep.o )
Linking Sleep ...
29448.191603s
10
29448.20158s
29458.211402s

$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 7.8.3

$ cabal --version
cabal-install version 1.20.0.3
using version 1.20.0.0 of the Cabal library 

Note: Actually, I would like to use sleep in C code to simulate some heavy computation in a function func and call that function in Haskell, but that doesn't work either, probably for the same reasons.

2
It's possible that the GHC runtime is using signals that disrupt the sleep. Have you checked the error code? Perhaps you should wrap it in a loop to restart it if it gets interrupted (in which case it's best to use nanosleep for higher precision).Rufflewind
@Rufflewind: sleep doesn't return an error code, but the unslept amount in seconds :/. Haven't tried nanosleep yet, but usleep doesn't work either.Zeta
The error code is "returned" via the errno variable. Wrap (c_sleep 10) inside throwErrnoIf (/= 0) "sleep" and you'll see that it's being interrupted.Rufflewind
See also this bug. Not exactly related to this issue but it does say the RTS, even the single-threaded one, sends signals periodically, which will interrupt any sleep call.Rufflewind
@Rufflewind: Thanks. Jeez, why doesn't man 3 sleep mention the errno update? I guess I'll have too write a void nanosleep_loop(uint32_t), since nanosleep gets affected too. The ticket probably answers this question, so feel free to add one.Zeta

2 Answers

12
votes

GHC's RTS appears to use signals for its own purposes, which means it won't be long before a sleep gets interrupted by one of these signals. I don't think it's a bug either, the runtime does come with its own territory, so to speak. The Haskellian approach would be to use threadDelay but it's not easy for a C program to access that without some trickery.

The proper way is to repeatedly resume the sleep despite interruptions from other signals. I recommend using nanosleep since sleep only has a precision of seconds and the signals appear to occur much more frequently than that.

#include <errno.h>
#include <time.h>

/* same as 'sleep' except it doesn't get interrupted by signals */
int keep_sleeping(unsigned long sec) {
    struct timespec rem, req = { (time_t) sec, 0 }; /* warning: may overflow */
    while ((rem.tv_sec || rem.tv_nsec) && nanosleep(&req, &rem)) {
        if (errno != EINTR) /* this check is probably unnecessary */
            return -1;
        req = rem;
    }
    return 0;
}
1
votes

All concurrency primitives always have a clawback statement that they may block for less time than specified - they may return spuriously. This is nothing to do with the language, it is the nature of concurrency, so if you want to wait for exactly the specified amount of time, in any language you need to construct a loop checking the clock after the sleep.