Integrated new I/O manager
[ghc-base.git] / System / Event / EPoll.hsc
diff --git a/System/Event/EPoll.hsc b/System/Event/EPoll.hsc
new file mode 100644 (file)
index 0000000..098946e
--- /dev/null
@@ -0,0 +1,213 @@
+{-# LANGUAGE ForeignFunctionInterface, GeneralizedNewtypeDeriving,
+    NoImplicitPrelude #-}
+
+--
+-- | A binding to the epoll I/O event notification facility
+--
+-- epoll is a variant of poll that can be used either as an edge-triggered or
+-- a level-triggered interface and scales well to large numbers of watched file
+-- descriptors.
+--
+-- epoll decouples monitor an fd from the process of registering it.
+--
+module System.Event.EPoll
+    (
+      new
+    , available
+    ) where
+
+import qualified System.Event.Internal as E
+
+#include "EventConfig.h"
+#if !defined(HAVE_EPOLL)
+import GHC.Base
+
+new :: IO E.Backend
+new = error "EPoll back end not implemented for this platform"
+
+available :: Bool
+available = False
+{-# INLINE available #-}
+#else
+
+#include <sys/epoll.h>
+
+import Control.Monad (when)
+import Data.Bits (Bits, (.|.), (.&.))
+import Data.Monoid (Monoid(..))
+import Data.Word (Word32)
+import Foreign.C.Error (throwErrnoIfMinus1, throwErrnoIfMinus1_)
+import Foreign.C.Types (CInt)
+import Foreign.Marshal.Utils (with)
+import Foreign.Ptr (Ptr)
+import Foreign.Storable (Storable(..))
+import GHC.Base
+import GHC.Err (undefined)
+import GHC.Num (Num(..))
+import GHC.Real (ceiling, fromIntegral)
+import GHC.Show (Show)
+import System.Posix.Internals (c_close)
+#if !defined(HAVE_EPOLL_CREATE1)
+import System.Posix.Internals (setCloseOnExec)
+#endif
+import System.Posix.Types (Fd(..))
+
+import qualified System.Event.Array    as A
+import           System.Event.Internal (Timeout(..))
+
+available :: Bool
+available = True
+{-# INLINE available #-}
+
+data EPoll = EPoll {
+      epollFd     :: {-# UNPACK #-} !EPollFd
+    , epollEvents :: {-# UNPACK #-} !(A.Array Event)
+    }
+
+-- | Create a new epoll backend.
+new :: IO E.Backend
+new = do
+  epfd <- epollCreate
+  evts <- A.new 64
+  let !be = E.backend poll modifyFd delete (EPoll epfd evts)
+  return be
+
+delete :: EPoll -> IO ()
+delete be = do
+  _ <- c_close . fromEPollFd . epollFd $ be
+  return ()
+
+-- | Change the set of events we are interested in for a given file
+-- descriptor.
+modifyFd :: EPoll -> Fd -> E.Event -> E.Event -> IO ()
+modifyFd ep fd oevt nevt = with (Event (fromEvent nevt) fd) $
+                             epollControl (epollFd ep) op fd
+  where op | oevt == mempty = controlOpAdd
+           | nevt == mempty = controlOpDelete
+           | otherwise      = controlOpModify
+
+-- | Select a set of file descriptors which are ready for I/O
+-- operations and call @f@ for all ready file descriptors, passing the
+-- events that are ready.
+poll :: EPoll                     -- ^ state
+     -> Timeout                   -- ^ timeout in milliseconds
+     -> (Fd -> E.Event -> IO ())  -- ^ I/O callback
+     -> IO ()
+poll ep timeout f = do
+  let events = epollEvents ep
+
+  -- Will return zero if the system call was interupted, in which case
+  -- we just return (and try again later.)
+  n <- A.unsafeLoad events $ \es cap ->
+       epollWait (epollFd ep) es cap $ fromTimeout timeout
+
+  when (n > 0) $ do
+    A.forM_ events $ \e -> f (eventFd e) (toEvent (eventTypes e))
+    cap <- A.capacity events
+    when (cap == n) $ A.ensureCapacity events (2 * cap)
+
+newtype EPollFd = EPollFd {
+      fromEPollFd :: CInt
+    } deriving (Eq, Show)
+
+data Event = Event {
+      eventTypes :: EventType
+    , eventFd    :: Fd
+    } deriving (Show)
+
+instance Storable Event where
+    sizeOf    _ = #size struct epoll_event
+    alignment _ = alignment (undefined :: CInt)
+
+    peek ptr = do
+        ets <- #{peek struct epoll_event, events} ptr
+        ed  <- #{peek struct epoll_event, data.fd}   ptr
+        let !ev = Event (EventType ets) ed
+        return ev
+
+    poke ptr e = do
+        #{poke struct epoll_event, events} ptr (unEventType $ eventTypes e)
+        #{poke struct epoll_event, data.fd}   ptr (eventFd e)
+
+newtype ControlOp = ControlOp CInt
+
+#{enum ControlOp, ControlOp
+ , controlOpAdd    = EPOLL_CTL_ADD
+ , controlOpModify = EPOLL_CTL_MOD
+ , controlOpDelete = EPOLL_CTL_DEL
+ }
+
+newtype EventType = EventType {
+      unEventType :: Word32
+    } deriving (Show, Eq, Num, Bits)
+
+#{enum EventType, EventType
+ , epollIn  = EPOLLIN
+ , epollOut = EPOLLOUT
+ , epollErr = EPOLLERR
+ , epollHup = EPOLLHUP
+ }
+
+-- | Create a new epoll context, returning a file descriptor associated with the context.
+-- The fd may be used for subsequent calls to this epoll context.
+--
+-- The size parameter to epoll_create is a hint about the expected number of handles.
+--
+-- The file descriptor returned from epoll_create() should be destroyed via
+-- a call to close() after polling is finished
+--
+epollCreate :: IO EPollFd
+epollCreate = do
+  fd <- throwErrnoIfMinus1 "epollCreate" $
+#if defined(HAVE_EPOLL_CREATE1)
+        c_epoll_create1 (#const EPOLL_CLOEXEC)
+#else
+        c_epoll_create 256 -- argument is ignored
+  setCloseOnExec fd
+#endif
+  let !epollFd' = EPollFd fd
+  return epollFd'
+
+epollControl :: EPollFd -> ControlOp -> Fd -> Ptr Event -> IO ()
+epollControl (EPollFd epfd) (ControlOp op) (Fd fd) event =
+    throwErrnoIfMinus1_ "epollControl" $ c_epoll_ctl epfd op fd event
+
+epollWait :: EPollFd -> Ptr Event -> Int -> Int -> IO Int
+epollWait (EPollFd epfd) events numEvents timeout =
+    fmap fromIntegral .
+    E.throwErrnoIfMinus1NoRetry "epollWait" $
+    c_epoll_wait epfd events (fromIntegral numEvents) (fromIntegral timeout)
+
+fromEvent :: E.Event -> EventType
+fromEvent e = remap E.evtRead  epollIn .|.
+              remap E.evtWrite epollOut
+  where remap evt to
+            | e `E.eventIs` evt = to
+            | otherwise         = 0
+
+toEvent :: EventType -> E.Event
+toEvent e = remap (epollIn  .|. epollErr .|. epollHup) E.evtRead `mappend`
+            remap (epollOut .|. epollErr .|. epollHup) E.evtWrite
+  where remap evt to
+            | e .&. evt /= 0 = to
+            | otherwise      = mempty
+
+fromTimeout :: Timeout -> Int
+fromTimeout Forever     = -1
+fromTimeout (Timeout s) = ceiling $ 1000 * s
+
+#if defined(HAVE_EPOLL_CREATE1)
+foreign import ccall unsafe "sys/epoll.h epoll_create1"
+    c_epoll_create1 :: CInt -> IO CInt
+#else
+foreign import ccall unsafe "sys/epoll.h epoll_create"
+    c_epoll_create :: CInt -> IO CInt
+#endif
+
+foreign import ccall unsafe "sys/epoll.h epoll_ctl"
+    c_epoll_ctl :: CInt -> CInt -> CInt -> Ptr Event -> IO CInt
+
+foreign import ccall safe "sys/epoll.h epoll_wait"
+    c_epoll_wait :: CInt -> Ptr Event -> CInt -> CInt -> IO CInt
+
+#endif /* defined(HAVE_EPOLL) */