[GHC] #14621: GHC 8.2 GCed too early
GHC
ghc-devs at haskell.org
Thu Dec 28 17:53:01 UTC 2017
#14621: GHC 8.2 GCed too early
-------------------------------------+-------------------------------------
Reporter: kazu-yamamoto | Owner: (none)
Type: bug | Status: upstream
Priority: normal | Milestone:
Component: Compiler | Version: 8.2.2
Resolution: | Keywords:
Operating System: Unknown/Multiple | Architecture:
| Unknown/Multiple
Type of failure: None/Unknown | Test Case:
Blocked By: | Blocking:
Related Tickets: | Differential Rev(s):
Wiki Page: |
-------------------------------------+-------------------------------------
Changes (by bgamari):
* status: new => upstream
Old description:
> The network package uses mkWeakVar so that unreachable Sockets can be
> GCed. See the master branch of https://github.com/haskell/network
>
> {{{
> mkSocket :: CInt
> -> Family
> -> SocketType
> -> ProtocolNumber
> -> SocketStatus
> -> IO Socket
> mkSocket fd fam sType pNum stat = do
> mStat <- newMVar stat
> withSocketsDo $ return ()
> let sock = Socket fd fam sType pNum mStat
> ##if MIN_VERSION_base(4,6,0)
> _ <- mkWeakMVar mStat $ close sock
> ##endif
> return sock
> }}}
>
> The following code cause "threadWait: invalid argument (Bad file
> descriptor)" in accept:
>
> {{{
> module Main where
>
> import Control.Concurrent (forkIO)
> import Control.Monad (void, forever)
> import Network.Socket hiding (recv)
> import Network.Socket.ByteString (recv, sendAll)
>
> main :: IO ()
> main = do
> let localhost = "localhost"
> listenPort = "9876"
> connectPort = "6789"
> proxy localhost listenPort connectPort
>
> proxy :: HostName -> ServiceName -> ServiceName -> IO ()
> proxy localhost listenPort connectPort = do
> fromClient <- serverSocket localhost listenPort
> toServer <- clientSocket localhost connectPort
> void $ forkIO $ relay toServer fromClient
> relay fromClient toServer
>
> relay :: Socket -> Socket -> IO ()
> relay s1 s2 = forever $ do
> payload <- recv s1 4096
> sendAll s2 payload
>
> serverSocket :: HostName -> ServiceName -> IO Socket
> serverSocket host port = do
> let hints = defaultHints {
> addrFlags = [AI_PASSIVE]
> , addrSocketType = Stream
> }
> addr:_ <- getAddrInfo (Just hints) (Just host) (Just port)
> sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol
> addr)
> bind sock (addrAddress addr)
> listen sock 1
> fst <$> accept sock
>
> clientSocket :: HostName -> ServiceName -> IO Socket
> clientSocket host port = do
> let hints = defaultHints { addrSocketType = Stream }
> addr:_ <- getAddrInfo (Just hints) (Just host) (Just port)
> sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol
> addr)
> connect sock (addrAddress addr)
> return sock
> }}}
>
> GC frees sock while accepting. This occurs with GHC 8.2 or higher.
New description:
The network package uses mkWeakVar so that unreachable Sockets can be
GCed. See the master branch of https://github.com/haskell/network
{{{#!hs
mkSocket :: CInt
-> Family
-> SocketType
-> ProtocolNumber
-> SocketStatus
-> IO Socket
mkSocket fd fam sType pNum stat = do
mStat <- newMVar stat
withSocketsDo $ return ()
let sock = Socket fd fam sType pNum mStat
##if MIN_VERSION_base(4,6,0)
_ <- mkWeakMVar mStat $ close sock
##endif
return sock
}}}
The following code cause "threadWait: invalid argument (Bad file
descriptor)" in accept:
{{{#!hs
module Main where
import Control.Concurrent (forkIO)
import Control.Monad (void, forever)
import Network.Socket hiding (recv)
import Network.Socket.ByteString (recv, sendAll)
main :: IO ()
main = do
let localhost = "localhost"
listenPort = "9876"
connectPort = "6789"
proxy localhost listenPort connectPort
proxy :: HostName -> ServiceName -> ServiceName -> IO ()
proxy localhost listenPort connectPort = do
fromClient <- serverSocket localhost listenPort
toServer <- clientSocket localhost connectPort
void $ forkIO $ relay toServer fromClient
relay fromClient toServer
relay :: Socket -> Socket -> IO ()
relay s1 s2 = forever $ do
payload <- recv s1 4096
sendAll s2 payload
serverSocket :: HostName -> ServiceName -> IO Socket
serverSocket host port = do
let hints = defaultHints {
addrFlags = [AI_PASSIVE]
, addrSocketType = Stream
}
addr:_ <- getAddrInfo (Just hints) (Just host) (Just port)
sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol
addr)
bind sock (addrAddress addr)
listen sock 1
fst <$> accept sock
clientSocket :: HostName -> ServiceName -> IO Socket
clientSocket host port = do
let hints = defaultHints { addrSocketType = Stream }
addr:_ <- getAddrInfo (Just hints) (Just host) (Just port)
sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol
addr)
connect sock (addrAddress addr)
return sock
}}}
GC frees sock while accepting. This occurs with GHC 8.2 or higher.
--
Comment:
This actually appears to be a bug in the `network` library which has been
exposed by more aggressive optimization by GHC. Specifically, `accept`
doesn't ensure that the `Socket`'s `status` field is kept alive while the
`accept` system call is underway. Consequently the GC correctly concludes
that it can be collected.
I have opened [[https://github.com/haskell/network/issues/287|network
#287]] to track this.
--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/14621#comment:1>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler
More information about the ghc-tickets
mailing list