socket 0.7.0.0 → 0.8.0.0
raw patch · 28 files changed
+873/−514 lines, 28 files
Files
- CHANGELOG.md +41/−0
- CONTRIBUTORS.txt +5/−0
- README.md +11/−14
- platform/linux/include/hs_socket.h +2/−0
- platform/linux/src/System/Socket/Internal/Platform.hsc +51/−22
- platform/win32/cbits/hs_socket.c +0/−4
- platform/win32/include/hs_socket.h +0/−2
- platform/win32/src/System/Socket/Internal/Platform.hsc +60/−25
- socket.cabal +3/−1
- src/System/Socket.hsc +123/−122
- src/System/Socket/Family/Inet.hsc +12/−13
- src/System/Socket/Family/Inet6.hsc +16/−16
- src/System/Socket/Internal/AddressInfo.hsc +32/−27
- src/System/Socket/Internal/Exception.hsc +132/−39
- src/System/Socket/Internal/Message.hsc +1/−1
- src/System/Socket/Internal/Socket.hs +96/−0
- src/System/Socket/Internal/Socket.hsc +0/−128
- src/System/Socket/Internal/SocketOption.hsc +115/−0
- src/System/Socket/Protocol/Default.hsc +19/−0
- src/System/Socket/Protocol/TCP.hsc +18/−2
- src/System/Socket/Protocol/UDP.hsc +2/−2
- src/System/Socket/Type/Datagram.hsc +1/−1
- src/System/Socket/Type/Raw.hsc +1/−1
- src/System/Socket/Type/SequentialPacket.hsc +1/−1
- src/System/Socket/Type/Stream.hsc +1/−1
- src/System/Socket/Unsafe.hs +103/−0
- src/System/Socket/Unsafe.hsc +0/−92
- test/test.hs +27/−0
CHANGELOG.md view
@@ -1,3 +1,44 @@+0.8.0.0 Lars Petersen <info@lars-petersen.net> 2017-02-25++ * Issue 26: Show instances for `SocketException` and `AddressInfoException`+ no longer use `strerror()` and `gai_strerror()`, but simply show the name+ of the error code. Linking to `gai_strerror()` was problematic on Windows+ and `strerror()` was not thread-safe on Linux.++ * Issue 38: Added SO_KEEPALIVE as `System.Socket.KeepAlive`.++ * Issue 37: Added TCP_NODELAY as `System.Socket.Protocol.TCP.NoDelay`.++ * Issue 40: Changed allocation function to malloPlainForeignPtr.+ This mechanism used in the ByteString library is heavily optimised and+ 2 - 2.5 times faster than the previous implementation.+ See https://github.com/lpeterse/haskell-socket/pull/40 for details.++ * Issue 31: Added a `Default` protocol type.++ * Issue 27: Fixed a memory leak that manifested when interrupting threads+ waiting on socket events.++ * Issue 25: The `awaitEvent: invalid argument` was caused by the forked-off+ thread in `threadWaitRead`. The new code introduced by the changes caused+ by issue 27 catches and swallows this exception in a sane way.++ * Renamed `unsafeSocketWaitRead/Write/Connected` to `waitRead/Write/Connected`+ as these operations are not really unsafe, just internal. The operation+ signatures changed due to issue 27. Also, documented Windows specific+ implementation details.++ * The `connect` operation does no longer hold the lock on the socket while+ waiting for connection establishment.++ * Refactored and adapted the `accept` operation for changes caused by issue 27.+ Operation semantics shouldn't have changed.++ * Made `SocketAddress` an associated data family and added a+ `Storable (SocketAddress f)` constraint to the `Family` class. This allows+ omitting the `Storable` in socket operations (`Family` suffices now). It+ should be compatible with all existing code and not require any changes.+ 0.7.0.0 Lars Petersen <info@lars-petersen.net> 2016-11-13 * Added function `sendAllLazy` and `sendAllBuilder`. Changed the signature and
CONTRIBUTORS.txt view
@@ -1,3 +1,7 @@+Vyacheslav Hashov (issues #37, #38, #40, #41, #42)+Alexander Vershilov (fixed bug in GHC upstream)+Antom @SX91 (reported and helped with #27)+Lukas Epple (small fix in documentation) Erik Rantapaa (issue #19) Mathieu Boespflug (issue #15) Ben Gamari (issue #10)@@ -5,3 +9,4 @@ Michael Fox (reported issue #7) Bryan O'Sullivan (legitimately criticised the bizarre naming) Alex Högy (reported the MSG_NOSIGNAL issue on OSX)+Lars Petersen
README.md view
@@ -4,11 +4,12 @@ [![Available on Hackage][badge-hackage]][hackage] [![License MIT][badge-license]][license] [![Build Status][badge-travis]][travis]+[![AppVeyor][badge-appveyor]][appveyor] ### Motivation -This library aims to expose a minimal and cross platform interface for-POSIX compliant networking code.+This library aims to expose a minimal and cross-platform interface for+BSD style networking code. ### Implementation Philosophy @@ -51,8 +52,9 @@ This workaround may be removed if someone is willing to sacrifice to improve the IO manager on Windows. -Each release is manually tested on a Windows 10 virtual machine with the-latest Haskell Platform (64bit).+Each commit and release is automatically tested with+[AppVeyor](https://ci.appveyor.com/project/lpeterse/haskell-socket) continuous+integration. #### MacOS @@ -69,20 +71,15 @@ The project uses [tasty](http://documentup.com/feuerbach/tasty) for testing. -There are two test suites: `default` and `threaded` which are using the same-code. Only difference is that one is compiled against GHC's single threaded RTS-and the other against the multi-threaded one. Run `cabal test` to run both-in sequence.--In order to see details and colored output you may also want to try--```bash-ghc --make test/test.hs && ./test/test-```+There are two test suites: `default` and `threaded` which share the same+code. The only difference is that one is compiled against GHC's single threaded+RTS and the other against the multi-threaded one. Run `cabal test` or `stack test`+to execute both in sequence. [badge-travis]: https://img.shields.io/travis/lpeterse/haskell-socket.svg [travis]: https://travis-ci.org/lpeterse/haskell-socket [badge-hackage]: https://img.shields.io/hackage/v/socket.svg?dummy+[badge-appveyor]: https://ci.appveyor.com/api/projects/status/i2il3a616s9yy48k/branch/master?svg=true [hackage]: https://hackage.haskell.org/package/socket [badge-license]: https://img.shields.io/badge/license-MIT-green.svg?dummy [license]: https://github.com/lpeterse/haskell-socket/blob/master/LICENSE
platform/linux/include/hs_socket.h view
@@ -1,6 +1,7 @@ #define _GNU_SOURCE #include <stdint.h>+#include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h>@@ -9,6 +10,7 @@ #include "sys/socket.h" #include "sys/un.h" #include "netinet/in.h"+#include <netinet/tcp.h> #include "netdb.h" int hs_socket (int domain, int type, int protocol, int *err);
platform/linux/src/System/Socket/Internal/Platform.hsc view
@@ -1,36 +1,65 @@-module System.Socket.Internal.Platform where+--------------------------------------------------------------------------------+-- |+-- Module : System.Socket.Internal.Platform+-- Copyright : (c) Lars Petersen 2015+-- License : MIT+--+-- Maintainer : info@lars-petersen.net+-- Stability : experimental+--------------------------------------------------------------------------------+module System.Socket.Internal.Platform+ ( waitRead, waitWrite, waitConnected, c_socket, c_close, c_connect,+ c_accept, c_bind, c_listen, c_recv, c_recvfrom, c_send, c_sendto,+ c_freeaddrinfo, c_getaddrinfo, c_getnameinfo, c_memset, c_gai_strerror,+ c_setsockopt, c_getsockopt) where -import Control.Monad (join)+import Control.Monad ( when, unless )+import Control.Concurrent.MVar+import Control.Concurrent ( threadWaitRead, threadWaitWrite,+ threadWaitReadSTM, threadWaitWriteSTM,+ forkIO, rtsSupportsBoundThreads, killThread )+import Control.Exception ( bracketOnError, mapException, throwIO,+ catch, SomeException(..) ) import Foreign.Ptr import Foreign.C.Types import Foreign.C.String-import GHC.Conc (threadWaitReadSTM, threadWaitWriteSTM, atomically)+import GHC.Conc.Sync ( STM, atomically ) import System.Posix.Types ( Fd(..) )+import System.Socket.Internal.Socket import System.Socket.Internal.Message+import System.Socket.Internal.Exception #include "hs_socket.h" -unsafeSocketWaitWrite :: Fd -> Int -> IO (IO ())-unsafeSocketWaitWrite fd _ = do- threadWaitWriteSTM fd >>= return . atomically . fst+waitRead :: Socket f t p -> Int -> IO ()+waitRead s _ = wait s threadWaitRead threadWaitReadSTM --- | Blocks until a socket should be tried for reading.------ > safeSocketWaitRead = do--- > wait <- withMVar msock $ \sock-> do--- > -- Register while holding a lock on the socket descriptor.--- > unsafeSocketWaitRead sock 0--- > -- Do the waiting without keeping the socket descriptor locked.--- > wait-unsafeSocketWaitRead :: Fd -- ^ Socket descriptor- -> Int -- ^ How many times has it been tried unsuccessfully so far? (currently only relevant on Windows)- -> IO (IO ()) -- ^ The outer action registers the waiting, the inner does the actual wait.-unsafeSocketWaitRead fd _ = do- threadWaitReadSTM fd >>= return . atomically . fst+waitWrite :: Socket f t p -> Int -> IO ()+waitWrite s _ = wait s threadWaitWrite threadWaitWriteSTM -unsafeSocketWaitConnected :: Fd -> IO ()-unsafeSocketWaitConnected fd = do- join $ unsafeSocketWaitWrite fd 0+waitConnected :: Socket f t p -> IO ()+waitConnected = flip waitWrite 0++wait :: Socket f t p -> (Fd -> IO ()) -> (Fd -> IO (STM (), IO ())) -> IO ()+wait (Socket mfd) threadWait threadWaitSTM+ | rtsSupportsBoundThreads = mapException+ ( const eBadFileDescriptor :: IOError -> SocketException )+ ( bracketOnError+ ( withMVar mfd $ \fd -> do+ when (fd < 0) (throwIO eBadFileDescriptor)+ threadWaitSTM fd+ ) snd ( atomically . fst )+ )+ | otherwise = do+ m <- newEmptyMVar+ bracketOnError+ ( withMVar mfd $ \fd-> do+ when (fd < 0) (throwIO eBadFileDescriptor)+ forkIO $ catch+ ( threadWait fd >> putMVar m True )+ ( \(SomeException _)-> putMVar m False )+ ) killThread+ ( const $ takeMVar m >>= flip unless (throwIO eBadFileDescriptor) ) type CSSize = CInt
platform/win32/cbits/hs_socket.c view
@@ -167,10 +167,6 @@ return i; }; -const char *hs_gai_strerror(int errcode) {- return gai_strerror(errcode);-};- int hs_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) {
platform/win32/include/hs_socket.h view
@@ -103,8 +103,6 @@ void hs_freeaddrinfo(struct addrinfo *res); -const char *hs_gai_strerror(int errcode);- #define SEOK 0 #define SEINTR WSAEINTR #define SEAGAIN WSATRY_AGAIN
platform/win32/src/System/Socket/Internal/Platform.hsc view
@@ -1,45 +1,83 @@+--------------------------------------------------------------------------------+-- |+-- Module : System.Socket.Internal.Platform+-- Copyright : (c) Lars Petersen 2015+-- License : MIT+--+-- Maintainer : info@lars-petersen.net+-- Stability : experimental+-------------------------------------------------------------------------------- module System.Socket.Internal.Platform where import Control.Concurrent ( threadDelay )-import Control.Concurrent.MVar ( MVar, withMVar )+import Control.Concurrent.MVar ( withMVar ) import Control.Exception ( throwIO ) import Control.Monad ( when )- import Data.Bits- import Foreign.Ptr import Foreign.C.Types import Foreign.C.String import Foreign.Storable import Foreign.Marshal- import System.Posix.Types ( Fd(..) )-+import System.Socket.Internal.Socket import System.Socket.Internal.Message import System.Socket.Internal.Exception -unsafeSocketWaitWrite :: Fd -> Int -> IO (IO ())-unsafeSocketWaitWrite _ iteration = do- return (threadDelay $ 1 `shiftL` min iteration 20)+-- | Wait until the socket becomes readable (Windows specific version).+--+-- This operation does not actually do anything with the socket. It just+-- calls `threadDelay` with a delay exponentially increasing in the second+-- parameter (0 => 1μs, 20 or greater => 1s). When the operation returns,+-- the socket shall be tried to read from - it does not imply that there is+-- actually data to read and it might be necessary to wait again!+--+-- On Windows, socket descriptors don't behave like file+-- descriptors, but require a specific select mechanism. Unfortunately,+-- this is not (yet) implemented in GHC's RTS and it cannot be called from here+-- as it would block.+--+-- In practice, this means that your application may suffer from reduced+-- responsibility (up to 1s in case the socket has not seen events for 20+-- cycles). Usually, this is only the case for the first of several sequential+-- reads from a socket. Subsequent reads will be executed without delay until+-- there is no more data to read, so it is still possible to achieve high+-- throughput. The central achievement of this approach is that all socket+-- operations offered by the library are interruptable and no special+-- considerations (apart the from one above) apply when running socket code on+-- Windows.+waitRead :: Socket f t p -> Int -> IO ()+waitRead _ iteration =+ threadDelay $ 1 `shiftL` min iteration 20 -unsafeSocketWaitRead :: Fd -> Int -> IO (IO ())-unsafeSocketWaitRead _ iteration = do- return (threadDelay $ 1 `shiftL` min iteration 20)+-- | Wait until the socket becomes writable (Windows specific version).+--+-- See `waitRead` for technical details.+waitWrite :: Socket f t p -> Int -> IO ()+waitWrite _ iteration =+ threadDelay $ 1 `shiftL` min iteration 20 -unsafeSocketWaitConnected :: Fd -> IO ()-unsafeSocketWaitConnected fd =+-- | Wait until the socket is confirmed to be connected (Windows specific version).+--+-- This operation uses an exponential-backoff algorithm as described in+-- `waitRead`. On each iteration `select` is called with a minmal timeout+-- to poll the connection status. This approach keeps the operation interruptable.+waitConnected :: Socket f t p -> IO ()+waitConnected (Socket mfd) = alloca $ \errPtr-> loop errPtr 0 where loop errPtr iteration = do- i <- c_connect_status fd errPtr- when (i /= 0) $ case i of- 1 -> do- -- Wait with exponential backoff.- threadDelay $ 1 `shiftL` min iteration 20- -- Try again.- loop errPtr $! iteration + 1- _ -> do- SocketException <$> peek errPtr >>= throwIO+ i <- withMVar mfd $ \fd-> do+ when (fd < 0) (throwIO eBadFileDescriptor)+ c_connect_status fd errPtr+ case i of+ 0 -> return ()+ 1 -> do+ -- Wait with exponential backoff.+ threadDelay $ 1 `shiftL` min iteration 20+ -- Try again.+ loop errPtr $! iteration + 1+ _ -> SocketException <$> peek errPtr >>= throwIO type CSSize = CInt@@ -94,6 +132,3 @@ foreign import ccall safe "hs_getnameinfo" c_getnameinfo :: Ptr a -> CInt -> CString -> CInt -> CString -> CInt -> CInt -> IO CInt--foreign import ccall unsafe "hs_gai_strerror"- c_gai_strerror :: CInt -> IO CString
socket.cabal view
@@ -1,5 +1,5 @@ name: socket-version: 0.7.0.0+version: 0.8.0.0 synopsis: An extensible socket library. description: This library is a minimal cross-platform interface for@@ -36,8 +36,10 @@ , System.Socket.Type.SequentialPacket , System.Socket.Protocol.UDP , System.Socket.Protocol.TCP+ , System.Socket.Protocol.Default , System.Socket.Unsafe other-modules: System.Socket.Internal.Socket+ , System.Socket.Internal.SocketOption , System.Socket.Internal.Exception , System.Socket.Internal.Message , System.Socket.Internal.AddressInfo
src/System/Socket.hsc view
@@ -124,24 +124,25 @@ , eaiSystem ) where +import Control.Applicative ( (<$>) ) import Control.Exception import Control.Monad-import Control.Applicative import Control.Concurrent import Data.Function import qualified Data.ByteString as BS import qualified Data.ByteString.Unsafe as BS--import GHC.Conc (closeFdWith)+import qualified Data.ByteString.Internal as BS -import Foreign.Ptr-import Foreign.Storable-import Foreign.Marshal.Alloc+import GHC.Conc ( closeFdWith )+ +import Foreign.Storable +import Foreign.Marshal.Alloc import System.Socket.Unsafe import System.Socket.Internal.Socket+import System.Socket.Internal.SocketOption import System.Socket.Internal.Exception import System.Socket.Internal.Message import System.Socket.Internal.AddressInfo@@ -153,13 +154,15 @@ -- -- Whereas the underlying POSIX socket operation takes 3 parameters, this library -- encodes this information in the type variables. This rules out several--- kinds of errors and escpecially simplifies the handling of addresses (by using--- associated type families). Examples:+-- kinds of errors and especially simplifies the handling of addresses (by using+-- associated data families). Examples: ----- > -- create a IPv4-UDP-datagram socket+-- > -- create an IPv4-UDP-datagram socket -- > sock <- socket :: IO (Socket Inet Datagram UDP)--- > -- create a IPv6-TCP-streaming socket+-- > -- create an IPv6-TCP-streaming socket -- > sock6 <- socket :: IO (Socket Inet6 Stream TCP)+-- > -- create an IPv6-streaming socket with default protocol (usually TCP)+-- > sock6 <- socket :: IO (Socket Inet6 Strem Default) -- -- - This operation sets up a finalizer that automatically closes the socket -- when the garbage collection decides to collect it. This is just a@@ -174,13 +177,12 @@ -- > somethingWith sock -- your computation here -- > return somethingelse ----- -- - This operation configures the socket non-blocking to work seamlessly -- with the runtime system's event notification mechanism. -- - This operation can safely deal with asynchronous exceptions without -- leaking file descriptors.--- - This operation throws `SocketException`s. Consult your @man@ page for--- details and specific @errno@s.+-- - This operation throws `SocketException`s. Consult your @man socket@ for+-- details and specific errors. socket :: (Family f, Type t, Protocol p) => IO (Socket f t p) socket = socket' where@@ -206,37 +208,41 @@ -- - This operation returns as soon as a connection has been established (as -- if the socket were blocking). The connection attempt has either failed or -- succeeded after this operation threw an exception or returned.--- - The socket is locked throughout the whole operation. -- - The operation throws `SocketException`s. Calling `connect` on a `close`d -- socket throws `eBadFileDescriptor` even if the former file descriptor has -- been reassigned.-connect :: (Family f, Storable (SocketAddress f)) => Socket f t p -> SocketAddress f -> IO ()-connect (Socket mfd) addr =- withMVar mfd $ \fd-> do- when (fd < 0) (throwIO eBadFileDescriptor)- alloca $ \addrPtr-> alloca $ \errPtr-> do- poke addrPtr addr- let addrLen = fromIntegral (sizeOf addr)- -- The actual connection attempt.- i <- c_connect fd addrPtr addrLen errPtr- -- On non-blocking sockets we expect to get EINPROGRESS or EWOULDBLOCK.- when (i /= 0) $ do- err <- SocketException <$> peek errPtr- if err == eInProgress || err == eWouldBlock- then do- -- The manpage says that in this case the connection- -- shall be established asynchronously and one is- -- supposed to wait.- unsafeSocketWaitConnected fd- -- At least on Linux a second connect after signaled writeability- -- will not fail (the next one would).- i' <- c_connect fd addrPtr addrLen errPtr- when (i' /= 0) $ do- err' <- SocketException <$> peek errPtr- -- On Windows, the second connect fails with `eIsConnected`.- -- In our case this is not an error condition - other errors are.- when (err' /= eIsConnected) (throwIO err')- else throwIO err+connect :: (Family f) => Socket f t p -> SocketAddress f -> IO ()+connect s@(Socket mfd) addr =+ alloca $ \addrPtr-> alloca $ \errPtr-> do+ poke addrPtr addr+ let addrLen = fromIntegral (sizeOf addr)+ -- The actual connection attempt.+ i <- withMVar mfd $ \fd-> do+ when (fd < 0) (throwIO eBadFileDescriptor)+ c_connect fd addrPtr addrLen errPtr+ -- On non-blocking sockets we expect to get EINPROGRESS or EWOULDBLOCK.+ when (i /= 0) $ do+ err <- SocketException <$> peek errPtr+ if err == eInProgress || err == eWouldBlock+ then do+ -- The manpage says that in this case the connection+ -- shall be established asynchronously and one is+ -- supposed to wait.+ waitConnected s+ -- We need to issue a second connect call to get the correct error+ -- code in case the connection has been refused etc.+ -- At least on Linux a second connect after signaled writeability+ -- will not fail in case the connection has been established+ -- sucessfully (the next one would).+ i' <- withMVar mfd $ \fd-> do+ when (fd < 0) (throwIO eBadFileDescriptor)+ c_connect fd addrPtr addrLen errPtr+ when (i' /= 0) $ do+ err' <- SocketException <$> peek errPtr+ -- On Windows, the second connect fails with `eIsConnected`.+ -- In our case this is not an error condition - other errors are.+ when (err' /= eIsConnected) (throwIO err')+ else throwIO err -- | Bind a socket to an address. --@@ -248,7 +254,7 @@ -- [argued here](http://stackoverflow.com/a/14485305). -- - This operation throws `SocketException`s. Consult your @man@ page for -- details and specific @errno@s.-bind :: (Family f, Storable (SocketAddress f)) => Socket f t p -> SocketAddress f -> IO ()+bind :: (Family f) => Socket f t p -> SocketAddress f -> IO () bind (Socket mfd) addr = alloca $ \addrPtr-> alloca $ \errPtr-> do poke addrPtr addr@@ -257,15 +263,15 @@ when (i /= 0) (SocketException <$> peek errPtr >>= throwIO) -- | Starts listening and queueing connection requests on a connection-mode--- socket.+-- socket. The second parameter determines the backlog size. ----- - Calling `listen` on a `close`d socket throws `eBadFileDescriptor` even if the former--- file descriptor has been reassigned.+-- - Calling `listen` on a `close`d socket throws `eBadFileDescriptor` even+-- if the former file descriptor has been reassigned. -- - The second parameter is called /backlog/ and sets a limit on how many--- unaccepted connections the socket implementation shall queue. A value+-- unaccepted connections the transport implementation shall queue. A value -- of @0@ leaves the decision to the implementation.--- - This operation throws `SocketException`s. Consult your @man@ page for--- details and specific @errno@s.+-- - This operation throws `SocketException`s. Consult your @man listen@ for+-- details and specific errors. listen :: Socket f t p -> Int -> IO () listen (Socket ms) backlog = withMVar ms $ \s-> alloca $ \errPtr-> do@@ -274,75 +280,75 @@ -- | Accept a new connection. ----- - Calling `accept` on a `close`d socket throws `eBadFileDescriptor` even if the former--- file descriptor has been reassigned.--- - This operation configures the new socket non-blocking (TODO: use `accept4` if available).+-- - Calling `accept` on a `close`d socket throws `eBadFileDescriptor` even+-- if the former file descriptor has been reassigned.+-- - This operation configures the new socket non-blocking. It uses `accept4`+-- (when available) in order to accept and set the socket non-blocking with+-- a single system call. -- - This operation sets up a finalizer for the new socket that automatically -- closes the new socket when the garbage collection decides to collect it. -- This is just a fail-safe. You might still run out of file descriptors as -- there's no guarantee about when the finalizer is run. You're advised to -- manually `close` the socket when it's no longer needed.--- - This operation throws `SocketException`s. Consult your @man@ page for--- details and specific @errno@s.--- - This operation catches `eAgain`, `eWouldBlock` and `eInterrupted` internally--- and retries automatically.-accept :: (Family f, Storable (SocketAddress f)) => Socket f t p -> IO (Socket f t p, SocketAddress f)+-- - This operation throws `SocketException`s.+-- - This operation catches `eAgain`, `eWouldBlock` and `eInterrupted`+-- internally and retries automatically.+accept :: (Family f) => Socket f t p -> IO (Socket f t p, SocketAddress f) accept s@(Socket mfd) = accept' where- accept' :: forall f t p. (Family f, Storable (SocketAddress f)) => IO (Socket f t p, SocketAddress f)+ accept' :: forall f t p. (Family f) => IO (Socket f t p, SocketAddress f) accept' = do -- Allocate local (!) memory for the address.- alloca $ \addrPtr-> do- alloca $ \addrPtrLen-> alloca $ \errPtr-> do- poke addrPtrLen (fromIntegral $ sizeOf (undefined :: SocketAddress f))- ( fix $ \again iteration-> do- -- We mask asynchronous exceptions during this critical section.- ews <- withMVar mfd $ \fd-> do- when (fd < 0) (throwIO eBadFileDescriptor)- bracketOnError- ( c_accept fd addrPtr addrPtrLen errPtr )- ( \ft-> when (ft >= 0) $ alloca $ void . c_close ft )- ( \ft-> if ft < 0- then do- err <- SocketException <$> peek errPtr- unless (err == eWouldBlock || err == eAgain) (throwIO err)- wait <- unsafeSocketWaitRead fd iteration- return $ Left wait- else do- addr <- peek addrPtr :: IO (SocketAddress f)- -- newMVar is guaranteed to be not interruptible.- mft <- newMVar ft- -- Register a finalizer on the new socket.- _ <- mkWeakMVar mft (close (Socket mft `asTypeOf` s))- return $ Right (Socket mft, addr)- )- -- If ews is Left we got EAGAIN or EWOULDBLOCK and retry after the next event.- case ews of- Left wait -> wait >> (again $! iteration + 1)- Right sock -> return sock- ) 0 -- This is the initial iteration value.+ alloca $ \addrPtr-> alloca $ \addrPtrLen-> alloca $ \errPtr-> do+ poke addrPtrLen (fromIntegral $ sizeOf (undefined :: SocketAddress f))+ ( fix $ \again iteration-> do+ msa <- withMVar mfd $ \fd-> do+ when (fd < 0) (throwIO eBadFileDescriptor)+ bracketOnError+ ( c_accept fd addrPtr addrPtrLen errPtr )+ ( \ft-> when (ft >= 0) $ alloca $ void . c_close ft )+ ( \ft-> if ft < 0+ then do+ err <- SocketException <$> peek errPtr+ -- EWOULDBLOCK and EAGAIN are valid in case there a no+ -- queued connections at the moment and we are supposed to+ -- wait. All other errors are unexpected and we throw them.+ unless (err == eWouldBlock || err == eAgain) (throwIO err)+ return Nothing+ else do+ addr <- peek addrPtr :: IO (SocketAddress f)+ s'@(Socket mft) <- Socket <$> newMVar ft+ -- Register a finalizer on the new socket.+ _ <- mkWeakMVar mft (close s')+ return $ Just (s', addr)+ )+ -- If ews is Left we got EAGAIN or EWOULDBLOCK and retry after the next event.+ case msa of+ Just sa -> return sa+ Nothing -> waitRead s iteration >> (again $! iteration + 1)+ ) 0 -- This is the initial iteration value. --- | Send a message on a connected socket.+-- | Send data. -- -- - Calling `send` on a `close`d socket throws `eBadFileDescriptor` even if the former -- file descriptor has been reassigned.--- - The operation returns the number of bytes sent. On `Datagram` and--- `SequentialPacket` sockets certain assurances on atomicity exist and `eAgain` or--- `eWouldBlock` are returned until the whole message would fit--- into the send buffer.--- - This operation throws `SocketException`s. Consult @man 3p send@ for--- details and specific @errno@s.+-- - The operation returns the number of bytes sent. On `System.Socket.Type.Datagram`+-- and `System.Socket.Type.SequentialPacket` sockets certain assurances on+-- atomicity exist and `eAgain` or `eWouldBlock` are thrown until the+-- whole message would fit into the send buffer.+-- - This operation throws `SocketException`s. Consult @man send@ for+-- details and specific errors. -- - `eAgain`, `eWouldBlock` and `eInterrupted` and handled internally and won't -- be thrown. For performance reasons the operation first tries a write -- on the socket and then waits when it got `eAgain` or `eWouldBlock`. send :: Socket f t p -> BS.ByteString -> MessageFlags -> IO Int send s bs flags = do bytesSent <- BS.unsafeUseAsCStringLen bs $ \(bufPtr,bufSize)->- unsafeSend s (castPtr bufPtr) (fromIntegral bufSize) flags+ unsafeSend s bufPtr (fromIntegral bufSize) flags return (fromIntegral bytesSent) -- | Like `send`, but allows to specify a destination address.-sendTo ::(Family f, Storable (SocketAddress f)) => Socket f t p -> BS.ByteString -> MessageFlags -> SocketAddress f -> IO Int+sendTo ::(Family f) => Socket f t p -> BS.ByteString -> MessageFlags -> SocketAddress f -> IO Int sendTo s bs flags addr = do bytesSent <- alloca $ \addrPtr-> do poke addrPtr addr@@ -350,44 +356,39 @@ unsafeSendTo s bufPtr (fromIntegral bufSize) flags addrPtr (fromIntegral $ sizeOf addr) return (fromIntegral bytesSent) --- | Receive a message on a connected socket.+-- | Receive data. ----- - Calling `receive` on a `close`d socket throws `eBadFileDescriptor` even if the former file descriptor has been reassigned. -- - The operation takes a buffer size in bytes a first parameter which -- limits the maximum length of the returned `Data.ByteString.ByteString`.--- - This operation throws `SocketException`s. Consult @man 3p receive@ for--- details and specific @errno@s.+-- - When an empty `Data.ByteString.ByteString` is returned this usally+-- (protocol specific) means that the peer gracefully closed the connection.+-- The user is advised to check for and handle this case.+-- - Calling `receive` on a `close`d socket throws `eBadFileDescriptor` even+-- if the former file descriptor has been reassigned.+-- - This operation throws `SocketException`s. Consult @man recv@ for+-- details and specific errors. -- - `eAgain`, `eWouldBlock` and `eInterrupted` and handled internally and won't be thrown. -- For performance reasons the operation first tries a read--- on the socket and then waits when it got `eAgain` or `eWouldBlock`.+-- on the socket and then waits when it got `eAgain` or `eWouldBlock` until+-- the socket is signaled to be readable. receive :: Socket f t p -> Int -> MessageFlags -> IO BS.ByteString-receive s bufSize flags =- bracketOnError- ( mallocBytes bufSize )- (\bufPtr-> free bufPtr )- (\bufPtr-> do- bytesReceived <- unsafeReceive s bufPtr (fromIntegral bufSize) flags- BS.unsafePackMallocCStringLen (bufPtr, fromIntegral bytesReceived)- )-+receive s bufSize flags = + BS.createUptoN bufSize $ \bufPtr-> + fromIntegral `fmap` unsafeReceive s bufPtr (fromIntegral bufSize) flags + -- | Like `receive`, but additionally yields the peer address.-receiveFrom :: (Family f, Storable (SocketAddress f)) => Socket f t p -> Int -> MessageFlags -> IO (BS.ByteString, SocketAddress f)+receiveFrom :: (Family f) => Socket f t p -> Int -> MessageFlags -> IO (BS.ByteString, SocketAddress f) receiveFrom = receiveFrom' where- receiveFrom' :: forall f t p. (Family f, Storable (SocketAddress f)) => Socket f t p -> Int -> MessageFlags -> IO (BS.ByteString, SocketAddress f)+ receiveFrom' :: forall f t p. (Family f) => Socket f t p -> Int -> MessageFlags -> IO (BS.ByteString, SocketAddress f) receiveFrom' s bufSize flags = do alloca $ \addrPtr-> do alloca $ \addrSizePtr-> do- poke addrSizePtr (fromIntegral $ sizeOf (undefined :: SocketAddress f))- bracketOnError- ( mallocBytes bufSize )- (\bufPtr-> free bufPtr )- (\bufPtr-> do- bytesReceived <- unsafeReceiveFrom s bufPtr (fromIntegral bufSize) flags addrPtr addrSizePtr- addr <- peek addrPtr- bs <- BS.unsafePackMallocCStringLen (bufPtr, fromIntegral bytesReceived)- return (bs, addr)- )+ poke addrSizePtr (fromIntegral $ sizeOf (undefined :: SocketAddress f)) + bs <- BS.createUptoN bufSize $ \bufPtr-> + fromIntegral `fmap` unsafeReceiveFrom s bufPtr (fromIntegral bufSize) flags addrPtr addrSizePtr + addr <- peek addrPtr + return (bs, addr) -- | Closes a socket. --@@ -395,7 +396,7 @@ -- If it throws an exception it is presumably a not recoverable situation and the process should exit. -- - This operation does not block. -- - This operation wakes up all threads that are currently blocking on this--- socket. All other threads are guaranteed not to block on operations on this socket in the future.+-- socket. All other threads are guaranteed to not block on operations on this socket in the future. -- Threads that perform operations other than `close` on this socket will fail with `eBadFileDescriptor` -- after the socket has been closed (`close` replaces the -- `System.Posix.Types.Fd` in the `Control.Concurrent.MVar.MVar` with @-1@
src/System/Socket/Family/Inet.hsc view
@@ -1,7 +1,7 @@ {-# LANGUAGE TypeFamilies, FlexibleInstances, GeneralizedNewtypeDeriving #-} -------------------------------------------------------------------------------- -- |--- Module : System.Socket+-- Module : System.Socket.Family.Inet -- Copyright : (c) Lars Petersen 2015 -- License : MIT --@@ -59,18 +59,17 @@ instance Family Inet where familyNumber _ = (#const AF_INET)---- | An [IPv4](https://en.wikipedia.org/wiki/IPv4) socket address.------ The socket address contains a port number that may be used by transport--- protocols like [TCP](https://en.wikipedia.org/wiki/Transmission_Control_Protocol).------ > SocketAddressInet inetLoopback 8080-data instance SocketAddress Inet- = SocketAddressInet- { inetAddress :: InetAddress- , inetPort :: InetPort- } deriving (Eq, Show)+ -- | An [IPv4](https://en.wikipedia.org/wiki/IPv4) socket address.+ --+ -- The socket address contains a port number that may be used by transport+ -- protocols like [TCP](https://en.wikipedia.org/wiki/Transmission_Control_Protocol).+ --+ -- > SocketAddressInet inetLoopback 8080+ data SocketAddress Inet+ = SocketAddressInet+ { inetAddress :: InetAddress+ , inetPort :: InetPort+ } deriving (Eq, Show) -- | To avoid errors with endianess it was decided to keep this type abstract. --
src/System/Socket/Family/Inet6.hsc view
@@ -1,7 +1,7 @@ {-# LANGUAGE TypeFamilies, FlexibleInstances, GeneralizedNewtypeDeriving #-} -------------------------------------------------------------------------------- -- |--- Module : System.Socket+-- Module : System.Socket.Family.Inet6 -- Copyright : (c) Lars Petersen 2015 -- License : MIT --@@ -46,6 +46,7 @@ import Foreign.Storable import System.Socket.Internal.Socket+import System.Socket.Internal.SocketOption import System.Socket.Internal.Platform #include "hs_socket.h"@@ -54,25 +55,24 @@ #let alignment t = "%lu", (unsigned long)offsetof(struct {char x__; t (y__); }, y__) #endif --- | The [Internet Protocol version 4](https://en.wikipedia.org/wiki/IPv4).+-- | The [Internet Protocol version 6](https://en.wikipedia.org/wiki/IPv6). data Inet6 instance Family Inet6 where familyNumber _ = (#const AF_INET6)---- | An [IPv6](https://en.wikipedia.org/wiki/IPv6) socket address.------ The socket address contains a port number that may be used by transport--- protocols like [TCP](https://en.wikipedia.org/wiki/Transmission_Control_Protocol).------ > SocketAddressInet6 inet6Loopback 8080 0 0-data instance SocketAddress Inet6- = SocketAddressInet6- { inet6Address :: Inet6Address- , inet6Port :: Inet6Port- , inet6FlowInfo :: Inet6FlowInfo- , inet6ScopeId :: Inet6ScopeId- } deriving (Eq, Show)+ -- | An [IPv6](https://en.wikipedia.org/wiki/IPv6) socket address.+ --+ -- The socket address contains a port number that may be used by transport+ -- protocols like [TCP](https://en.wikipedia.org/wiki/Transmission_Control_Protocol).+ --+ -- > SocketAddressInet6 inet6Loopback 8080 0 0+ data SocketAddress Inet6+ = SocketAddressInet6+ { inet6Address :: Inet6Address+ , inet6Port :: Inet6Port+ , inet6FlowInfo :: Inet6FlowInfo+ , inet6ScopeId :: Inet6ScopeId+ } deriving (Eq, Show) -- | To avoid errors with endianess it was decided to keep this type abstract. --
src/System/Socket/Internal/AddressInfo.hsc view
@@ -2,7 +2,7 @@ FlexibleContexts, TypeFamilies, GeneralizedNewtypeDeriving #-} -------------------------------------------------------------------------------- -- |--- Module : System.Socket+-- Module : System.Socket.Internal.AddressInfo -- Copyright : (c) Lars Petersen 2015 -- License : MIT --@@ -54,8 +54,6 @@ import Foreign.C.String import Foreign.Marshal.Alloc -import System.IO.Unsafe- import System.Socket.Family.Inet import System.Socket.Family.Inet6 import System.Socket.Internal.Socket@@ -67,10 +65,6 @@ #let alignment t = "%lu", (unsigned long)offsetof(struct {char x__; t (y__); }, y__) #endif ----------------------------------------------------------------------------------- AddressInfo--------------------------------------------------------------------------------- data AddressInfo f t p = AddressInfo { addressInfoFlags :: AddressInfoFlags@@ -85,24 +79,33 @@ -- AddressInfoException ------------------------------------------------------------------------------- --- | Contains the error code that can be matched against. Use `show`--- to get a human readable explanation of the error.+-- | Contains the error code that can be matched against.+--+-- Hint: Use guards or @MultiWayIf@ to match against specific exceptions:+--+-- > if | e == eaiFail -> ...+-- > | e == eaiNoName -> ...+-- > | otherwise -> ... newtype AddressInfoException = AddressInfoException CInt deriving (Eq, Typeable) instance Show AddressInfoException where- show e = "AddressInfoException \"" ++ gaiStrerror e ++ "\""+ show e+ | e == eaiAgain = "eaiAgain"+ | e == eaiBadFlags = "eaiBadFlags"+ | e == eaiFail = "eaiFail"+ | e == eaiFamily = "eaiFamily"+ | e == eaiMemory = "eaiMemory"+ | e == eaiNoName = "eaiNoName"+ | e == eaiService = "eaiService"+ | e == eaiSocketType = "eaiSocketType"+ | e == eaiSystem = "eaiSystem"+ | otherwise = let AddressInfoException n = e+ in "AddressInfoException " ++ show n instance Exception AddressInfoException --- | A wrapper around @gai_strerror@.-gaiStrerror :: AddressInfoException -> String-gaiStrerror (AddressInfoException e) =- unsafePerformIO $ do- msgPtr <- c_gai_strerror e- peekCString msgPtr- -- | > AddressInfoException "Temporary failure in name resolution" eaiAgain :: AddressInfoException eaiAgain = AddressInfoException (#const EAI_AGAIN)@@ -156,9 +159,9 @@ aiAddressConfig :: AddressInfoFlags aiAddressConfig = AddressInfoFlags (#const AI_ADDRCONFIG) --- | @AI_ALL@: Return both IPv4 (as mapped `SocketAddressInet6`) and IPv6 addresses when--- `aiV4Mapped` is set independent of whether IPv6 addresses exist for this--- name.+-- | @AI_ALL@: Return both IPv4 (as v4-mapped IPv6 address) and IPv6 addresses+-- when `aiV4Mapped` is set independent of whether IPv6 addresses exist for+-- this name. aiAll :: AddressInfoFlags aiAll = AddressInfoFlags (#const AI_ALL) @@ -216,8 +219,9 @@ niNumericService :: NameInfoFlags niNumericService = NameInfoFlags (#const NI_NUMERICSERV) +-- | This class is for address families that support name resolution. class (Family f) => HasAddressInfo f where- -- | Maps names to addresses (i.e. by DNS lookup).+-- | Maps names to addresses (i.e. by DNS lookup). -- -- The operation throws `AddressInfoException`s. --@@ -312,13 +316,14 @@ , serviceName :: BS.ByteString } deriving (Eq, Show) --- | Maps addresses to readable host- and service names.------ The operation throws `AddressInfoException`s.------ > > getNameInfo (SocketAddressInet inetLoopback 80) mempty--- > NameInfo {hostName = "localhost.localdomain", serviceName = "http"}+-- | This class is for address families that support reverse name resolution. class (Family f) => HasNameInfo f where+ -- | (Reverse-)map an address back to a human-readable host- and service name.+ --+ -- The operation throws `AddressInfoException`s.+ --+ -- > > getNameInfo (SocketAddressInet inetLoopback 80) mempty+ -- > NameInfo {hostName = "localhost.localdomain", serviceName = "http"} getNameInfo :: SocketAddress f -> NameInfoFlags -> IO NameInfo instance HasNameInfo Inet where
src/System/Socket/Internal/Exception.hsc view
@@ -1,14 +1,51 @@ {-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving #-} -------------------------------------------------------------------------------- -- |--- Module : System.Socket+-- Module : System.Socket.Internal.Exception -- Copyright : (c) Lars Petersen 2015 -- License : MIT -- -- Maintainer : info@lars-petersen.net -- Stability : experimental ---------------------------------------------------------------------------------module System.Socket.Internal.Exception where+module System.Socket.Internal.Exception+ ( SocketException (..)+ , eOk+ , eInterrupted+ , eBadFileDescriptor+ , eInvalid+ , ePipe+ , eWouldBlock+ , eAgain+ , eNotSocket+ , eDestinationAddressRequired+ , eMessageSize+ , eProtocolType+ , eNoProtocolOption+ , eProtocolNotSupported+ , eSocketTypeNotSupported+ , eOperationNotSupported+ , eProtocolFamilyNotSupported+ , eAddressFamilyNotSupported+ , eAddressInUse+ , eAddressNotAvailable+ , eNetworkDown+ , eNetworkUnreachable+ , eNetworkReset+ , eConnectionAborted+ , eConnectionReset+ , eNoBufferSpace+ , eIsConnected+ , eNotConnected+ , eShutdown+ , eTooManyReferences+ , eTimedOut+ , eConnectionRefused+ , eHostDown+ , eHostUnreachable+ , eAlready+ , eInProgress+ ) where import Control.Exception import Data.Typeable@@ -16,6 +53,13 @@ #include "hs_socket.h" +-- | Contains the error code that can be matched against.+--+-- Hint: Use guards or @MultiWayIf@ to match against specific exceptions:+--+-- > if | e == eAddressInUse -> ...+-- > | e == eAddressNotAvailable -> ...+-- > | otherwise -> ... newtype SocketException = SocketException CInt deriving (Typeable, Eq, Ord)@@ -23,145 +67,194 @@ instance Exception SocketException instance Show SocketException where- show e@(SocketException i)- | e == eOk = "eOk"- | e == eInterrupted = "eInterrupted"- | e == eBadFileDescriptor = "eBadFileDescriptor"- | e == eInvalid = "eInvalid"- | e == ePipe = "ePipe"- | e == eWouldBlock = "eWouldBlock"- | e == eAgain = "eAgain"- | e == eNotSocket = "eNotSocket"- | e == eDestinationAddressRequired = "eDestinationAddressRequired"- | e == eMessageSize = "eMessageSize"- | e == eProtocolType = "eProtocolType"- | e == eNoProtocolOption = "eNoProtocolOption"- | e == eProtocolNotSupported = "eProtocolNotSupported"- | e == eSocketTypeNotSupported = "eSocketTypeNotSupported"- | e == eOperationNotSupported = "eOperationNotSupported"- | e == eProtocolFamilyNotSupported = "eProtocolFamilyNotSupported"- | e == eAddressFamilyNotSupported = "eAddressFamilyNotSupported"- | e == eAddressInUse = "eAddressInUse"- | e == eAddressNotAvailable = "eAddressNotAvailable"- | e == eNetworkDown = "eNetworkDown"- | e == eNetworkUnreachable = "eNetworkUnreachable"- | e == eNetworkReset = "eNetworkReset"- | e == eConnectionAborted = "eConnectionAborted"- | e == eConnectionReset = "eConnectionReset"- | e == eNoBufferSpace = "eNoBufferSpace"- | e == eIsConnected = "eIsConnected"- | e == eNotConnected = "eNotConnected"- | e == eShutdown = "eShutdown"- | e == eTooManyReferences = "eTooManyReferences"- | e == eTimedOut = "eTimedOut"- | e == eConnectionRefused = "eConnectionRefused"- | e == eHostDown = "eHostDown"- | e == eHostUnreachable = "eHostUnreachable"- | e == eAlready = "eAlready"- | e == eInProgress = "eInProgress"- | otherwise = "SocketException " ++ show i+ show e+ | e == eOk = "eOk"+ | e == eInterrupted = "eInterrupted"+ | e == eBadFileDescriptor = "eBadFileDescriptor"+ | e == eInvalid = "eInvalid"+ | e == ePipe = "ePipe"+ | e == eWouldBlock = "eWouldBlock"+ | e == eAgain = "eAgain"+ | e == eNotSocket = "eNotSocket"+ | e == eDestinationAddressRequired = "eDestinationAddressRequired"+ | e == eMessageSize = "eMessageSize"+ | e == eProtocolType = "eProtocolType"+ | e == eNoProtocolOption = "eNoProtocolOption"+ | e == eProtocolNotSupported = "eProtocolNotSupported"+ | e == eSocketTypeNotSupported = "eSocketTypeNotSupported"+ | e == eOperationNotSupported = "eOperationNotSupported"+ | e == eProtocolFamilyNotSupported = "eProtocolFamilyNotSupported"+ | e == eAddressFamilyNotSupported = "eAddressFamilyNotSupported"+ | e == eAddressInUse = "eAddressInUse"+ | e == eAddressNotAvailable = "eAddressNotAvailable"+ | e == eNetworkDown = "eNetworkDown"+ | e == eNetworkUnreachable = "eNetworkUnreachable"+ | e == eNetworkReset = "eNetworkReset"+ | e == eConnectionAborted = "eConnectionAborted"+ | e == eConnectionReset = "eConnectionReset"+ | e == eNoBufferSpace = "eNoBufferSpace"+ | e == eIsConnected = "eIsConnected"+ | e == eNotConnected = "eNotConnected"+ | e == eShutdown = "eShutdown"+ | e == eTooManyReferences = "eTooManyReferences"+ | e == eTimedOut = "eTimedOut"+ | e == eConnectionRefused = "eConnectionRefused"+ | e == eHostDown = "eHostDown"+ | e == eHostUnreachable = "eHostUnreachable"+ | e == eAlready = "eAlready"+ | e == eInProgress = "eInProgress"+ | otherwise = let SocketException n = e+ in "SocketException " ++ show n +-- | > SocketException "No error" eOk :: SocketException eOk = SocketException (#const SEOK) +-- | > SocketException "Interrupted system call"+--+-- NOTE: This exception shall not be thrown by any public operation in this+-- library, but is handled internally. eInterrupted :: SocketException eInterrupted = SocketException (#const SEINTR) +-- | > SocketException "Bad file descriptor" eBadFileDescriptor :: SocketException eBadFileDescriptor = SocketException (#const SEBADF) +-- | > SocketException "Invalid argument" eInvalid :: SocketException eInvalid = SocketException (#const SEINVAL) +-- | > SocketException "Broken pipe" ePipe :: SocketException ePipe = SocketException (#const SEPIPE) +-- | > SocketException "Resource temporarily unavailable"+--+-- NOTE: This exception shall not be thrown by any public operation in this+-- library, but is handled internally. eWouldBlock :: SocketException eWouldBlock = SocketException (#const SEWOULDBLOCK) +-- | > SocketException "Resource temporarily unavailable" eAgain :: SocketException eAgain = SocketException (#const SEAGAIN) +-- | > SocketException "Socket operation on non-socket"+--+-- NOTE: This should be ruled out by the type system. eNotSocket :: SocketException eNotSocket = SocketException (#const SENOTSOCK) +-- | > SocketException "Destination address required" eDestinationAddressRequired :: SocketException eDestinationAddressRequired = SocketException (#const SEDESTADDRREQ) +-- | > SocketException "Message too long" eMessageSize :: SocketException eMessageSize = SocketException (#const SEMSGSIZE) +-- | > SocketException "Protocol wrong type for socket"++-- NOTE: This should be ruled out by the type system. eProtocolType :: SocketException eProtocolType = SocketException (#const SEPROTOTYPE) +-- | > SocketException "Protocol not available" eNoProtocolOption :: SocketException eNoProtocolOption = SocketException (#const SENOPROTOOPT) +-- | > SocketException "Protocol not supported" eProtocolNotSupported :: SocketException eProtocolNotSupported = SocketException (#const SEPROTONOSUPPORT) +-- | > SocketException "Socket type not supported" eSocketTypeNotSupported :: SocketException eSocketTypeNotSupported = SocketException (#const SESOCKTNOSUPPORT) +-- | > SocketException "Operation not supported" eOperationNotSupported :: SocketException eOperationNotSupported = SocketException (#const SEOPNOTSUPP) +-- | > SocketException "Protocol family not supported" eProtocolFamilyNotSupported :: SocketException eProtocolFamilyNotSupported = SocketException (#const SEPFNOSUPPORT) +-- | > SocketException "Address family not supported by protocol" eAddressFamilyNotSupported :: SocketException eAddressFamilyNotSupported = SocketException (#const SEAFNOSUPPORT) +-- | > SocketException "Address already in use" eAddressInUse :: SocketException eAddressInUse = SocketException (#const SEADDRINUSE) +-- | > SocketException "Cannot assign requested address" eAddressNotAvailable :: SocketException eAddressNotAvailable = SocketException (#const SEADDRNOTAVAIL) +-- | > SocketException "Network is down" eNetworkDown :: SocketException eNetworkDown = SocketException (#const SENETDOWN) +-- | > SocketException "Network is unreachable" eNetworkUnreachable :: SocketException eNetworkUnreachable = SocketException (#const SENETUNREACH) +-- | > SocketException "Network dropped connection on reset" eNetworkReset :: SocketException eNetworkReset = SocketException (#const SENETRESET) +-- | > SocketException "Software caused connection abort" eConnectionAborted :: SocketException eConnectionAborted = SocketException (#const SECONNABORTED) +-- | > SocketException "Connection reset by peer" eConnectionReset :: SocketException eConnectionReset = SocketException (#const SECONNRESET) +-- | > SocketException "No buffer space available" eNoBufferSpace :: SocketException eNoBufferSpace = SocketException (#const SENOBUFS) +-- | > SocketException "Transport endpoint is already connected" eIsConnected :: SocketException eIsConnected = SocketException (#const SEISCONN) +-- | > SocketException "Transport endpoint is not connected" eNotConnected :: SocketException eNotConnected = SocketException (#const SENOTCONN) +-- | > SocketException "Cannot send after transport endpoint shutdown" eShutdown :: SocketException eShutdown = SocketException (#const SESHUTDOWN) +-- | > SocketException "Too many references: cannot splice" eTooManyReferences :: SocketException eTooManyReferences = SocketException (#const SETOOMANYREFS) +-- | > SocketException "Connection timed out" eTimedOut :: SocketException eTimedOut = SocketException (#const SETIMEDOUT) +-- | > SocketException "Connection refused" eConnectionRefused :: SocketException eConnectionRefused = SocketException (#const SECONNREFUSED) +-- | > SocketException "Host is down" eHostDown :: SocketException eHostDown = SocketException (#const SEHOSTDOWN) +-- | > SocketException "No route to host" eHostUnreachable :: SocketException eHostUnreachable = SocketException (#const SEHOSTUNREACH) +-- | > SocketException "Operation already in progress"+--+-- NOTE: This exception shall not be thrown by any public operation in this+-- library, but is handled internally. eAlready :: SocketException eAlready = SocketException (#const SEALREADY) +-- | > SocketException "Operation now in progress" eInProgress :: SocketException eInProgress = SocketException (#const SEINPROGRESS)
src/System/Socket/Internal/Message.hsc view
@@ -1,7 +1,7 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} -------------------------------------------------------------------------------- -- |--- Module : System.Socket+-- Module : System.Socket.Internal.Message -- Copyright : (c) Lars Petersen 2015 -- License : MIT --
+ src/System/Socket/Internal/Socket.hs view
@@ -0,0 +1,96 @@+{-# LANGUAGE TypeFamilies, FlexibleContexts #-}+--------------------------------------------------------------------------------+-- |+-- Module : System.Socket.Internal.Socket+-- Copyright : (c) Lars Petersen 2015+-- License : MIT+--+-- Maintainer : info@lars-petersen.net+-- Stability : experimental+--------------------------------------------------------------------------------+module System.Socket.Internal.Socket (+ Socket (..)+ , Family (..)+ , Type (..)+ , Protocol (..)+ ) where++import Control.Concurrent.MVar+import Foreign.C.Types+import Foreign.Storable+import System.Posix.Types++-- | A generic socket type. Use `System.Socket.socket` to create a new socket.+--+-- The socket is just an `Control.Concurrent.MVar.MVar`-wrapped file descriptor.+-- The `System.Socket.Unsafe.Socket` constructor is exported trough the unsafe+-- module in order to make this library easily extensible, but it is usually+-- not necessary nor advised to work directly on the file descriptor.+-- If you do, the following rules must be obeyed:+--+-- - Make sure not to deadlock. Use `Control.Concurrent.MVar.withMVar` or similar.+-- - The lock __must not__ be held during a blocking call. This would make it impossible+-- to send and receive simultaneously or to close the socket.+-- - The lock __must__ be held when calling operations that use the file descriptor.+-- Otherwise the socket might get closed or even reused by another+-- thread/capability which might result in reading from or writing on a+-- totally different socket. This is a security nightmare!+-- - The socket is non-blocking and all the code relies on that assumption.+-- You need to use GHC's eventing mechanism primitives to block until+-- something happens. The former rules forbid to use `GHC.Conc.threadWaitRead` as it+-- does not separate between registering the file descriptor (for which+-- the lock __must__ be held) and the actual waiting (for which you must+-- __not__ hold the lock).+-- Also see [this](https://mail.haskell.org/pipermail/haskell-cafe/2014-September/115823.html)+-- thread and read the library code to see how the problem is currently circumvented.+newtype Socket f t p+ = Socket (MVar Fd)++-- | The address `Family` determines the network protocol to use.+--+-- The most common address families are `System.Socket.Family.Inet` (IPv4)+-- and `System.Socket.Family.Inet6` (IPv6).+class Storable (SocketAddress f) => Family f where+ -- | The number designating this `Family` on the specific platform. This+ -- method is only exported for implementing extension libraries.+ --+ -- This function shall yield the values of constants like `AF_INET`, `AF_INET6` etc.+ familyNumber :: f -> CInt+ -- | The `SocketAddress` type is a [data family](https://wiki.haskell.org/GHC/Type_families#Detailed_definition_of_data_families).+ -- This allows to provide different data constructors depending on the socket+ -- family without knowing all of them in advance or the need to extend this+ -- core library.+ --+ -- > SocketAddressInet inetLoopback 8080 :: SocketAddress Inet+ -- > SocketAddressInet6 inet6Loopback 8080 0 0 :: SocketAddress Inet6+ data SocketAddress f++-- | The `Type` determines properties of the transport layer and the semantics+-- of basic socket operations.+--+-- The instances supplied by this library are `System.Socket.Type.Raw`+-- (no transport layer), `System.Socket.Type.Stream`+-- (for unframed binary streams, e.g. `System.Socket.Protocol.TCP`),+-- `System.Socket.Type.Datagram` (for datagrams+-- of limited length, e.g. `System.Socket.Protocol.UDP`) and+-- `System.Socket.Type.SequentialPacket` (for framed messages of arbitrary+-- length, e.g. `System.Socket.Protocol.SCTP`).+class Type t where+ -- | This number designates this `Type` on the specific platform. This+ -- method is only exported for implementing extension libraries.+ --+ -- The function shall yield the values of constants like `SOCK_STREAM`,+ -- `SOCK_DGRAM` etc.+ typeNumber :: t -> CInt++-- | The `Protocol` determines the transport protocol to use.+--+-- Use `System.Socket.Protocol.Default` to let the operating system choose+-- a transport protocol compatible with the socket's `Type`.+class Protocol p where+ -- | This number designates this `Protocol` on the specific platform. This+ -- method is only exported for implementing extension libraries.+ --+ -- The function shall yield the values of constants like `IPPROTO_TCP`,+ -- `IPPROTO_UDP` etc.+ protocolNumber :: p -> CInt
− src/System/Socket/Internal/Socket.hsc
@@ -1,128 +0,0 @@-{-# LANGUAGE TypeFamilies #-}------------------------------------------------------------------------------------ |--- Module : System.Socket--- Copyright : (c) Lars Petersen 2015--- License : MIT------ Maintainer : info@lars-petersen.net--- Stability : experimental----------------------------------------------------------------------------------module System.Socket.Internal.Socket (- Socket (..)- , SocketAddress- , Family (..)- , Type (..)- , Protocol (..)- , SocketOption (..)- , unsafeGetSocketOption- , unsafeSetSocketOption- , Error (..)- , ReuseAddress (..)- ) where--import Control.Concurrent.MVar-import Control.Exception-import Control.Monad-import Control.Applicative ((<$>))--import Foreign.Ptr-import Foreign.Storable-import Foreign.C.Types-import Foreign.Marshal.Alloc-import System.Posix.Types--import System.Socket.Internal.Platform-import System.Socket.Internal.Exception--#include "hs_socket.h"---- | A generic socket type. Use `System.Socket.socket` to create a new socket.------ The socket is just an `Control.Concurrent.MVar.MVar`-wrapped file descriptor.--- The `System.Socket.Unsafe.Socket` constructor is exported trough the unsafe--- module in order to make this library easily extensible, but it is usually--- not necessary nor advised to work directly on the file descriptor.--- If you do, the following rules must be obeyed:------ - Make sure not to deadlock. Use `Control.Concurrent.MVar.withMVar` or similar.--- - The lock __must not__ be held during a blocking call. This would make it impossible--- to send and receive simultaneously or to close the socket.--- - The lock __must__ be held when calling operations that use the file descriptor.--- Otherwise the socket might get closed or even reused by another--- thread/capability which might result in reading from or writing--- totally different connection. This is a security nightmare!--- - The socket is non-blocking and all the code relies on that assumption.--- You need to use GHC's eventing mechanism primitives to block until--- something happens. The former rules forbid to use `GHC.Conc.threadWaitRead` as it--- does not separate between registering the file descriptor (for which--- the lock __must__ be held) and the actual waiting (for which you must--- __not__ hold the lock).--- Also see [this](https://mail.haskell.org/pipermail/haskell-cafe/2014-September/115823.html)--- thread and read the library code to see how the problem is currently circumvented.-newtype Socket f t p- = Socket (MVar Fd)---- | The `SocketAddress` type is a [data family](https://wiki.haskell.org/GHC/Type_families#Detailed_definition_of_data_families).--- This allows to provide different data constructors depending on the socket--- family wihtout knowing all of them in advance or the need to patch this--- core library.------ > SocketAddressInet inetLoopback 8080 :: SocketAddress Inet--- > SocketAddressInet6 inet6Loopback 8080 0 0 :: SocketAddress Inet6-data family SocketAddress f--class Family f where- familyNumber :: f -> CInt--class Type t where- typeNumber :: t -> CInt--class Protocol p where- protocolNumber :: p -> CInt--class SocketOption o where- getSocketOption :: Socket f t p -> IO o- setSocketOption :: Socket f t p -> o -> IO ()---- | @SO_ERROR@-data Error- = Error SocketException- deriving (Eq, Ord, Show)--instance SocketOption Error where- getSocketOption s =- Error . SocketException Control.Applicative.<$> unsafeGetSocketOption s (#const SOL_SOCKET) (#const SO_ERROR)- setSocketOption _ _ = throwIO eInvalid---- | @SO_REUSEADDR@-data ReuseAddress- = ReuseAddress Bool- deriving (Eq, Ord, Show)--instance SocketOption ReuseAddress where- getSocketOption s =- ReuseAddress . ((/=0) :: CInt -> Bool) <$> unsafeGetSocketOption s (#const SOL_SOCKET) (#const SO_REUSEADDR)- setSocketOption s (ReuseAddress o) =- unsafeSetSocketOption s (#const SOL_SOCKET) (#const SO_REUSEADDR) (if o then 1 else 0 :: CInt)------------------------------------------------------------------------------------ Unsafe helpers----------------------------------------------------------------------------------unsafeSetSocketOption :: Storable a => Socket f t p -> CInt -> CInt -> a -> IO ()-unsafeSetSocketOption (Socket mfd) level name value =- withMVar mfd $ \fd-> alloca $ \vPtr-> alloca $ \errPtr-> do- poke vPtr value- i <- c_setsockopt fd level name vPtr (fromIntegral $ sizeOf value) errPtr- when (i < 0) (SocketException <$> peek errPtr >>= throwIO)--unsafeGetSocketOption :: Storable a => Socket f t p -> CInt -> CInt -> IO a-unsafeGetSocketOption (Socket mfd) level name =- withMVar mfd $ \fd-> alloca $ \vPtr-> alloca $ \lPtr-> alloca $ \errPtr-> do- u <- return undefined- poke lPtr (fromIntegral $ sizeOf u)- i <- c_getsockopt fd level name vPtr (lPtr :: Ptr CInt) errPtr- when (i < 0) (SocketException <$> peek errPtr >>= throwIO)- x <- peek vPtr- return (x `asTypeOf` u)
+ src/System/Socket/Internal/SocketOption.hsc view
@@ -0,0 +1,115 @@+--------------------------------------------------------------------------------+-- |+-- Module : System.Socket.Internal.SocketOption+-- Copyright : (c) Lars Petersen 2016+-- License : MIT+--+-- Maintainer : info@lars-petersen.net+-- Stability : experimental+--------------------------------------------------------------------------------+module System.Socket.Internal.SocketOption (+ SocketOption (..)+ , unsafeGetSocketOption+ , unsafeSetSocketOption+ , Error (..)+ , ReuseAddress (..)+ , KeepAlive(..)+ ) where++import Control.Concurrent.MVar+import Control.Exception+import Control.Monad+import Control.Applicative ((<$>))++import Foreign.Ptr+import Foreign.Storable+import Foreign.C.Types+import Foreign.Marshal.Alloc++import System.Socket.Internal.Socket+import System.Socket.Internal.Platform+import System.Socket.Internal.Exception++#include "hs_socket.h"++-- | `SocketOption`s allow to read and write certain properties of a socket.+--+-- - Each option shall have a corresponding data type that models the data+-- associated with the socket option.+-- - Use `System.Socket.Unsafe.unsafeGetSocketOption` and+-- `System.Socket.Unsafe.unsafeSetSocketOption` in order to implement custom socket+-- options.+class SocketOption o where+ -- | Get a specific `SocketOption`.+ --+ -- - This operation throws `SocketException`s. Consult @man getsockopt@ for+ -- details and specific errors.+ getSocketOption :: Socket f t p -> IO o+ -- | Set a specific `SocketOption`.+ --+ -- - This operation throws `SocketException`s. Consult @man setsockopt@ for+ -- details and specific errors.+ setSocketOption :: Socket f t p -> o -> IO ()++-- | Reports the last error that occured on the socket.+--+-- - Also known as @SO_ERROR@.+-- - The operation `setSocketOption` always throws `eInvalid` for this option.+-- - Use with care in the presence of concurrency!+data Error+ = Error SocketException+ deriving (Eq, Ord, Show)++instance SocketOption Error where+ getSocketOption s =+ Error . SocketException Control.Applicative.<$> unsafeGetSocketOption s (#const SOL_SOCKET) (#const SO_ERROR)+ setSocketOption _ _ = throwIO eInvalid++-- | Allows or disallows the reuse of a local address in a `System.Socket.bind` call.+--+-- - Also known as @SO_REUSEADDR@.+-- - This is particularly useful when experiencing `eAddressInUse` exceptions.+data ReuseAddress+ = ReuseAddress Bool+ deriving (Eq, Ord, Show)++instance SocketOption ReuseAddress where+ getSocketOption s =+ ReuseAddress . ((/=0) :: CInt -> Bool) <$> unsafeGetSocketOption s (#const SOL_SOCKET) (#const SO_REUSEADDR)+ setSocketOption s (ReuseAddress o) =+ unsafeSetSocketOption s (#const SOL_SOCKET) (#const SO_REUSEADDR) (if o then 1 else 0 :: CInt)++-- | When enabled the protocol checks in a protocol-specific manner+-- if the other end is still alive.+--+-- - Also known as @SO_KEEPALIVE@.+data KeepAlive+ = KeepAlive Bool+ deriving (Eq, Ord, Show)++instance SocketOption KeepAlive where+ getSocketOption s =+ KeepAlive . ((/=0) :: CInt -> Bool) <$> unsafeGetSocketOption s (#const SOL_SOCKET) (#const SO_KEEPALIVE)+ setSocketOption s (KeepAlive o) =+ unsafeSetSocketOption s (#const SOL_SOCKET) (#const SO_KEEPALIVE) (if o then 1 else 0 :: CInt)++-------------------------------------------------------------------------------+-- Unsafe helpers+-------------------------------------------------------------------------------++unsafeSetSocketOption :: Storable a => Socket f t p -> CInt -> CInt -> a -> IO ()+unsafeSetSocketOption (Socket mfd) level name value =+ withMVar mfd $ \fd-> alloca $ \vPtr-> alloca $ \errPtr-> do+ poke vPtr value+ i <- c_setsockopt fd level name vPtr (fromIntegral $ sizeOf value) errPtr+ when (i < 0) (SocketException <$> peek errPtr >>= throwIO)++unsafeGetSocketOption :: Storable a => Socket f t p -> CInt -> CInt -> IO a+unsafeGetSocketOption (Socket mfd) level name =+ withMVar mfd $ \fd-> alloca $ \vPtr-> alloca $ \lPtr-> alloca $ \errPtr-> do+ u <- return undefined+ poke lPtr (fromIntegral $ sizeOf u)+ i <- c_getsockopt fd level name vPtr (lPtr :: Ptr CInt) errPtr+ when (i < 0) (SocketException <$> peek errPtr >>= throwIO)+ x <- peek vPtr+ return (x `asTypeOf` u)
+ src/System/Socket/Protocol/Default.hsc view
@@ -0,0 +1,19 @@+--------------------------------------------------------------------------------+-- |+-- Module : System.Socket.Protocol.Default+-- Copyright : (c) Lars Petersen 2015+-- License : MIT+--+-- Maintainer : info@lars-petersen.net+-- Stability : experimental+--------------------------------------------------------------------------------+module System.Socket.Protocol.Default where++import System.Socket.Internal.Socket++#include "hs_socket.h"++data Default++instance Protocol Default where+ protocolNumber _ = 0
src/System/Socket/Protocol/TCP.hsc view
@@ -1,6 +1,6 @@ -------------------------------------------------------------------------------- -- |--- Module : System.Socket+-- Module : System.Socket.Protocol.TCP -- Copyright : (c) Lars Petersen 2015 -- License : MIT --@@ -9,11 +9,27 @@ -------------------------------------------------------------------------------- module System.Socket.Protocol.TCP where +import Foreign.C.Types+ import System.Socket.Internal.Socket+import System.Socket.Internal.SocketOption #include "hs_socket.h" data TCP -instance Protocol TCP where+instance Protocol TCP where protocolNumber _ = (#const IPPROTO_TCP)++-- | If set to True, disable the Nagle's algorithm.+--+-- - Also know as @TCP_NODELAY@.+data NoDelay+ = NoDelay Bool+ deriving (Eq, Ord, Show)++instance SocketOption NoDelay where+ getSocketOption s =+ (NoDelay . (/=0) :: CInt -> NoDelay) `fmap` unsafeGetSocketOption s (#const IPPROTO_TCP) (#const TCP_NODELAY)+ setSocketOption s (NoDelay o) =+ unsafeSetSocketOption s (#const IPPROTO_TCP) (#const TCP_NODELAY) (if o then 1 else 0 :: CInt)
src/System/Socket/Protocol/UDP.hsc view
@@ -1,6 +1,6 @@ -------------------------------------------------------------------------------- -- |--- Module : System.Socket+-- Module : System.Socket.Protocol.UDP -- Copyright : (c) Lars Petersen 2015 -- License : MIT --@@ -15,5 +15,5 @@ data UDP -instance Protocol UDP where+instance Protocol UDP where protocolNumber _ = (#const IPPROTO_UDP)
src/System/Socket/Type/Datagram.hsc view
@@ -1,6 +1,6 @@ -------------------------------------------------------------------------------- -- |--- Module : System.Socket+-- Module : System.Socket.Type.Datagram -- Copyright : (c) Lars Petersen 2015 -- License : MIT --
src/System/Socket/Type/Raw.hsc view
@@ -1,6 +1,6 @@ -------------------------------------------------------------------------------- -- |--- Module : System.Socket+-- Module : System.Socket.Type.Raw -- Copyright : (c) Lars Petersen 2015 -- License : MIT --
src/System/Socket/Type/SequentialPacket.hsc view
@@ -1,6 +1,6 @@ -------------------------------------------------------------------------------- -- |--- Module : System.Socket+-- Module : System.Socket.Type.SequentialPacket -- Copyright : (c) Lars Petersen 2015 -- License : MIT --
src/System/Socket/Type/Stream.hsc view
@@ -1,6 +1,6 @@ -------------------------------------------------------------------------------- -- |--- Module : System.Socket+-- Module : System.Socket.Type.Stream -- Copyright : (c) Lars Petersen 2015 -- License : MIT --
+ src/System/Socket/Unsafe.hs view
@@ -0,0 +1,103 @@+--------------------------------------------------------------------------------+-- |+-- Module : System.Socket.Unsafe+-- Copyright : (c) Lars Petersen 2015+-- License : MIT+--+-- Maintainer : info@lars-petersen.net+-- Stability : experimental+--------------------------------------------------------------------------------+module System.Socket.Unsafe (+ Socket (..)+ -- * Unsafe operations+ -- ** unsafeSend+ , unsafeSend+ -- ** unsafeSendTo+ , unsafeSendTo+ -- ** unsafeReceive+ , unsafeReceive+ -- ** unsafeReceiveFrom+ , unsafeReceiveFrom+ -- ** unsafeGetSocketOption+ , unsafeGetSocketOption+ -- ** unsafeSetSocketOption+ , unsafeSetSocketOption+ -- * Waiting for events+ -- ** waitRead+ , waitRead+ -- ** waitWrite+ , waitWrite+ -- ** waitConnected+ , waitConnected+ -- ** tryWaitRetryLoop+ , tryWaitRetryLoop+ ) where++import Control.Applicative ( (<$>) )+import Control.Concurrent.MVar+import Control.Exception ( throwIO )+import Control.Monad+import Foreign.C.Types+import Foreign.Ptr+import Foreign.Marshal+import Foreign.Storable+import System.Posix.Types ( Fd(..) )+import System.Socket.Internal.Socket+import System.Socket.Internal.SocketOption+import System.Socket.Internal.Platform+import System.Socket.Internal.Exception+import System.Socket.Internal.Message++-- | Like `System.Socket.send`, but using `Ptr` and length instead of a `ByteString`.+--+-- This function is /unsafe/. @bufPtr@ must refer to a buffer which size is at least @bufSize@ bytes.+unsafeSend :: Socket a t p -> Ptr b -> CSize -> MessageFlags -> IO CInt+unsafeSend s bufPtr bufSize flags =+ tryWaitRetryLoop s waitWrite (\fd-> c_send fd bufPtr bufSize flags )++-- | Like `System.Socket.sendTo`, but using `Ptr` and length instead of a `ByteString`.+--+-- This function is /unsafe/. @bufPtr@ must refer to a buffer which size is at least @bufSize@ bytes.+unsafeSendTo :: Socket f t p -> Ptr b -> CSize -> MessageFlags -> Ptr (SocketAddress f) -> CInt -> IO CInt+unsafeSendTo s bufPtr bufSize flags addrPtr addrSize =+ tryWaitRetryLoop s waitWrite (\fd-> c_sendto fd bufPtr (fromIntegral bufSize) flags addrPtr addrSize)++-- | Like `System.Socket.receive`, but using `Ptr` and length instead of a `ByteString`.+--+-- This function is /unsafe/. @bufPtr@ must refer to a buffer which size is at least @bufSize@ bytes.+unsafeReceive :: Socket a t p -> Ptr b -> CSize -> MessageFlags -> IO CInt+unsafeReceive s bufPtr bufSize flags =+ tryWaitRetryLoop s waitRead (\fd-> c_recv fd bufPtr bufSize flags)++-- | Like `System.Socket.receiveFrom`, but using `Ptr` and length instead of a `ByteString`.+--+-- This function is /unsafe/. @bufPtr@ must refer to a buffer which size is at least @bufSize@ bytes.+unsafeReceiveFrom :: Socket f t p -> Ptr b -> CSize -> MessageFlags -> Ptr (SocketAddress f) -> Ptr CInt -> IO CInt+unsafeReceiveFrom s bufPtr bufSize flags addrPtr addrSizePtr =+ tryWaitRetryLoop s waitRead (\fd-> c_recvfrom fd bufPtr bufSize flags addrPtr addrSizePtr)++tryWaitRetryLoop :: Socket f t p -> (Socket f t p -> Int -> IO ()) -> (Fd -> Ptr CInt -> IO CInt) -> IO CInt+tryWaitRetryLoop s@(Socket mfd) wait action = loop 0+ where+ loop iteration = do+ -- acquire lock on socket+ mi <- withMVar mfd $ \fd-> alloca $ \errPtr-> do+ when (fd < 0) (throwIO eBadFileDescriptor)+ i <- action fd errPtr+ if i < 0 then do+ err <- SocketException <$> peek errPtr+ unless (err == eWouldBlock || err == eAgain || err == eInterrupted)+ (throwIO err)+ return Nothing+ else+ -- The following is quite interesting for debugging:+ -- when (iteration /= 0) (print iteration)+ return (Just i)+ -- lock on socket is release here+ case mi of+ Just i ->+ return i+ Nothing -> do+ wait s iteration -- this call is (eventually) blocking+ loop $! iteration + 1 -- tail recursion+
− src/System/Socket/Unsafe.hsc
@@ -1,92 +0,0 @@------------------------------------------------------------------------------------ |--- Module : System.Socket--- Copyright : (c) Lars Petersen 2015--- License : MIT------ Maintainer : info@lars-petersen.net--- Stability : experimental----------------------------------------------------------------------------------module System.Socket.Unsafe (- -- * Socket Constructor- Socket (..)- -- * Socket Operations- -- ** unsafeSend- , unsafeSend- -- ** unsafeSendTo- , unsafeSendTo- -- ** unsafeReceive- , unsafeReceive- -- ** unsafeReceiveFrom- , unsafeReceiveFrom- -- * Socket Options- -- ** unsafeGetSocketOption- , unsafeGetSocketOption- -- ** unsafeSetSocketOption- , unsafeSetSocketOption- -- * Waiting For Events- -- ** unsafeSocketWaitRead- , unsafeSocketWaitRead- -- ** unsafeSocketWaitWrite- , unsafeSocketWaitWrite- -- * Other Helpers- -- ** tryWaitRetryLoop- , tryWaitRetryLoop- ) where--import Control.Applicative ((<$>))-import Control.Monad-import Control.Exception-import Control.Concurrent.MVar--import Foreign.C.Types-import Foreign.Ptr-import Foreign.Storable-import Foreign.Marshal.Alloc--import System.Socket.Internal.Socket-import System.Socket.Internal.Platform-import System.Socket.Internal.Exception-import System.Socket.Internal.Message--import System.Posix.Types (Fd)--#include "hs_socket.h"--unsafeSend :: Socket a t p -> Ptr b -> CSize -> MessageFlags -> IO CInt-unsafeSend s bufPtr bufSize flags = do- tryWaitRetryLoop s unsafeSocketWaitWrite (\fd-> c_send fd bufPtr bufSize flags )--unsafeSendTo :: Socket f t p -> Ptr b -> CSize -> MessageFlags -> Ptr (SocketAddress f) -> CInt -> IO CInt-unsafeSendTo s bufPtr bufSize flags addrPtr addrSize = do- tryWaitRetryLoop s unsafeSocketWaitWrite (\fd-> c_sendto fd bufPtr (fromIntegral bufSize) flags addrPtr addrSize)--unsafeReceive :: Socket a t p -> Ptr b -> CSize -> MessageFlags -> IO CInt-unsafeReceive s bufPtr bufSize flags =- tryWaitRetryLoop s unsafeSocketWaitRead (\fd-> c_recv fd bufPtr bufSize flags)--unsafeReceiveFrom :: Socket f t p -> Ptr b -> CSize -> MessageFlags -> Ptr (SocketAddress f) -> Ptr CInt -> IO CInt-unsafeReceiveFrom s bufPtr bufSize flags addrPtr addrSizePtr = do- tryWaitRetryLoop s unsafeSocketWaitRead (\fd-> c_recvfrom fd bufPtr bufSize flags addrPtr addrSizePtr)--tryWaitRetryLoop :: Socket f t p -> (Fd -> Int-> IO (IO ())) -> (Fd -> Ptr CInt -> IO CInt) -> IO CInt-tryWaitRetryLoop (Socket mfd) getWaitAction action = loop 0- where- loop iteration = do- ewr <- withMVar mfd $ \fd-> alloca $ \errPtr-> do- when (fd < 0) (throwIO eBadFileDescriptor)- i <- action fd errPtr- if (i < 0) then do- err <- SocketException <$> peek errPtr- unless (err == eWouldBlock || err == eAgain) (throwIO err)- Left <$> getWaitAction fd iteration- else- -- The following is quite interesting for debugging:- -- when (iteration /= 0) (print iteration)- return (Right i)- case ewr of- Left wait -> do- wait- loop $! iteration + 1- Right result -> do- return result
test/test.hs view
@@ -8,6 +8,7 @@ import Control.Monad (unless, void, when) import qualified Data.ByteString.Builder as BB import qualified Data.ByteString.Lazy as LBS+import qualified Data.ByteString as BS import Data.Int (Int64) import Data.Maybe (isJust) import Data.Monoid (mempty, mappend)@@ -202,6 +203,32 @@ send client helloWorld mempty msg <- wait serverRecv when (msg /= helloWorld) (assertFailure "Received message was bogus.")+ )+ , testCase "recv empty bytestring when peer disconnected gracefully" $ bracket+ ( socket :: IO (Socket Inet Stream TCP) ) close+ (\server-> do+ let addr = SocketAddressInet inetLoopback port+ let msg = "msg"+ setSocketOption server (ReuseAddress True)+ bind server addr+ listen server 5+ (peer,msg') <- bracket ( socket :: IO (Socket Inet Stream TCP) ) close+ (\client-> do+ connect client addr+ (peer, _) <- accept server+ _ <- send client msg mempty+ msg' <- receive peer 4096 mempty+ return (peer,msg')+ )+ -- The client is disconnected after here.+ when (msg' /= msg) $+ assertFailure "Received message does not match."+ msg'' <- receive peer 4096 mempty+ unless (BS.null msg'') $+ assertFailure "Expected subsequent receives to return empty bytestring."+ msg''' <- receive peer 4096 mempty+ unless (BS.null msg''') $+ assertFailure "Expected subsequent receives to return empty bytestring." ) , testCase "trigger ePipe exception" $ bracket ( do