KQueue evtmgr backend fails to register for write events
The root of the problem is that the GHC.Event.KQueue.toFilter function has type GHC.Event.Internal.Event -> Filter with GHC's Event being a bitmask which can represent read events, write events or a combination of those.
It happens that the event manager requests it's backend to be notified about read and write events on some fd, and because the kqueue EVFILT_*s are not bitmasks, the above function cannot capture this, silently dropping the desire to be notified about write events.
The following program triggers the problematic behaviour:
import Control.Concurrent ( forkIO, killThread )
import Control.Exception ( finally )
import Control.Monad.Trans.Resource ( runResourceT )
import Control.Monad.IO.Class ( liftIO )
import qualified Data.ByteString as BS
import Data.Conduit ( ($$), ($=) )
import qualified Data.Conduit.Binary as CB
import qualified Data.Conduit.List as CL
import qualified Data.Conduit.Network as CN
import Data.IORef ( newIORef, modifyIORef', readIORef)
main :: IO ()
main = CN.runTCPClient (CN.clientSettings 5555 "192.168.2.11") client
where
logMsg cnt = CL.mapM $ \bs -> liftIO $ do
modifyIORef' cnt (+ 1)
readIORef cnt >>= \x -> putStrLn $
"msg #" ++ show x ++ " of size: " ++ show (BS.length bs)
return bs
client ad = do
reader <- forkIO (runResourceT $ CN.appSource ad
$$ CL.mapM_ ( \bs -> (liftIO . putStrLn) $
"read " ++ show (BS.length bs) ++ " bytes"))
cnt <- newIORef ( 0 :: Int )
let
runPipe = runResourceT $ CB.sourceFile "cool-linux-distro.iso"
$$ logMsg cnt
$= CN.appSink ad
runPipe `finally` (killThread reader)
Having a nc -l -p 5555 > /dev/null running on another machine is sufficient to sink the data.
Assuming that we can read bigfile.iso faster than we can send out over the socket, the send syscall will at some point give an EAGAIN as can be seen in the truss output:
write(1,"msg #20 of size: 32752\n",23) = 23 (0x17)
sendto(12,"\f\2409\0\M^RA\^T\M-&A\M-'\M-d8"...,32752,0x0,NULL,0x0) = 32752 (0x7ff0)
read(13,"\M^?\0'\\\M-B\M-:+\^]D\M-0\M-="...,32752) = 32752 (0x7ff0)
poll({ 1/POLLOUT },1,0) = 1 (0x1)
msg #21 of size: 32752
write(1,"msg #21 of size: 32752\n",23) = 23 (0x17)
sendto(12,"\M^?\0'\\\M-B\M-:+\^]D\M-0\M-="...,32752,0x0,NULL,0x0) = 19204 (0x4b04)
sendto(12,"\M-j$2\M^BH\M-#-\^A\M-E\^O\M^Y\a"...,13548,0x0,NULL,0x0) ERR#35 'Resource temporarily unavailable'
SIGNAL 26 (SIGVTALRM)
sigprocmask(SIG_SETMASK,{ SIGVTALRM },0x0) = 0 (0x0)
sigreturn(0x7fffffff9c60) ERR#35 'Resource temporarily unavailable'
kevent(3,{ 12,EVFILT_READ,EV_ADD|EV_ONESHOT,0x0,0x0,0x0 },1,0x0,0,{ 0.000000000 }) = 0 (0x0)
_umtx_op(0x800c71ed0,UMTX_OP_WAIT_UINT_PRIVATE,0x0,0x0,0x0) ERR#4 'Interrupted system call'
SIGNAL 26 (SIGVTALRM)
sigprocmask(SIG_SETMASK,{ SIGVTALRM },0x0) = 0 (0x0)
sigreturn(0x7fffffffdc00) ERR#4 'Interrupted system call'
_umtx_op(0x800c71ed0,UMTX_OP_WAIT_UINT_PRIVATE,0x0,0x0,0x0) ERR#4 'Interrupted system call'
SIGNAL 26 (SIGVTALRM)
Not the sendto call on fd 12 resulting in ERR#35, soon followed by an kevent call for that same fd and only EVFILT_READ set. This makes little sense as it was an attempt to write that just failed. This is caused by toFilter giving precedence to read events, dropping the write event. Not starting the reader thread prevents bad things from happening as then the write events are properly passed thru to kqueue.
I have an initial version of a patch fixing this.
Trac metadata
| Trac field | Value |
|---|---|
| Version | 8.0.2 |
| Type | Bug |
| TypeOfFailure | OtherFailure |
| Priority | normal |
| Resolution | Unresolved |
| Component | Runtime System |
| Test case | |
| Differential revisions | |
| BlockedBy | |
| Related | |
| Blocking | |
| CC | |
| Operating system | |
| Architecture |