Skip to content

Commit 29c11bf

Browse files
committed
Merge PR #460
2 parents 37e43d2 + 445aef9 commit 29c11bf

File tree

3 files changed

+58
-2
lines changed

3 files changed

+58
-2
lines changed

Network/Socket/SockAddr.hs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ module Network.Socket.SockAddr (
1111
, recvBufMsg
1212
) where
1313

14+
import Control.Exception (try, throwIO, IOException)
15+
import System.Directory (removeFile)
16+
import System.IO.Error (isAlreadyInUseError, isDoesNotExistError)
17+
1418
import qualified Network.Socket.Buffer as G
1519
import qualified Network.Socket.Name as G
1620
import qualified Network.Socket.Syscall as G
@@ -41,7 +45,25 @@ connect = G.connect
4145
-- 'defaultPort' is passed then the system assigns the next available
4246
-- use port.
4347
bind :: Socket -> SockAddr -> IO ()
44-
bind = G.bind
48+
bind s a = case a of
49+
SockAddrUnix p -> do
50+
-- gracefully handle the fact that UNIX systems don't clean up closed UNIX
51+
-- domain sockets, inspired by https://stackoverflow.com/a/13719866
52+
res <- try (G.bind s a)
53+
case res of
54+
Right () -> return ()
55+
Left e | not (isAlreadyInUseError e) -> throwIO (e :: IOException)
56+
Left e | otherwise -> do
57+
-- socket might be in use, try to connect
58+
res2 <- try (G.connect s a)
59+
case res2 of
60+
Right () -> close s >> throwIO e
61+
Left e2 | not (isDoesNotExistError e2) -> throwIO (e2 :: IOException)
62+
_ -> do
63+
-- socket not actually in use, remove it and retry bind
64+
removeFile p
65+
G.bind s a
66+
_ -> G.bind s a
4567

4668
-- | Accept a connection. The socket must be bound to an address and
4769
-- listening for connections. The return value is a pair @(conn,

network.cabal

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ library
8383
build-depends:
8484
base >= 4.7 && < 5,
8585
bytestring == 0.10.*,
86-
deepseq
86+
deepseq,
87+
directory
8788

8889
include-dirs: include
8990
includes: HsNet.h HsNetDef.h alignment.h win32defs.h
@@ -141,6 +142,7 @@ test-suite spec
141142
directory,
142143
HUnit,
143144
network,
145+
temporary,
144146
hspec >= 2.6
145147

146148
test-suite doctests

tests/Network/SocketSpec.hs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import Network.Socket
1111
import Network.Socket.ByteString
1212
import Network.Test.Common
1313
import System.Mem (performGC)
14+
import System.IO.Error (tryIOError, isAlreadyInUseError)
15+
import System.IO.Temp (withSystemTempDirectory)
1416

1517
import Test.Hspec
1618

@@ -63,6 +65,36 @@ spec = do
6365
sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)
6466
bind sock (addrAddress addr) `shouldThrow` anyIOException
6567

68+
it "successfully binds to a unix socket, twice" $ do
69+
withSystemTempDirectory "haskell-network" $ \path -> do
70+
let sfile = path ++ "/socket-file"
71+
let addr = SockAddrUnix sfile
72+
when (isSupportedSockAddr addr) $ do
73+
sock0 <- socket AF_UNIX Stream defaultProtocol
74+
bind sock0 addr
75+
listen sock0 1
76+
77+
sock1 <- socket AF_UNIX Stream defaultProtocol
78+
tryIOError (bind sock1 addr) >>= \o -> case o of
79+
Right () -> error "bind should have failed but succeeded"
80+
Left e | not (isAlreadyInUseError e) -> ioError e
81+
_ -> return ()
82+
83+
close sock0
84+
85+
-- Unix systems tend to leave the file existing, which is
86+
-- why our `bind` does its workaround. however if any
87+
-- system in the future does fix this issue, we don't want
88+
-- this test to fail, since that would defeat the purpose
89+
-- of our workaround. but you can uncomment the below lines
90+
-- if you want to play with this on your own system.
91+
--import System.Directory (doesPathExist)
92+
--ex <- doesPathExist sfile
93+
--unless ex $ error "socket file was deleted unexpectedly"
94+
95+
sock2 <- socket AF_UNIX Stream defaultProtocol
96+
bind sock2 addr
97+
6698
describe "UserTimeout" $ do
6799
it "can be set" $ do
68100
when (isSupportedSocketOption UserTimeout) $ do

0 commit comments

Comments
 (0)