Skip to content

Commit e73616c

Browse files
Merge pull request #555 from kazu-yamamoto/timo
Socket timeout
2 parents 176aa09 + 353d8ef commit e73616c

File tree

3 files changed

+152
-91
lines changed

3 files changed

+152
-91
lines changed

Network/Socket.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ module Network.Socket
143143
,RecvIPv4TTL,RecvIPv4TOS,RecvIPv4PktInfo
144144
,RecvIPv6HopLimit,RecvIPv6TClass,RecvIPv6PktInfo)
145145
, StructLinger (..)
146+
, SocketTimeout (..)
146147
, isSupportedSocketOption
147148
, whenSupported
148149
, getSocketOption

Network/Socket/ByteString/IO.hsc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,11 @@ sendManyTo s cs addr = do
232232
--
233233
-- For TCP sockets, a zero length return value means the peer has
234234
-- closed its half side of the connection.
235+
--
236+
-- Currently, the 'recv' family is blocked on Windows because a proper
237+
-- IO manager is not implemented. To use with 'System.Timeout.timeout'
238+
-- on Windows, use 'Network.Socket.setSocketOption' with
239+
-- 'Network.Socket.RecvTimeOut' as well.
235240
recv :: Socket -- ^ Connected socket
236241
-> Int -- ^ Maximum number of bytes to receive
237242
-> IO ByteString -- ^ Data received

Network/Socket/Options.hsc

Lines changed: 146 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ module Network.Socket.Options (
2626
, getSockOpt
2727
, setSockOpt
2828
, StructLinger (..)
29+
, SocketTimeout (..)
2930
) where
3031

3132
import qualified Text.Read as P
@@ -38,7 +39,9 @@ import Network.Socket.Internal
3839
import Network.Socket.Types
3940
import Network.Socket.ReadShow
4041

41-
-----------------------------------------------------------------------------
42+
#include <sys/time.h>
43+
44+
----------------------------------------------------------------
4245
-- Socket Properties
4346

4447
-- | Socket options for use with 'setSocketOption' and 'getSocketOption'.
@@ -55,18 +58,75 @@ data SocketOption = SockOpt
5558
#endif
5659
deriving (Eq)
5760

61+
----------------------------------------------------------------
62+
63+
socketOptionBijection :: Bijection SocketOption String
64+
socketOptionBijection =
65+
[ (UnsupportedSocketOption, "UnsupportedSocketOption")
66+
, (Debug, "Debug")
67+
, (ReuseAddr, "ReuseAddr")
68+
, (SoDomain, "SoDomain")
69+
, (Type, "Type")
70+
, (SoProtocol, "SoProtocol")
71+
, (SoError, "SoError")
72+
, (DontRoute, "DontRoute")
73+
, (Broadcast, "Broadcast")
74+
, (SendBuffer, "SendBuffer")
75+
, (RecvBuffer, "RecvBuffer")
76+
, (KeepAlive, "KeepAlive")
77+
, (OOBInline, "OOBInline")
78+
, (Linger, "Linger")
79+
, (ReusePort, "ReusePort")
80+
, (RecvLowWater, "RecvLowWater")
81+
, (SendLowWater, "SendLowWater")
82+
, (RecvTimeOut, "RecvTimeOut")
83+
, (SendTimeOut, "SendTimeOut")
84+
, (UseLoopBack, "UseLoopBack")
85+
, (MaxSegment, "MaxSegment")
86+
, (NoDelay, "NoDelay")
87+
, (UserTimeout, "UserTimeout")
88+
, (Cork, "Cork")
89+
, (TimeToLive, "TimeToLive")
90+
, (RecvIPv4TTL, "RecvIPv4TTL")
91+
, (RecvIPv4TOS, "RecvIPv4TOS")
92+
, (RecvIPv4PktInfo, "RecvIPv4PktInfo")
93+
, (IPv6Only, "IPv6Only")
94+
, (RecvIPv6HopLimit, "RecvIPv6HopLimit")
95+
, (RecvIPv6TClass, "RecvIPv6TClass")
96+
, (RecvIPv6PktInfo, "RecvIPv6PktInfo")
97+
]
98+
99+
instance Show SocketOption where
100+
showsPrec = bijectiveShow socketOptionBijection def
101+
where
102+
defname = "SockOpt"
103+
unwrap = \(CustomSockOpt nm) -> nm
104+
def = defShow defname unwrap showIntInt
105+
106+
107+
instance Read SocketOption where
108+
readPrec = bijectiveRead socketOptionBijection def
109+
where
110+
defname = "SockOpt"
111+
def = defRead defname CustomSockOpt readIntInt
112+
113+
----------------------------------------------------------------
114+
115+
pattern UnsupportedSocketOption :: SocketOption
116+
pattern UnsupportedSocketOption = SockOpt (-1) (-1)
117+
58118
-- | Does the 'SocketOption' exist on this system?
59119
isSupportedSocketOption :: SocketOption -> Bool
60120
isSupportedSocketOption opt = opt /= SockOpt (-1) (-1)
61121

