Permalink
Browse files

Fix OSX RTS crash due to bad coercion.

The code coerces Int to CInt, which causes an overflow if Int is bigger
than CInt (for example, Int 64bit, CInt 32 bit). This results in a
negative value being passed to c_poll.

On Linux all negative values are treated as infinite timeouts, which
gives subtly wrong semantics, but is unlikely to produce actual bugs.

OSX insists that only -1 is a valid value for infinite timeout, any
other negative timeout is treated as an invalid argument.

This patch replaces the c_poll call with a loop that handles the
overflow gracefully by chaining multiple calls to poll to obtain the
proper semantics.
  • Loading branch information...
1 parent 365b9d8 commit 6d8ea02a3f6a2cdb82a9ad786a8c4db780b92091 @merijn committed Jul 24, 2013
Showing with 23 additions and 1 deletion.
  1. +23 −1 GHC/Event/Poll.hsc
View
@@ -37,6 +37,7 @@ import Foreign.Ptr (Ptr)
import Foreign.Storable (Storable(..))
import GHC.Base
import GHC.Conc.Sync (withMVar)
+import GHC.Enum (maxBound)
import GHC.Num (Num(..))
import GHC.Real (ceiling, fromIntegral)
import GHC.Show (Show)
@@ -92,7 +93,7 @@ poll p mtout f = do
E.throwErrnoIfMinus1NoRetry "c_poll" $
case mtout of
Just tout ->
- c_poll ptr (fromIntegral len) (fromIntegral (fromTimeout tout))
+ c_pollLoop ptr (fromIntegral len) (fromTimeout tout)
Nothing ->
c_poll_unsafe ptr (fromIntegral len) 0
unless (n == 0) $ do
@@ -104,6 +105,27 @@ poll p mtout f = do
return (i', i' == n)
else return (i, True)
return (fromIntegral n)
+ where
+ -- The poll timeout is specified as an Int, but c_poll takes a CInt. These
+ -- can't be safely coerced as on many systems (e.g. x86_64) CInt has a a
+ -- maxBound of (2^32 - 1), even though Int may have a significantly higher
+ -- bound.
+ --
+ -- This function deals with timeouts greater than maxBound :: CInt, by
+ -- looping until c_poll returns a non-zero value (0 indicates timeout
+ -- expired) OR the full timeout has passed.
+ c_pollLoop :: Ptr PollFd -> (#type nfds_t) -> Int -> IO CInt
+ c_pollLoop ptr len tout
+ | tout <= maxPollTimeout = c_poll ptr len (fromIntegral tout)
+ | otherwise = do
+ result <- c_poll ptr len (fromIntegral maxPollTimeout)
+ if result == 0
+ then c_pollLoop ptr len (fromIntegral (tout - maxPollTimeout))
+ else return result
+
+ -- Timeout of c_poll is limited by max value of CInt
+ maxPollTimeout :: Int
+ maxPollTimeout = fromIntegral (maxBound :: CInt)
fromTimeout :: E.Timeout -> Int
fromTimeout E.Forever = -1

0 comments on commit 6d8ea02

Please sign in to comment.