Rename System.Event to GHC.Event
[ghc-base.git] / GHC / Event / KQueue.hsc
diff --git a/GHC/Event/KQueue.hsc b/GHC/Event/KQueue.hsc
new file mode 100644 (file)
index 0000000..19c7adf
--- /dev/null
@@ -0,0 +1,303 @@
+{-# LANGUAGE CPP
+           , ForeignFunctionInterface
+           , GeneralizedNewtypeDeriving
+           , NoImplicitPrelude
+           , RecordWildCards
+           , BangPatterns
+  #-}
+
+module GHC.Event.KQueue
+    (
+      new
+    , available
+    ) where
+
+import qualified GHC.Event.Internal as E
+
+#include "EventConfig.h"
+#if !defined(HAVE_KQUEUE)
+import GHC.Base
+
+new :: IO E.Backend
+new = error "KQueue back end not implemented for this platform"
+
+available :: Bool
+available = False
+{-# INLINE available #-}
+#else
+
+import Control.Concurrent.MVar (MVar, newMVar, swapMVar, withMVar)
+import Control.Monad (when, unless)
+import Data.Bits (Bits(..))
+import Data.Word (Word16, Word32)
+import Foreign.C.Error (throwErrnoIfMinus1)
+import Foreign.C.Types (CInt, CLong, CTime)
+import Foreign.Marshal.Alloc (alloca)
+import Foreign.Ptr (Ptr, nullPtr)
+import Foreign.Storable (Storable(..))
+import GHC.Base
+import GHC.Enum (toEnum)
+import GHC.Err (undefined)
+import GHC.Num (Num(..))
+import GHC.Real (ceiling, floor, fromIntegral)
+import GHC.Show (Show(show))
+import GHC.Event.Internal (Timeout(..))
+import System.Posix.Internals (c_close)
+import System.Posix.Types (Fd(..))
+import qualified GHC.Event.Array as A
+
+#if defined(HAVE_KEVENT64)
+import Data.Int (Int64)
+import Data.Word (Word64)
+import Foreign.C.Types (CUInt)
+#else
+import Foreign.C.Types (CIntPtr, CUIntPtr)
+#endif
+
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/time.h>
+
+-- Handle brokenness on some BSD variants, notably OS X up to at least
+-- 10.6.  If NOTE_EOF isn't available, we have no way to receive a
+-- notification from the kernel when we reach EOF on a plain file.
+#ifndef NOTE_EOF
+# define NOTE_EOF 0
+#endif
+
+available :: Bool
+available = True
+{-# INLINE available #-}
+
+------------------------------------------------------------------------
+-- Exported interface
+
+data EventQueue = EventQueue {
+      eqFd       :: {-# UNPACK #-} !QueueFd
+    , eqChanges  :: {-# UNPACK #-} !(MVar (A.Array Event))
+    , eqEvents   :: {-# UNPACK #-} !(A.Array Event)
+    }
+
+new :: IO E.Backend
+new = do
+  qfd <- kqueue
+  changesArr <- A.empty
+  changes <- newMVar changesArr 
+  events <- A.new 64
+  let !be = E.backend poll modifyFd delete (EventQueue qfd changes events)
+  return be
+
+delete :: EventQueue -> IO ()
+delete q = do
+  _ <- c_close . fromQueueFd . eqFd $ q
+  return ()
+
+modifyFd :: EventQueue -> Fd -> E.Event -> E.Event -> IO ()
+modifyFd q fd oevt nevt = withMVar (eqChanges q) $ \ch -> do
+  let addChange filt flag = A.snoc ch $ event fd filt flag noteEOF
+  when (oevt `E.eventIs` E.evtRead)  $ addChange filterRead flagDelete
+  when (oevt `E.eventIs` E.evtWrite) $ addChange filterWrite flagDelete
+  when (nevt `E.eventIs` E.evtRead)  $ addChange filterRead flagAdd
+  when (nevt `E.eventIs` E.evtWrite) $ addChange filterWrite flagAdd
+
+poll :: EventQueue
+     -> Timeout
+     -> (Fd -> E.Event -> IO ())
+     -> IO ()
+poll EventQueue{..} tout f = do
+    changesArr <- A.empty
+    changes <- swapMVar eqChanges changesArr
+    changesLen <- A.length changes
+    len <- A.length eqEvents
+    when (changesLen > len) $ A.ensureCapacity eqEvents (2 * changesLen)
+    n <- A.useAsPtr changes $ \changesPtr chLen ->
+           A.unsafeLoad eqEvents $ \evPtr evCap ->
+             withTimeSpec (fromTimeout tout) $
+               kevent eqFd changesPtr chLen evPtr evCap
+
+    unless (n == 0) $ do
+        cap <- A.capacity eqEvents
+        when (n == cap) $ A.ensureCapacity eqEvents (2 * cap)
+        A.forM_ eqEvents $ \e -> f (fromIntegral (ident e)) (toEvent (filter e))
+
+------------------------------------------------------------------------
+-- FFI binding
+
+newtype QueueFd = QueueFd {
+      fromQueueFd :: CInt
+    } deriving (Eq, Show)
+
+#if defined(HAVE_KEVENT64)
+data Event = KEvent64 {
+      ident  :: {-# UNPACK #-} !Word64
+    , filter :: {-# UNPACK #-} !Filter
+    , flags  :: {-# UNPACK #-} !Flag
+    , fflags :: {-# UNPACK #-} !FFlag
+    , data_  :: {-# UNPACK #-} !Int64
+    , udata  :: {-# UNPACK #-} !Word64
+    , ext0   :: {-# UNPACK #-} !Word64
+    , ext1   :: {-# UNPACK #-} !Word64
+    } deriving Show
+
+event :: Fd -> Filter -> Flag -> FFlag -> Event
+event fd filt flag fflag = KEvent64 (fromIntegral fd) filt flag fflag 0 0 0 0
+
+instance Storable Event where
+    sizeOf _ = #size struct kevent64_s
+    alignment _ = alignment (undefined :: CInt)
+
+    peek ptr = do
+        ident'  <- #{peek struct kevent64_s, ident} ptr
+        filter' <- #{peek struct kevent64_s, filter} ptr
+        flags'  <- #{peek struct kevent64_s, flags} ptr
+        fflags' <- #{peek struct kevent64_s, fflags} ptr
+        data'   <- #{peek struct kevent64_s, data} ptr
+        udata'  <- #{peek struct kevent64_s, udata} ptr
+        ext0'   <- #{peek struct kevent64_s, ext[0]} ptr
+        ext1'   <- #{peek struct kevent64_s, ext[1]} ptr
+        let !ev = KEvent64 ident' (Filter filter') (Flag flags') fflags' data'
+                           udata' ext0' ext1'
+        return ev
+
+    poke ptr ev = do
+        #{poke struct kevent64_s, ident} ptr (ident ev)
+        #{poke struct kevent64_s, filter} ptr (filter ev)
+        #{poke struct kevent64_s, flags} ptr (flags ev)
+        #{poke struct kevent64_s, fflags} ptr (fflags ev)
+        #{poke struct kevent64_s, data} ptr (data_ ev)
+        #{poke struct kevent64_s, udata} ptr (udata ev)
+        #{poke struct kevent64_s, ext[0]} ptr (ext0 ev)
+        #{poke struct kevent64_s, ext[1]} ptr (ext1 ev)
+#else
+data Event = KEvent {
+      ident  :: {-# UNPACK #-} !CUIntPtr
+    , filter :: {-# UNPACK #-} !Filter
+    , flags  :: {-# UNPACK #-} !Flag
+    , fflags :: {-# UNPACK #-} !FFlag
+    , data_  :: {-# UNPACK #-} !CIntPtr
+    , udata  :: {-# UNPACK #-} !(Ptr ())
+    } deriving Show
+
+event :: Fd -> Filter -> Flag -> FFlag -> Event
+event fd filt flag fflag = KEvent (fromIntegral fd) filt flag fflag 0 nullPtr
+
+instance Storable Event where
+    sizeOf _ = #size struct kevent
+    alignment _ = alignment (undefined :: CInt)
+
+    peek ptr = do
+        ident'  <- #{peek struct kevent, ident} ptr
+        filter' <- #{peek struct kevent, filter} ptr
+        flags'  <- #{peek struct kevent, flags} ptr
+        fflags' <- #{peek struct kevent, fflags} ptr
+        data'   <- #{peek struct kevent, data} ptr
+        udata'  <- #{peek struct kevent, udata} ptr
+        let !ev = KEvent ident' (Filter filter') (Flag flags') fflags' data'
+                         udata'
+        return ev
+
+    poke ptr ev = do
+        #{poke struct kevent, ident} ptr (ident ev)
+        #{poke struct kevent, filter} ptr (filter ev)
+        #{poke struct kevent, flags} ptr (flags ev)
+        #{poke struct kevent, fflags} ptr (fflags ev)
+        #{poke struct kevent, data} ptr (data_ ev)
+        #{poke struct kevent, udata} ptr (udata ev)
+#endif
+
+newtype FFlag = FFlag Word32
+    deriving (Eq, Show, Storable)
+
+#{enum FFlag, FFlag
+ , noteEOF = NOTE_EOF
+ }
+
+newtype Flag = Flag Word16
+    deriving (Eq, Show, Storable)
+
+#{enum Flag, Flag
+ , flagAdd     = EV_ADD
+ , flagDelete  = EV_DELETE
+ }
+
+newtype Filter = Filter Word16
+    deriving (Bits, Eq, Num, Show, Storable)
+
+#{enum Filter, Filter
+ , filterRead   = EVFILT_READ
+ , filterWrite  = EVFILT_WRITE
+ }
+
+data TimeSpec = TimeSpec {
+      tv_sec  :: {-# UNPACK #-} !CTime
+    , tv_nsec :: {-# UNPACK #-} !CLong
+    }
+
+instance Storable TimeSpec where
+    sizeOf _ = #size struct timespec
+    alignment _ = alignment (undefined :: CInt)
+
+    peek ptr = do
+        tv_sec'  <- #{peek struct timespec, tv_sec} ptr
+        tv_nsec' <- #{peek struct timespec, tv_nsec} ptr
+        let !ts = TimeSpec tv_sec' tv_nsec'
+        return ts
+
+    poke ptr ts = do
+        #{poke struct timespec, tv_sec} ptr (tv_sec ts)
+        #{poke struct timespec, tv_nsec} ptr (tv_nsec ts)
+
+kqueue :: IO QueueFd
+kqueue = QueueFd `fmap` throwErrnoIfMinus1 "kqueue" c_kqueue
+
+-- TODO: We cannot retry on EINTR as the timeout would be wrong.
+-- Perhaps we should just return without calling any callbacks.
+kevent :: QueueFd -> Ptr Event -> Int -> Ptr Event -> Int -> Ptr TimeSpec
+       -> IO Int
+kevent k chs chlen evs evlen ts
+    = fmap fromIntegral $ E.throwErrnoIfMinus1NoRetry "kevent" $
+#if defined(HAVE_KEVENT64)
+      c_kevent64 k chs (fromIntegral chlen) evs (fromIntegral evlen) 0 ts
+#else
+      c_kevent k chs (fromIntegral chlen) evs (fromIntegral evlen) ts
+#endif
+
+withTimeSpec :: TimeSpec -> (Ptr TimeSpec -> IO a) -> IO a
+withTimeSpec ts f =
+    if tv_sec ts < 0 then
+        f nullPtr
+      else
+        alloca $ \ptr -> poke ptr ts >> f ptr
+
+fromTimeout :: Timeout -> TimeSpec
+fromTimeout Forever     = TimeSpec (-1) (-1)
+fromTimeout (Timeout s) = TimeSpec (toEnum sec) (toEnum nanosec)
+  where
+    sec :: Int
+    sec     = floor s
+
+    nanosec :: Int
+    nanosec = ceiling $ (s - fromIntegral sec) * 1000000000
+
+toEvent :: Filter -> E.Event
+toEvent (Filter f)
+    | f == (#const EVFILT_READ) = E.evtRead
+    | f == (#const EVFILT_WRITE) = E.evtWrite
+    | otherwise = error $ "toEvent: unknown filter " ++ show f
+
+foreign import ccall unsafe "kqueue"
+    c_kqueue :: IO CInt
+
+#if defined(HAVE_KEVENT64)
+foreign import ccall safe "kevent64"
+    c_kevent64 :: QueueFd -> Ptr Event -> CInt -> Ptr Event -> CInt -> CUInt
+               -> Ptr TimeSpec -> IO CInt
+#elif defined(HAVE_KEVENT)
+foreign import ccall safe "kevent"
+    c_kevent :: QueueFd -> Ptr Event -> CInt -> Ptr Event -> CInt
+             -> Ptr TimeSpec -> IO CInt
+#else
+#error no kevent system call available!?
+#endif
+
+#endif /* defined(HAVE_KQUEUE) */