GHC does not use select()/poll() correctly on non-Linux platforms
From my discovery at https://phabricator.haskell.org/D42#30542:
Why does the existing code work on platforms that are not Linux? In my select man page it says:
On Linux, select() modifies timeout to reflect the amount of time not
slept; most other implementations do not do this. (POSIX.1-2001 per‐
mits either behavior.) This causes problems both when Linux code which
reads timeout is ported to other operating systems, and when code is
ported to Linux that reuses a struct timeval for multiple select()s in
a loop without reinitializing it. Consider timeout to be undefined
after select() returns.
The existing select loop seems to rely on the fact that &tv is updated as described here.
Same for man 2 poll
.
E.g. man 2 select
on FreeBSD 11 says explicitly:
BUGS
Version 2 of the Single UNIX Specification (``SUSv2'') allows systems to
modify the original timeout in place. Thus, it is unwise to assume that
the timeout value will be unmodified by the select() system call.
FreeBSD does not modify the return value, which can cause problems for
applications ported from other systems.
I have tested this now on FreeBSD, and indeed it doesn't work as expected.
With GHC 7.10.2:
import System.IO
main = hWaitForInput stdin (1 * 1000)
ghc --make test.hs -rtsopts
[root@ ~]# time ./test
real 0m1.386s
user 0m0.004s
sys 0m0.000s
[root@ ~]# time ./test +RTS -V0.01
real 0m1.386s
user 0m0.001s
sys 0m0.000s
[root@ ~]# time ./test +RTS -V0.001
real 0m1.678s
user 0m0.003s
sys 0m0.002s
[root@ ~]# time ./test +RTS -V0.0001
real 0m11.311s
user 0m0.032s
sys 0m0.139s
See how when we increase the timer signal, the sleep suddenly takes 10x longer than it should.
That's because it triggers the case where EINTR is received in https://github.com/ghc/ghc/blob/f46369b8a1bf90a3bdc30f2b566c3a7e03672518%5E/libraries/base/cbits/inputReady.c\#L48, letting us use the same unmodified 1-second struct timeval *timeout
again and again.
This demo of the bug works for GHC 7.10 and 8.0.1; in 8.0.2 hWaitForInput
is broken (https://ghc.haskell.org/trac/ghc/ticket/12912\#[ticket:13497\#comment:134309](https://gitlab.haskell.org//ghc/ghc/issues/13497#note_134309)) so the demo doesn't work there.
Convenience: Here is the call chain of hWaitForInput