select 0.3 → 0.4
raw patch · 22 files changed
+736/−250 lines, 22 filesdep −vector
Dependencies removed: vector
Files
- example/Example.hs +0/−40
- example/FdSetExample.hs +40/−0
- example/SelectExample.hs +41/−0
- example/SelectExample2.hs +56/−0
- example/TestStdin.hs +17/−0
- example/build.sh +19/−0
- select.cabal +40/−15
- src/Misc.hs +23/−0
- src/System/Posix/IO/Select.hs +91/−37
- src/System/Posix/IO/Select/FdSet.hs +100/−0
- src/System/Posix/IO/Select/FdSet/Internal.hsc +51/−0
- src/System/Posix/IO/Select/FdSet/Unsafe.hs +56/−0
- src/System/Posix/IO/Select/FdSet/cbits.c +27/−0
- src/System/Posix/IO/Select/FdSet/cbits.h +9/−0
- src/System/Posix/IO/Select/MVar.hs +0/−17
- src/System/Posix/IO/Select/STM.hs +0/−51
- src/System/Posix/IO/Select/Types.hs +0/−10
- src/System/Posix/IO/Select/Types.hsc +65/−0
- src/System/Posix/IO/Select/cbits.c +94/−0
- src/System/Posix/IO/Select/cbits.h +7/−0
- src/System/Posix/IO/Select/select_wrapper.c +0/−73
- src/System/Posix/IO/Select/select_wrapper.h +0/−7
− example/Example.hs
@@ -1,40 +0,0 @@-module Main where--import System.Posix.IO-import System.Posix.IO.Select-import Control.Concurrent-import Control.Concurrent.STM.OrElseIO-import Control.Concurrent.STM-import Control.Concurrent.STM.TMVar-import Control.Concurrent.STM.TChan--flags :: OpenFileFlags-flags = OpenFileFlags { append = False, exclusive = False, noctty = False, nonBlock = True, trunc = False }---- Needs the threaded runtime!-main :: IO ()-main = - newEmptyTMVarIO >>= \t ->- forkIO (interruptor t) >>- putStrLn "The interruptor has been launched." >>- newTChanIO >>= \c ->- forkIO (interruptor2 c) >>- putStrLn "The TChan interruptor has been launched." >>- openFd "fifo" ReadOnly Nothing flags >>= \fd -> - openFd "fifo2" ReadOnly Nothing flags >>= \fd2 -> - openFd "fifo3" ReadOnly Nothing flags >>= \fd3 -> - openFd "fifo4" ReadOnly Nothing flags >>= \fd4 -> - putStrLn "The file was opened, so let the waiting game begin!" >>- runOrElse (select [fd,fd2,fd3,fd4] [] [] (Time 9 0)) (readTChan c) >>= \ret ->- case ret of- Left 0 -> putStrLn "select() timed out on the files."- Left x -> putStrLn "There's data in one of the files!"- Right s -> putStrLn $ "Oh no, the interruptor is here: " ++ s --interruptor :: TMVar String -> IO ()-interruptor t = threadDelay (10 * 1000000) >>- atomically (putTMVar t "Bwahahaha, the interruptor was first!")--interruptor2 :: TChan String -> IO ()-interruptor2 c = threadDelay (10 * 1000000) >>- atomically (writeTChan c "Bwhaha, TChan interruptor was first!")
+ example/FdSetExample.hs view
@@ -0,0 +1,40 @@+module Main where++import Prelude hiding (elem)+import System.Posix.IO+import System.Posix.IO.Select.FdSet.Unsafe+import System.Posix.IO.Select.Types++flags :: OpenFileFlags+flags = OpenFileFlags { append = False, exclusive = False, noctty = False, nonBlock = True, trunc = False }++main :: IO ()+main = openFd "fifo" ReadOnly Nothing flags >>= \fd -> + openFd "fifo2" ReadOnly Nothing flags >>= \fd2 -> + openFd "fifo3" ReadOnly Nothing flags >>= \fd3 -> + openFd "fifo4" ReadOnly Nothing flags >>= \fd4 -> + putStrLn "Making" >>+ let+ set = fromList [fd, fd3]+ set2 = insert fd4 set+ set3 = fromList [fd, fd3]+ set4 = remove fd4 (remove fd3 (remove fd2 (remove fd set3)))+ in+ print (fd `elem` set) >> + print (fd2 `elem` set) >> + print (fd3 `elem` set) >>+ print (fd4 `elem` set) >> + print [fd, fd3] >>+ print (unsafeToList set) >>+ print (fd `elem` set2) >> + print (fd2 `elem` set2) >> + print (fd3 `elem` set2) >>+ print (fd4 `elem` set2) >>+ print (fd `elem` set3) >> + print (fd2 `elem` set3) >> + print (fd3 `elem` set3) >>+ print (fd4 `elem` set3) >>+ print (fd `elem` set4) >> + print (fd2 `elem` set4) >> + print (fd3 `elem` set4) >>+ print (fd4 `elem` set4)
+ example/SelectExample.hs view
@@ -0,0 +1,41 @@+module Main where++import System.Posix.IO+import System.Posix.IO.Select.FdSet.Unsafe+import System.Posix.IO.Select+import System.Posix.IO.Select.Types+import System.Posix.Types+import Control.Concurrent+import Control.Concurrent.STM.OrElseIO+import Control.Concurrent.STM+import Control.Concurrent.STM.TMVar+import Control.Concurrent.STM.TChan++flags :: OpenFileFlags+flags = OpenFileFlags { append = False, exclusive = False, noctty = False, nonBlock = True, trunc = False }++main :: IO ()+main =+ newEmptyTMVarIO >>= \t ->+ forkIO (interruptor t) >>+ putStrLn "The interruptor has been launched." >>+ newTChanIO >>= \c ->+ forkIO (interruptor2 c) >>+ putStrLn "The TChan interruptor has been launched." >>+ openFd "fifo" ReadOnly Nothing flags >>= \fd -> + openFd "fifo2" ReadOnly Nothing flags >>= \fd2 -> + openFd "fifo3" ReadOnly Nothing flags >>= \fd3 -> + openFd "fifo4" ReadOnly Nothing flags >>= \fd4 -> + putStrLn "The file was opened, so let the waiting game begin!" >>+ runOrElse (select' [fd,fd2,fd3,fd4] [] [] (finite 5 0)) (readTChan c) >>= \ret ->+ case ret of+ Left x -> putStrLn ("select() returned: " ++ show x)+ Right s -> putStrLn ("Oh no, the interruptor is here: " ++ s )++interruptor :: TMVar String -> IO ()+interruptor t = threadDelay (10 * 1000000) >>+ atomically (putTMVar t "Bwahahaha, the interruptor was first!")++interruptor2 :: TChan String -> IO ()+interruptor2 c = threadDelay (15 * 1000000) >>+ atomically (writeTChan c "Bwhaha, TChan interruptor was first!")
+ example/SelectExample2.hs view
@@ -0,0 +1,56 @@+module Main where++import System.IO+import System.Posix.IO+import System.Posix.IO.Select.FdSet.Unsafe+import System.Posix.IO.Select+import System.Posix.IO.Select.Types+import System.Posix.Types+import Control.Concurrent+import Control.Concurrent.STM.OrElseIO+import Control.Concurrent.STM+import Control.Concurrent.STM.TMVar+import Control.Concurrent.STM.TChan++flags :: OpenFileFlags+flags = OpenFileFlags { append = False, exclusive = False, noctty = False, nonBlock = True, trunc = False }++main :: IO ()+main =+ handleToFd stdin >>= \stdinfd ->+ newEmptyTMVarIO >>= \t ->+ forkIO (interruptor t) >>+ putStrLn "The interruptor has been launched." >>+ newTChanIO >>= \c ->+ forkIO (interruptor2 c) >>+ putStrLn "The TChan interruptor has been launched." >>+ openFd "fifo" ReadOnly Nothing flags >>= \fd -> + openFd "fifo2" ReadOnly Nothing flags >>= \fd2 -> + openFd "fifo3" ReadOnly Nothing flags >>= \fd3 -> + openFd "fifo4" ReadOnly Nothing flags >>= \fd4 -> + let+ readfds = [fd, fd2, fd3, stdinfd, fd4]+ writefds = []+ exceptfds = []+ in+ putStrLn "The file was opened, so let the waiting game begin!" >>+ runOrElse (select (fromList readfds) empty empty (finite 9 0)) (readTChan c) >>= \ret ->+ case ret of+ Left x -> (+ case x of+ Error -> putStrLn ("Select error.")+ Timeout -> putStrLn ("Select timed out.")+ Ready n _ _ _ -> putStrLn ("There are " ++ show n ++ " readies.")+ ) >>+ putStrLn ("Ready read: " ++ (show (inList readfds (readyRead x)))) >>+ putStrLn ("Ready write: " ++ (show (inList writefds (readyWrite x)))) >>+ putStrLn ("Ready except: " ++ (show (inList exceptfds (readyException x))))+ Right s -> putStrLn ("Oh no, the interruptor is here: " ++ s )++interruptor :: TMVar String -> IO ()+interruptor t = threadDelay (10 * 1000000) >>+ atomically (putTMVar t "Bwahahaha, the interruptor was first!")++interruptor2 :: TChan String -> IO ()+interruptor2 c = threadDelay (15 * 1000000) >>+ atomically (writeTChan c "Bwhaha, TChan interruptor was first!")
+ example/TestStdin.hs view
@@ -0,0 +1,17 @@+module Main where++import System.Posix.IO.Select+import System.Posix.IO.Select.Types+import System.Posix.IO+import System.Posix.Types+import System.IO++main :: IO ()+main = do+ fd <- handleToFd stdin+ n <- select'' [fd] [] [] (finite 0 500)+ case n of+ 0 -> print "timeout"+ -1 -> error "select failed"+ _ -> print "event"+ return ()
+ example/build.sh view
@@ -0,0 +1,19 @@+#!/bin/bash++p=$(pwd)++cobjs="../src/System/Posix/IO/Select/FdSet/cbits.o ../src/System/Posix/IO/Select/cbits.o"++cd ../src/System/Posix/IO/Select/FdSet/+hsc2hs Internal.hsc+gcc -DNDEBUG -Wall -std=c99 -c cbits.c+cd ..+gcc -Wall -std=c99 -lrt -c cbits.c+hsc2hs Types.hsc+cd $p++ghc -fforce-recomp -threaded -o stdin-test --make TestStdin.hs -i../src $cobjs+ghc -fforce-recomp -threaded -o fdset-example --make FdSetExample.hs -i../src $cobjs+ghc -fforce-recomp -threaded -o select-example --make SelectExample.hs -i../src $cobjs+ghc -fforce-recomp -threaded -o select-example2 --make SelectExample2.hs -i../src $cobjs+
select.cabal view
@@ -1,5 +1,5 @@ Name: select-Version: 0.3+Version: 0.4 Synopsis: Wrap the select(2) POSIX function Homepage: http://nonempty.org/software/haskell-select License: BSD3@@ -9,35 +9,60 @@ Copyright: 2012 Gard Spreemann Category: System Build-type: Simple-Extra-source-files: src/System/Posix/IO/Select/select_wrapper.c src/System/Posix/IO/Select/select_wrapper.h+Extra-source-files: src/System/Posix/IO/Select/cbits.c, + src/System/Posix/IO/Select/cbits.h,+ src/System/Posix/IO/Select/FdSet/cbits.c,+ src/System/Posix/IO/Select/FdSet/cbits.h Cabal-version: >=1.2 Description: While tinkering on a project, I frequently found myself having to make FFI calls to @select(2)@. This package provides an interface to that system call.- It also used to expose an STM interface for running @select(2)@ with alternative STM actions,- but that functionality was split into the stm-orelse-io package from version 0.3. .- Changes in version 0.3:+ Changes in version 0.4: .- * Split all STM-related functionality into a separate package, stm-orelse-io, independent- of select.+ * Introduce an interface to @fd_set@. .+ * Add functions with various amounts of abstraction; in particular, 'select'''+ matches 'select' in previous versions.+ .+ * Retry @select(2)@ in case of interruption by signals.+ .+ * Remove dependence on the vector package.+ .+ WARNINGS:+ .+ * Behavior with write and exception file descriptors is under-tested. + Feedback is welcome.+ .+ * This package is far from complete, and should be tested throughly before being relied upon.+ . TODO: .- * Provide a type for @fd_set@ that can be passed to and from C so that we can have a version - of 'System.Posix.IO.select' that reports /which/ file descriptors are ready, instead of how many.- Its type will be something like @[Fd] -> [Fd] -> [Fd] -> Timeout -> IO ([Fd], [Fd], [Fd])@.+ * Return the error code specified by errno if @select@ fails. .+ * Possibly move the select retry code from C to Haskell.+ .+ * Reorder functions to make the documentation more logical.+ .+ * Use hsc2hs's #const to get preprocessor constants (removes some of FdSet's cbits.c).+ . /NOTE/: I feel I'm occupying prime namespace realestate with a package name- like select. I'll happily let myself be chased away if anybody else wants+ like select. I'll happily let myself be chased away if someone more qualified wants to use this package name. Let me know. Library Exposed-modules: System.Posix.IO.Select,- System.Posix.IO.Select.Types+ System.Posix.IO.Select.Types,+ System.Posix.IO.Select.FdSet,+ System.Posix.IO.Select.FdSet.Unsafe+ Other-modules: Misc,+ System.Posix.IO.Select.FdSet.Internal hs-source-dirs: src- Build-depends: base >= 4 && <5, vector+ Build-depends: base >= 4 && <5 - c-sources: src/System/Posix/IO/Select/select_wrapper.c+ c-sources: src/System/Posix/IO/Select/cbits.c,+ src/System/Posix/IO/Select/FdSet/cbits.c+ include-dirs: src/System/Posix/IO/Select/+ src/System/Posix/IO/Select/FdSet/ cc-options: -std=c99 -Wall -DNDEBUG -+ extra-libraries: rt
+ src/Misc.hs view
@@ -0,0 +1,23 @@+-- | Some internally used convenience functions.+module Misc where++import Foreign.C.Types+import Foreign.ForeignPtr+import Foreign.Storable+import Foreign.Ptr++mallocInitForeignPtr :: (Storable a) => (Ptr a -> IO ()) -> IO (ForeignPtr a)+mallocInitForeignPtr initializer = mallocForeignPtr >>= \fptr ->+ withForeignPtr fptr initializer >>+ return fptr++nullForeignPtr :: IO (ForeignPtr a)+nullForeignPtr = newForeignPtr_ nullPtr++cError :: CInt -> Bool+cError = (== -1)++cTrue :: CInt -> Bool+cTrue 0 = False+cTrue _ = True +
src/System/Posix/IO/Select.hs view
@@ -1,48 +1,102 @@ {-# LANGUAGE ForeignFunctionInterface #-} --- | Interface to the @select(2)@ POSIX function.--- --- TODO: +-- | Interface to @select(2)@. The arguments to the functions exposed+-- here must fulfill the same requirements as the arguments for+-- @select@ itself; see the man page. ----- * Make a version of @'select'@ that shows which file descriptors--- are ready. (Needs a type for fd_set that can be passed to and from--- C).-module System.Posix.IO.Select (select, Timeout(Never, Time)) where+-- If the @select@ call made by any of these functions is interrupted+-- (@select@ returning @-1@ and @errno@ being @EINTR@) before the+-- given time has elapsed, the call will be retried. If the specified+-- amount of time has passed and @select@ is still being interrupted,+-- the functions below will make one last attempt with timeout+-- zero. If that call too is interrupted, behavior will be as if+-- @select@ returned an error.+module System.Posix.IO.Select (select, select', select'') where -import System.Posix.IO.Select.Types+import System.Posix.IO.Select.Types +import qualified System.Posix.IO.Select.FdSet as FS+import qualified System.Posix.IO.Select.FdSet.Unsafe as FSUNSAFE+import qualified System.Posix.IO.Select.FdSet.Internal as FSI import Foreign import Foreign.C.Types import System.Posix.Types-import qualified Data.Vector.Storable as V -- For contiguous memory and passing to C. Should perhaps be an Array instead.+import Foreign.Ptr+import Foreign.ForeignPtr+import Misc -foreign import ccall "select_wrapper.h select_wrapper"- c_select_wrapper :: Ptr CInt -> CInt -> - Ptr CInt -> CInt -> - Ptr CInt -> CInt ->- CChar -> CLong -> CLong -> - IO CInt+foreign import ccall "Select/cbits.h xselect"+ c_xselect :: Fd -> Ptr () -> Ptr () -> Ptr () -> Ptr () -> IO CInt+-- nfds readfds writefds exceptfds timeout --- | @'select' readFds writeFds exceptFds timeout@ calls the--- @select(2)@ function with the file descriptors in @readFds@ as the--- FD set to watch for read readiness, and similarly for @writeFds@--- and @exceptFds@, with @timeout@ specifying the timeout. The return--- value is that of the call.-select :: [Fd] -> [Fd] -> [Fd] -> Timeout -> IO CInt-select readFds writeFds exceptFds timeout =- let- rfds = V.fromList (map fromIntegral readFds) :: V.Vector CInt -- Make contiguous memory.- nr = fromIntegral (V.length rfds)- wfds = V.fromList (map fromIntegral writeFds) :: V.Vector CInt- nw = fromIntegral (V.length wfds)- efds = V.fromList (map fromIntegral exceptFds) :: V.Vector CInt- ne = fromIntegral (V.length efds)- (tFlag, s, us) = timeoutToC timeout- in- -- Yikes, the next line is ugly. But stare at unsafeWith, and you'll see this is right.- V.unsafeWith rfds $ \pr -> V.unsafeWith wfds $ \pw -> V.unsafeWith efds $ \pe -> - c_select_wrapper pr nr pw nw pe ne tFlag s us +-- | The first 'FS.FdSet' is watched for readiness to read, the second+-- for readiness to write, and the third for exceptions. The 'Timeout'+-- argument specifies when @select@ should time out (if at all). +select :: FS.FdSet -> FS.FdSet -> FS.FdSet -> Timeout -> IO Result+select rfds wfds efds timeout =+ (+ case timeout of+ Never -> nullForeignPtr+ Time timeval -> mallocInitForeignPtr ((flip poke) timeval)+ ) >>= \timevalp ->+ FS.duplicate rfds >>= \(FSI.FdSet rfdsp brfd) ->+ FS.duplicate wfds >>= \(FSI.FdSet wfdsp bwfd) ->+ FS.duplicate efds >>= \(FSI.FdSet efdsp befd) ->+ withForeignPtr rfdsp $ \rfdsp' -> + withForeignPtr wfdsp $ \wfdsp' -> + withForeignPtr efdsp $ \efdsp' ->+ withForeignPtr timevalp $ \timevalp' -> + c_xselect (1 + maximum [brfd, bwfd, befd]) rfdsp' wfdsp' efdsp' (castPtr timevalp') >>= \ret ->+ return (+ if cError ret+ then Error+ else if cTrue ret+ then Ready ret (FSI.FdSet rfdsp brfd) (FSI.FdSet wfdsp bwfd) (FSI.FdSet efdsp befd)+ else Timeout+ ) -timeoutToC :: Timeout -> (CChar, CLong, CLong)-timeoutToC Never = (0, 0, 0)-timeoutToC (Time s us) = (1, fromIntegral s, fromIntegral us)+-- | A simpler version of 'select' that uses file descriptor lists+-- instead of 'FS.FdSet's. 'Nothing' is returned in case @select@ gives+-- an error, otherwise @'Just' (rfds, wfds, efds)@ is returned, where+-- @rfds@, @wfds@ and @efds@ are lists of ready file descriptors (they+-- may be empty, such as in the case of @select@ timing out).+select' :: [Fd] -> [Fd] -> [Fd] -> Timeout -> IO (Maybe ([Fd], [Fd], [Fd]))+select' rfds wfds efds timeout =+ (+ case timeout of+ Never -> nullForeignPtr+ Time timeval -> mallocInitForeignPtr ((flip poke) timeval)+ ) >>= \timevalp ->+ FS.fromList rfds >>= \rset@(FSI.FdSet rfdsp brfd) ->+ FS.fromList wfds >>= \wset@(FSI.FdSet wfdsp bwfd) ->+ FS.fromList efds >>= \eset@(FSI.FdSet efdsp befd) ->+ withForeignPtr rfdsp $ \rfdsp' -> + withForeignPtr wfdsp $ \wfdsp' -> + withForeignPtr efdsp $ \efdsp' ->+ withForeignPtr timevalp $ \timevalp' -> + c_xselect (1 + maximum [brfd, bwfd, befd]) rfdsp' wfdsp' efdsp' (castPtr timevalp') >>= \ret ->+ if cError ret+ then return Nothing+ else FS.inList rfds rset >>= \rfds' ->+ FS.inList wfds wset >>= \wfds' ->+ FS.inList efds eset >>= \efds' ->+ return $ Just (rfds', wfds', efds') +-- | This simpler version of 'select' takes the same arguments as+-- 'select'', but only returns the return value of @select(2)@ itself+-- (i.e. @-1@ on error, otherwise the number of ready file descriptors+-- in total).+select'' :: [Fd] -> [Fd] -> [Fd] -> Timeout -> IO CInt+select'' rfds wfds efds timeout =+ (+ case timeout of+ Never -> nullForeignPtr+ Time timeval -> mallocInitForeignPtr ((flip poke) timeval)+ ) >>= \timevalp ->+ FS.fromList rfds >>= \rset@(FSI.FdSet rfdsp brfd) ->+ FS.fromList wfds >>= \wset@(FSI.FdSet wfdsp bwfd) ->+ FS.fromList efds >>= \eset@(FSI.FdSet efdsp befd) ->+ withForeignPtr rfdsp $ \rfdsp' -> + withForeignPtr wfdsp $ \wfdsp' -> + withForeignPtr efdsp $ \efdsp' ->+ withForeignPtr timevalp $ \timevalp' -> + c_xselect (1 + maximum [brfd, bwfd, befd]) rfdsp' wfdsp' efdsp' (castPtr timevalp')
+ src/System/Posix/IO/Select/FdSet.hs view
@@ -0,0 +1,100 @@+-- | Interface to @fd_set@. See @select(2)@. +--+-- The type 'I.FdSet' is opaque, but is implemented internally as a+-- pointer to an @fd_set@. All operations on 'I.FdSet's must adhere to+-- the requirements of @FD_CLR@, @FD_ISSET@, @FD_SET@ and @FD_ZERO@+-- (see @select(2)@). This includes requiring /valid/ file descriptors+-- for all operations. Most functions in this module are kept in the+-- 'IO' monad to make it easier to guarantee validity of the file+-- descriptors, but since invalid ones seem to work fine in practice+-- (at least on Linux), the module+-- "System.Posix.IO.Select.FdSet.Unsafe" provides a non-'IO'+-- interface.+--+-- Functions that return an 'I.FdSet', such as 'insert', copy the+-- underlying @fd_set@ in order to be referentially transparent.+--+-- In the documentation that follows, a file descriptor is said to be+-- /in range/ if it is non-negative and strictly smaller than the+-- system-defined @FD_SETSIZE@. Many functions silently ignore file+-- descriptors that are not in range.+module System.Posix.IO.Select.FdSet (I.FdSet(), fromList, insert, insertList, empty, elem,+ remove, removeList, inList, inRange, bound, duplicate) where++import Prelude hiding (elem)+import qualified System.Posix.IO.Select.FdSet.Internal as I+import Foreign+import qualified System.IO.Unsafe as UNSAFE+import System.Posix.Types+import Misc+import Control.Monad++-- | Create an 'FdSet' from a list of file descriptors. File+-- descriptors not in range (see above) are silently ignored.+fromList :: [Fd] -> IO I.FdSet+fromList fds = I.allocate' >>= \ptr ->+ withForeignPtr ptr I.c_fd_zero_wrapper >>+ mapM_ ((flip I.insert') ptr) (filter inRange fds) >>+ return (I.FdSet ptr (maximum (0:fds)))++-- | Insert a file descriptor.+insert :: Fd -> I.FdSet -> IO I.FdSet +insert fd = insertList [fd]++-- | Insert multiple file descriptors. This is more efficient than+-- multiple 'insert's (only a single copy of the set is made).+insertList :: [Fd] -> I.FdSet -> IO I.FdSet+insertList fds (I.FdSet ptr l) = + I.duplicate' ptr >>= \newPtr ->+ mapM_ ((flip I.insert') newPtr) (filter inRange fds) >>+ return (I.FdSet newPtr (max l (maximum (0:fds))))++-- | An empty 'FdSet'.+empty :: IO I.FdSet+empty = I.allocate' >>= \ptr ->+ withForeignPtr ptr I.c_fd_zero_wrapper >>+ return (I.FdSet ptr 0)++-- | Test for membership. Recall that POSIX allows undefined behavior+-- if the file descriptor is not valid (it does, however, seem to work+-- fine on Linux).+elem :: Fd -> I.FdSet -> IO Bool+elem fd (I.FdSet ptr _) = I.elem' fd ptr >>= (return . cTrue)++-- | Remove a file descriptor.+remove :: Fd -> I.FdSet -> IO I.FdSet+remove fd = removeList [fd]++-- | Remove multiple file descriptors. This is more efficient than+-- multiple 'remove's (only a single copy of the set is made).+removeList :: [Fd] -> I.FdSet -> IO I.FdSet+removeList fds (I.FdSet ptr l) = + I.duplicate' ptr >>= \newPtr ->+ mapM_ ((flip I.remove') newPtr) (filter inRange fds) >>+ return (I.FdSet newPtr l) -- We don't actually shrink the maximum fd here. Should be ok!++-- | @'inList' fds fdset@ gives a list of all file descriptors in+-- @fd@ that are in @fdset@.+inList :: [Fd] -> I.FdSet -> IO [Fd]+inList fds (I.FdSet ptr l) = filterM (\fd -> I.elem' fd ptr >>= (return . cTrue)) fds'+ where+ fds' = filter (\fd -> fd <= l && inRange fd) fds++-- | Test if a file descriptor is in range (see introduction).+inRange :: Fd -> Bool+inRange fd = fd' >= 0 && fd' < limit+ where+ fd' = fromIntegral fd+ limit = UNSAFE.unsafePerformIO I.c_fd_setsize_wrapper++-- | This file descriptor is at least as large as the largest in the+-- set. If no file descriptors have ever been removed from the set,+-- the value is /the largest/ in the set, but this assumption may not+-- hold after removals or other operations.+bound :: I.FdSet -> Fd+bound (I.FdSet _ l) = l++-- | Copy an 'FdSet'.+duplicate :: I.FdSet -> IO I.FdSet+duplicate (I.FdSet ptr l) = I.duplicate' ptr >>= \newPtr ->+ return (I.FdSet newPtr l)
+ src/System/Posix/IO/Select/FdSet/Internal.hsc view
@@ -0,0 +1,51 @@+{-# LANGUAGE ForeignFunctionInterface #-}++module System.Posix.IO.Select.FdSet.Internal where++import Foreign+import Foreign.C.Types+import System.Posix.Types+import Misc++-- NOT an instance of storable. We treat fdset as opaque, maintaing only a pointer.+data FdSet = FdSet (ForeignPtr ()) Fd++#include <sys/select.h>+#include "cbits.h"+#let alignment t = "%lu", (unsigned long)offsetof(struct {char x__; t (y__); }, y__)++remove' :: Fd -> ForeignPtr () -> IO ()+remove' fd ptr = withForeignPtr ptr (c_fd_clr_wrapper (fromIntegral fd))+ +insert' :: Fd -> ForeignPtr () -> IO ()+insert' fd ptr = withForeignPtr ptr (c_fd_set_wrapper (fromIntegral fd))++elem' :: Fd -> ForeignPtr () -> IO CInt+elem' fd ptr = withForeignPtr ptr (c_fd_isset_wrapper (fromIntegral fd))++pointer :: FdSet -> ForeignPtr ()+pointer (FdSet p _) = p++allocate' :: IO (ForeignPtr ())+allocate' = mallocForeignPtrBytes #{size fd_set}+-- debugging: putStrLn ("Allocating " ++ show #{size fd_set} ++ ".") >> -- Remove when finished testing.++duplicate' :: ForeignPtr () -> IO (ForeignPtr ())+duplicate' ptr = allocate' >>= \newPtr ->+ withForeignPtr ptr (\ptr' -> withForeignPtr newPtr (\newPtr' -> copyBytes newPtr' ptr' #{size fd_set})) >>+ return newPtr++foreign import ccall "cbits.h fd_zero_wrapper"+ c_fd_zero_wrapper :: Ptr () -> IO ()++foreign import ccall "cbits.h fd_set_wrapper"+ c_fd_set_wrapper :: CInt -> Ptr () -> IO ()++foreign import ccall "cbits.h fd_clr_wrapper"+ c_fd_clr_wrapper :: CInt -> Ptr () -> IO ()++foreign import ccall "cbits.h fd_isset_wrapper"+ c_fd_isset_wrapper :: CInt -> Ptr () -> IO CInt++foreign import ccall "cbits.h fd_setsize_wrapper"+ c_fd_setsize_wrapper :: IO CInt
+ src/System/Posix/IO/Select/FdSet/Unsafe.hs view
@@ -0,0 +1,56 @@+-- | Pure interface to @fd_set@.+--+-- As far as I can tell, the data structure and functions in+-- "System.Posix.IO.Select.FdSet" are referentially transparent, and+-- it should be OK to escape the 'IO' monad. However, POSIX requires+-- that all operations on @fd_set@s are done with /valid/ file+-- descriptors. This can potentially be hard to ensure with lazy+-- evaluation in a pure setting, so for now this module bears the+-- unsafe label. On Linux, invalid file descriptors /seem/ to be just+-- fine.+--+-- See "System.Posix.IO.Select.FdSet" for documentation in+-- general. All functions here essentially just add+-- 'UNSAFE.unsafePerformIO'.+module System.Posix.IO.Select.FdSet.Unsafe (F.FdSet(), fromList, insert, insertList,+ elem, remove, removeList, inList,+ empty, unsafeToList) where++import Prelude hiding (elem)+import System.Posix.Types+import qualified System.Posix.IO.Select.FdSet as F+import qualified System.IO.Unsafe as UNSAFE++fromList :: [Fd] -> F.FdSet+fromList = UNSAFE.unsafePerformIO . F.fromList++empty :: F.FdSet+empty = UNSAFE.unsafePerformIO F.empty++insert :: Fd -> F.FdSet -> F.FdSet+insert fd fdset = UNSAFE.unsafePerformIO (F.insert fd fdset)++insertList :: [Fd] -> F.FdSet -> F.FdSet+insertList fds fdset = UNSAFE.unsafePerformIO (F.insertList fds fdset)++elem :: Fd -> F.FdSet -> Bool+elem fd fdset = UNSAFE.unsafePerformIO (F.elem fd fdset)++remove :: Fd -> F.FdSet -> F.FdSet+remove fd fdset = UNSAFE.unsafePerformIO (F.remove fd fdset)++removeList :: [Fd] -> F.FdSet -> F.FdSet+removeList fds fdset = UNSAFE.unsafePerformIO (F.removeList fds fdset)++inList :: [Fd] -> F.FdSet -> [Fd]+inList fds fdset = UNSAFE.unsafePerformIO (F.inList fds fdset)++-- toList :: F.FdSet -> [Fd]+-- toList = UNSAFE.unsafePerformIO . F.toList++-- | This converts an 'FdSet' to a list by testing every file+-- descriptor in range for membership, which tends to involve invalid+-- file descriptors, giving undefined behavior according to POSIX. Use+-- 'inList' if possible.+unsafeToList :: F.FdSet -> [Fd]+unsafeToList fdset = inList [(fromIntegral 0)..(F.bound fdset)] fdset
+ src/System/Posix/IO/Select/FdSet/cbits.c view
@@ -0,0 +1,27 @@+#include <sys/select.h>+#include "cbits.h"++void fd_clr_wrapper(int fd, fd_set* set)+{+ FD_CLR(fd, set);+}++int fd_isset_wrapper(int fd, fd_set* set)+{+ return FD_ISSET(fd, set);+}++void fd_set_wrapper(int fd, fd_set* set)+{+ FD_SET(fd, set);+}++void fd_zero_wrapper(fd_set* set)+{+ FD_ZERO(set);+}++int fd_setsize_wrapper()+{+ return FD_SETSIZE;+}
+ src/System/Posix/IO/Select/FdSet/cbits.h view
@@ -0,0 +1,9 @@+#pragma once++#include <sys/select.h>++void fd_clr_wrapper(int fd, fd_set* set);+int fd_isset_wrapper(int fd, fd_set* set);+void fd_set_wrapper(int fd, fd_set* set);+void fd_zero_wrapper(fd_set* set);+int fd_setsize_wrapper();
− src/System/Posix/IO/Select/MVar.hs
@@ -1,17 +0,0 @@--- | Treat the POSIX @select(2)@ function as an 'MVar' 'CInt'.--module System.Posix.IO.Select.MVar (select, Timeout(Never, Time)) where--import System.Posix.IO.Select.Types-import qualified System.Posix.IO.Select as S-import Foreign.C.Types-import Control.Concurrent.MVar-import qualified Control.Concurrent.MVarIO as MIO-import System.Posix.Types---- | This version of 'S.select' immediately returns and makes the--- return value of the @select(2)@ call available as an 'MVar'--- 'CInt'. See 'S.select' for argument information.-select :: [Fd] -> [Fd] -> [Fd] -> Timeout -> IO (MVar CInt)-select readFds writeFds exceptFds timeout = MIO.run (S.select readFds writeFds exceptFds timeout)-
− src/System/Posix/IO/Select/STM.hs
@@ -1,51 +0,0 @@--- | Treat the POSIX @select(2)@ function as a 'TMVar' 'CInt'.--module System.Posix.IO.Select.STM (select, selectOrTakeTMVar, selectOrElse, selectOrElse', selectOrReadTChan, Timeout(Never, Time)) where--import System.Posix.IO.Select.Types-import qualified System.Posix.IO.Select as S-import Foreign.C.Types-import Control.Concurrent.STM-import qualified Control.Concurrent.STM.RunOrElse as ROE-import qualified Control.Concurrent.STM.TMVarIO as TIO-import System.Posix.Types---- | This version of 'S.select' immediately returns and makes the--- return value of the @select(2)@ call available as a 'TMVar'--- 'CInt'. See 'S.select' for argument information.-select :: [Fd] -> [Fd] -> [Fd] -> Timeout -> IO (TMVar CInt)-select readFds writeFds exceptFds timeout = TIO.run (S.select readFds writeFds exceptFds timeout)---- | The parameters are the same as for 'select', except for the--- addition of a 'TMVar'. The function returns as soon as either the--- @select(2)@ has completed, or the 'TMVar' is full. ------ The return value is either the return value of @select(2)@, or the--- content of the 'TMVar'. If the 'TMVar' becomes available first,--- then the @select(2)@ call may hang around forever or until it times--- out (as specified by the 'Timeout' parameter). If the @select(2)@--- returns before the 'TMVar' is available, the 'TMVar' is guaranteed--- to be left in place.------ See also 'selectOrReadTChan' and 'selectOrElse'. Note that--- 'selectOrTakeTMVar' and the former are special cases of the latter.------ (Incidentally, 'selectOrTakeTMVar' is the task I really wanted to--- accomplish, and solving it just turned into this little library).-selectOrTakeTMVar :: [Fd] -> [Fd] -> [Fd] -> Timeout -> TMVar a -> IO (Either CInt a)-selectOrTakeTMVar readFds writeFds exceptFds timeout mv = selectOrElse readFds writeFds exceptFds timeout (takeTMVar mv)---- | Analogous to 'selectOrTakeTMVar', except with a general STM--- action in place of taking a 'TMVar'.-selectOrElse :: [Fd] -> [Fd] -> [Fd] -> Timeout -> STM a -> IO (Either CInt a)-selectOrElse readFds writeFds exceptFds timeout stm = ROE.runOrElse (S.select readFds writeFds exceptFds timeout) stm---- | A version of 'selectOrElse' that prefers the STM operation to--- 'select' when both are available.-selectOrElse' :: STM a -> [Fd] -> [Fd] -> [Fd] -> Timeout -> IO (Either a CInt)-selectOrElse' stm readFds writeFds exceptFds timeout = ROE.runOrElse' stm (S.select readFds writeFds exceptFds timeout)---- | Special case of 'selectOrElse' where the STM action is reading a--- 'TChan'.-selectOrReadTChan :: [Fd] -> [Fd] -> [Fd] -> Timeout -> TChan a -> IO (Either CInt a)-selectOrReadTChan readFds writeFds exceptFds timeout tc = selectOrElse readFds writeFds exceptFds timeout (readTChan tc)
− src/System/Posix/IO/Select/Types.hs
@@ -1,10 +0,0 @@--- | Some types used by the other modules in this package.-module System.Posix.IO.Select.Types where--type Seconds = Int-type Microseconds = Int---- | A timeout of @'Never'@ tells @select(2)@ to never time out, while--- @'Time' s us@ sets the timeout parameters to @s@ seconds and @us@--- microseconds.-data Timeout = Never | Time Seconds Microseconds
+ src/System/Posix/IO/Select/Types.hsc view
@@ -0,0 +1,65 @@+{-# LANGUAGE ForeignFunctionInterface #-}++-- | Various types.+module System.Posix.IO.Select.Types where++import Foreign.C.Types+import Foreign.Storable+import qualified System.Posix.IO.Select.FdSet.Unsafe as FSUNSAFE+import qualified System.Posix.IO.Select.FdSet as FS++-- | A timeout of @'Never'@ tells @select(2)@ to never time out, while+-- @'Time'@ sets a finite timeout.+data Timeout = Never | Time CTimeval++-- | A 'Storable' instance for @struct timeval@ (see @select(2)@). The+-- first argument corresponds to @tv_sec@, the second to @tv_usec@.+data CTimeval = CTimeval CLong CLong++-- | @'finite' s us@ tells @select@ to time out after @s@ seconds and+-- @us@ microseconds.+finite :: CLong -> CLong -> Timeout+finite s us = Time (CTimeval s us)++#include <sys/time.h>+#let alignment t = "%lu", (unsigned long)offsetof(struct {char x__; t (y__); }, y__)+instance Storable CTimeval where+ sizeOf _ = #{size struct timeval}+ alignment _ = #{alignment struct timeval}+ peek ptr = #{peek struct timeval, tv_sec} ptr >>= \s ->+ #{peek struct timeval, tv_usec} ptr >>= \us ->+ return (CTimeval s us)+ poke ptr (CTimeval s us) = #{poke struct timeval, tv_sec} ptr s >>+ #{poke struct timeval, tv_usec} ptr us++-- | Abstraction for the return value of @select@. @'Error'@ means+-- that @select@ returned an error, while 'Timeout' means it+-- timed out. @'Ready' n rfds wfds efds@ specifies that it returned+-- because the file descriptors in @rfds@, @wfds@ and @efds@ are ready+-- in their respective ways (see @select(2)@), with @n@ descriptors in+-- total.+data Result = Error | Timeout | Ready CInt FS.FdSet FS.FdSet FS.FdSet++-- | The total number of ready file descriptors across all three sets.+numReady :: Result -> CInt+numReady Error = 0+numReady Timeout = 0+numReady (Ready n _ _ _) = n++-- | The file descriptors ready for reading.+readyRead :: Result -> FS.FdSet+readyRead Error = FSUNSAFE.empty+readyRead Timeout = FSUNSAFE.empty+readyRead (Ready _ rfds _ _) = rfds++-- | The file descriptors ready for writing.+readyWrite :: Result -> FS.FdSet+readyWrite Error = FSUNSAFE.empty+readyWrite Timeout = FSUNSAFE.empty+readyWrite (Ready _ _ wfds _) = wfds++-- | The file descriptors having exceptional conditions.+readyException :: Result -> FS.FdSet+readyException Error = FSUNSAFE.empty+readyException Timeout = FSUNSAFE.empty+readyException (Ready _ _ _ efds) = efds
+ src/System/Posix/IO/Select/cbits.c view
@@ -0,0 +1,94 @@+#define _POSIX_C_SOURCE 200112L+#include <stdlib.h>+#include <sys/select.h>+#include <time.h>+#include <sys/time.h>+#include <errno.h>+#include "cbits.h"+#ifndef NDEBUG+#include <stdio.h>+#endif+++int xselect(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout)+{+ struct timespec remaining;+ int ret;+ struct timespec start;+ struct timespec current;+ struct timespec stop;+ if (timeout != NULL)+ {+ remaining.tv_sec = timeout->tv_sec;+ remaining.tv_nsec = timeout->tv_usec*1000;+ clock_gettime(CLOCK_MONOTONIC, &start);+ stop.tv_sec = start.tv_sec + timeout->tv_sec;+ stop.tv_nsec = start.tv_nsec + timeout->tv_usec*1000;+ }+ fd_set readfds_orig = *readfds;+ fd_set writefds_orig = *writefds;+ fd_set exceptfds_orig = *exceptfds;++ for (;;)+ {+ #ifndef NDEBUG+ if (timeout != NULL)+ {+ printf("C: Remaining time: %ld s, %ld ns\n", remaining.tv_sec, remaining.tv_nsec);+ printf("C: Will select for %ld s, %ld us\n", timeout->tv_sec, timeout->tv_usec);+ }+ else+ printf("C: Will select forever.\n");+ #endif++ ret = select(nfds, readfds, writefds, exceptfds, timeout);++ if (ret == -1 && errno == EINTR)+ {+ #ifndef NDEBUG+ printf("C: Was interrupted.\n");+ #endif+ if (timeout != NULL) // Case of finite timeouts.+ {+ clock_gettime(CLOCK_MONOTONIC, ¤t);+ remaining.tv_sec = stop.tv_sec - current.tv_sec;+ remaining.tv_nsec = stop.tv_nsec - current.tv_nsec;++ while (remaining.tv_nsec > 1000000000)+ {+ remaining.tv_sec++;+ remaining.tv_nsec -= 1000000000;+ }+ while (remaining.tv_nsec < 0)+ {+ remaining.tv_sec--;+ remaining.tv_nsec += 1000000000;+ }+ if (remaining.tv_sec < 0)+ {+ remaining.tv_sec = 0;+ remaining.tv_nsec = 0;+ }++ #ifndef NDEBUG+ printf("C: Was interrupted with %ld s, %ld ns remaining\n", remaining.tv_sec, remaining.tv_nsec);+ #endif++ // Set up for retry.+ timeout->tv_sec = remaining.tv_sec;+ timeout->tv_usec = remaining.tv_nsec/1000;+ }+ // Set up for retry (also in infinite case).+ *readfds = readfds_orig;+ *writefds = writefds_orig;+ *exceptfds = exceptfds_orig;+ #ifndef NDEBUG+ printf("C: Retrying select.\n");+ #endif+ }+ else+ return ret;+ }++ return -2; // Cannot happen.+}
+ src/System/Posix/IO/Select/cbits.h view
@@ -0,0 +1,7 @@+#pragma once++#include <sys/select.h>+#include <sys/time.h>++void subtract(struct timespec* result, const struct timespec* x, const struct timespec* y);+int xselect(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
− src/System/Posix/IO/Select/select_wrapper.c
@@ -1,73 +0,0 @@-#include <unistd.h>-#include <sys/time.h>-#include <sys/select.h>-#ifndef NDEBUG-#include <stdio.h>-#include <errno.h>-#include <string.h>-#endif--#include "select_wrapper.h"--int select_wrapper(const int* readfds, int num_readfds,- const int* writefds, int num_writefds,- const int* exceptfds, int num_exceptfds,- char timeout, long secs, long usecs)-{- struct timeval tv;- tv.tv_sec = secs;- tv.tv_usec = usecs;-- int nfds = -1;-- fd_set rfds;- FD_ZERO(&rfds);- for (int i = 0; i < num_readfds; i++)- {- #ifndef NDEBUG- printf("select_wrapper.c: Registering read FD %d.\n", readfds[i]);- #endif- FD_SET(readfds[i], &rfds);- if (readfds[i] > nfds)- nfds = readfds[i];- }-- fd_set wfds;- FD_ZERO(&wfds);- for (int i = 0; i < num_writefds; i++)- {- #ifndef NDEBUG- printf("select_wrapper.c: Registering write FD %d.\n", writefds[i]);- #endif- FD_SET(writefds[i], &wfds);- if (writefds[i] > nfds)- nfds = writefds[i];- }-- fd_set efds;- FD_ZERO(&efds);- for (int i = 0; i < num_exceptfds; i++)- {- #ifndef NDEBUG- printf("select_wrapper.c: Registering exception FD %d.\n", exceptfds[i]);- #endif- FD_SET(exceptfds[i], &efds);- if (exceptfds[i] > nfds)- nfds = exceptfds[i];- } -- nfds++;- int ret = -1;- if (timeout)- ret = select(nfds, &rfds, &wfds, &efds, &tv);- else- ret = select(nfds, &rfds, &wfds, &efds, NULL);-- #ifndef NDEBUG- printf("select_wrapper.c: Returning %d.\n", ret);- if (ret == -1)- printf("select_wrapper.c: Error code %d: %s\n", errno, strerror(errno));- #endif-- return ret;-}
− src/System/Posix/IO/Select/select_wrapper.h
@@ -1,7 +0,0 @@-#pragma once--int select_wrapper(const int* readfds, int num_readfds,- const int* writefds, int num_writefds,- const int* exceptfds, int num_exceptfds,- char timeout, long secs, long usecs);-