62-
-- | Get the 'SocketType' of an active socket.
63-
--
64-
-- Since: 3.0.1.0
65-
getSocketType :: Socket -> IO SocketType
66-
getSocketType s = unpackSocketType <$> getSockOpt s Type
122+
-- | Execute the given action only when the specified socket option is
123+
-- supported. Any return value is ignored.
124+
whenSupported :: SocketOption -> IO a -> IO ()
125+
whenSupported s action
126+
| isSupportedSocketOption s = action >> return ()
127+
| otherwise = return ()
67128

68-
pattern UnsupportedSocketOption :: SocketOption
69-
pattern UnsupportedSocketOption = SockOpt (-1) (-1)
129+
----------------------------------------------------------------
70130

71131
#ifdef SOL_SOCKET
72132
-- | SO_ACCEPTCONN, read-only
@@ -192,14 +252,14 @@ pattern SendLowWater = SockOpt (#const SOL_SOCKET) (#const SO_SNDLOWAT)
192252
#else
193253
pattern SendLowWater = SockOpt (-1) (-1)
194254
#endif
195-
-- | SO_RCVTIMEO: this does not work at this moment.
255+
-- | SO_RCVTIMEO: timeout in microseconds
196256
pattern RecvTimeOut :: SocketOption
197257
#ifdef SO_RCVTIMEO
198258
pattern RecvTimeOut = SockOpt (#const SOL_SOCKET) (#const SO_RCVTIMEO)
199259
#else
200260
pattern RecvTimeOut = SockOpt (-1) (-1)
201261
#endif
202-
-- | SO_SNDTIMEO: this does not work at this moment.
262+
-- | SO_SNDTIMEO: timeout in microseconds
203263
pattern SendTimeOut :: SocketOption
204264
#ifdef SO_SNDTIMEO
205265
pattern SendTimeOut = SockOpt (#const SOL_SOCKET) (#const SO_SNDTIMEO)
@@ -317,41 +377,7 @@ pattern CustomSockOpt xy <- ((\(SockOpt x y) -> (x, y)) -> xy)
317377
where
318378
CustomSockOpt (x, y) = SockOpt x y
319379

320-
#if __GLASGOW_HASKELL__ >= 806
321-
{-# COMPLETE CustomSockOpt #-}
322-
#endif
323-
#ifdef SO_LINGER
324-
-- | Low level 'SO_LINBER' option value, which can be used with 'setSockOpt'.
325-
--
326-
data StructLinger = StructLinger {
327-
-- | Set the linger option on.
328-
sl_onoff :: CInt,
329-
330-
-- | Linger timeout.
331-
sl_linger :: CInt
332-
}
333-
deriving (Eq, Ord, Show)
334-
335-
instance Storable StructLinger where
336-
sizeOf _ = (#const sizeof(struct linger))
337-
alignment _ = alignment (0 :: CInt)
338-
339-
peek p = do
340-
onoff <- (#peek struct linger, l_onoff) p
341-
linger <- (#peek struct linger, l_linger) p
342-
return $ StructLinger onoff linger
343-
344-
poke p (StructLinger onoff linger) = do
345-
(#poke struct linger, l_onoff) p onoff
346-
(#poke struct linger, l_linger) p linger
347-
#endif
348-
349-
-- | Execute the given action only when the specified socket option is
350-
-- supported. Any return value is ignored.
351-
whenSupported :: SocketOption -> IO a -> IO ()
352-
whenSupported s action
353-
| isSupportedSocketOption s = action >> return ()
354-
| otherwise = return ()
380+
----------------------------------------------------------------
355381

356382
-- | Set a socket option that expects an 'Int' value.
357383
setSocketOption :: Socket
@@ -363,6 +389,8 @@ setSocketOption s so@Linger v = do
363389
let arg = if v == 0 then StructLinger 0 0 else StructLinger 1 (fromIntegral v)
364390
setSockOpt s so arg
365391
#endif
392+
setSocketOption s so@RecvTimeOut v = setSockOpt s so $ SocketTimeout $ fromIntegral v
393+
setSocketOption s so@SendTimeOut v = setSockOpt s so $ SocketTimeout $ fromIntegral v
366394
setSocketOption s sa v = setSockOpt s sa (fromIntegral v :: CInt)
367395

368396
-- | Set a socket option.
@@ -378,6 +406,8 @@ setSockOpt s (SockOpt level opt) v = do
378406
throwSocketErrorIfMinus1_ "Network.Socket.setSockOpt" $
379407
c_setsockopt fd level opt ptr sz
380408

409+
----------------------------------------------------------------
410+
381411
-- | Get a socket option that gives an 'Int' value.
382412
getSocketOption :: Socket
383413
-> SocketOption -- Option Name
@@ -387,6 +417,12 @@ getSocketOption s so@Linger = do
387417
StructLinger onoff linger <- getSockOpt s so
388418
return $ fromIntegral $ if onoff == 0 then 0 else linger
389419
#endif
420+
getSocketOption s so@RecvTimeOut = do
421+
SocketTimeout to <- getSockOpt s so
422+
return $ fromIntegral to
423+
getSocketOption s so@SendTimeOut = do
424+
SocketTimeout to <- getSockOpt s so
425+
return $ fromIntegral to
390426
getSocketOption s so = do
391427
n :: CInt <- getSockOpt s so
392428
return $ fromIntegral n
@@ -404,56 +440,75 @@ getSockOpt s (SockOpt level opt) = do
404440
c_getsockopt fd level opt ptr ptr_sz
405441
peek ptr
406442

443+
----------------------------------------------------------------
407444

408-
socketOptionBijection :: Bijection SocketOption String
409-
socketOptionBijection =
410-
[ (UnsupportedSocketOption, "UnsupportedSocketOption")
411-
, (Debug, "Debug")
412-
, (ReuseAddr, "ReuseAddr")
413-
, (SoDomain, "SoDomain")
414-
, (Type, "Type")
415-
, (SoProtocol, "SoProtocol")
416-
, (SoError, "SoError")
417-
, (DontRoute, "DontRoute")
418-
, (Broadcast, "Broadcast")
419-
, (SendBuffer, "SendBuffer")
420-
, (RecvBuffer, "RecvBuffer")
421-
, (KeepAlive, "KeepAlive")
422-
, (OOBInline, "OOBInline")
423-
, (Linger, "Linger")
424-
, (ReusePort, "ReusePort")
425-
, (RecvLowWater, "RecvLowWater")
426-
, (SendLowWater, "SendLowWater")
427-
, (RecvTimeOut, "RecvTimeOut")
428-
, (SendTimeOut, "SendTimeOut")
429-
, (UseLoopBack, "UseLoopBack")
430-
, (MaxSegment, "MaxSegment")
431-
, (NoDelay, "NoDelay")
432-
, (UserTimeout, "UserTimeout")
433-
, (Cork, "Cork")
434-
, (TimeToLive, "TimeToLive")
435-
, (RecvIPv4TTL, "RecvIPv4TTL")
436-
, (RecvIPv4TOS, "RecvIPv4TOS")
437-
, (RecvIPv4PktInfo, "RecvIPv4PktInfo")
438-
, (IPv6Only, "IPv6Only")
439-
, (RecvIPv6HopLimit, "RecvIPv6HopLimit")
440-
, (RecvIPv6TClass, "RecvIPv6TClass")
441-
, (RecvIPv6PktInfo, "RecvIPv6PktInfo")
442-
]
445+
-- | Get the 'SocketType' of an active socket.
446+
--
447+
-- Since: 3.0.1.0
448+
getSocketType :: Socket -> IO SocketType
449+
getSocketType s = unpackSocketType <$> getSockOpt s Type
443450

444-
instance Show SocketOption where
445-
showsPrec = bijectiveShow socketOptionBijection def
446-
where
447-
defname = "SockOpt"
448-
unwrap = \(CustomSockOpt nm) -> nm
449-
def = defShow defname unwrap showIntInt
451+
----------------------------------------------------------------
450452

453+
#if __GLASGOW_HASKELL__ >= 806
454+
{-# COMPLETE CustomSockOpt #-}
455+
#endif
456+
#ifdef SO_LINGER
457+
-- | Low level 'SO_LINBER' option value, which can be used with 'setSockOpt'.
458+
--
459+
data StructLinger = StructLinger {
460+
-- | Set the linger option on.
461+
sl_onoff :: CInt,
451462

452-
instance Read SocketOption where
453-
readPrec = bijectiveRead socketOptionBijection def
454-
where
455-
defname = "SockOpt"
456-
def = defRead defname CustomSockOpt readIntInt
463+
-- | Linger timeout.
464+
sl_linger :: CInt
465+
}
466+
deriving (Eq, Ord, Show)
467+
468+
instance Storable StructLinger where
469+
sizeOf _ = (#const sizeof(struct linger))
470+
alignment _ = alignment (0 :: CInt)
471+
472+
peek p = do
473+
onoff <- (#peek struct linger, l_onoff) p
474+
linger <- (#peek struct linger, l_linger) p
475+
return $ StructLinger onoff linger
476+
477+
poke p (StructLinger onoff linger) = do
478+
(#poke struct linger, l_onoff) p onoff
479+
(#poke struct linger, l_linger) p linger
480+
#endif
481+
482+
----------------------------------------------------------------
483+
484+
-- | Timeout in microseconds.
485+
-- This will be converted into struct timeval on Unix and
486+
-- DWORD (as milliseconds) on Windows.
487+
newtype SocketTimeout = SocketTimeout Word32 deriving (Eq, Ord, Show)
488+
489+
#if defined(mingw32_HOST_OS)
490+
instance Storable SocketTimeout where
491+
sizeOf (SocketTimeout to) = sizeOf to -- DWORD as milliseconds
492+
alignment _ = 0
493+
peek ptr = do
494+
to <- peek (castPtr ptr)
495+
return $ SocketTimeout (to * 1000)
496+
poke ptr (SocketTimeout to) = poke (castPtr ptr) (to `div` 1000)
497+
#else
498+
instance Storable SocketTimeout where
499+
sizeOf _ = (#size struct timeval)
500+
alignment _ = (#const offsetof(struct {char x__; struct timeval (y__); }, y__))
501+
peek ptr = do
502+
sec <- (#peek struct timeval, tv_sec) ptr
503+
usec <- (#peek struct timeval, tv_usec) ptr
504+
return $ SocketTimeout (sec * 1000000 + usec)
505+
poke ptr (SocketTimeout to) = do
506+
let (sec, usec) = to `divMod` 1000000
507+
(#poke struct timeval, tv_sec) ptr sec
508+
(#poke struct timeval, tv_usec) ptr usec
509+
#endif
510+
511+
----------------------------------------------------------------
457512

458513
foreign import CALLCONV unsafe "getsockopt"
459514
c_getsockopt :: CInt -> CInt -> CInt -> Ptr a -> Ptr CInt -> IO CInt

0 commit comments

Comments
 (0)