{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE MultiWayIf #-}
module Std.IO.UV.Manager
( UVManager
, getUVManager
, getBlockMVar
, peekBufferTable
, pokeBufferTable
, withUVManager
, withUVManager_
, getUVSlot
, withUVRequest
, withUVRequest_
, withUVRequest'
, withUVRequestEx
, initUVStream
, UVStream(..)
, forkBa
) where
import Control.Concurrent
import Control.Concurrent.MVar
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Primitive (touch)
import Data.IORef
import Data.Bits (shiftL)
import Data.Primitive.PrimArray
import Data.Word
import Foreign.C
import Foreign.Ptr
import Foreign.Storable
import GHC.Conc.Sync (labelThread)
import Std.Data.Array
import Std.Data.PrimIORef
import Std.IO.Buffered
import Std.IO.Exception
import Std.IO.UV.Errno
import Std.IO.Resource
import Std.IO.UV.FFI
import System.IO.Unsafe
#define IDLE_LIMIT 20
data UVManager = UVManager
{ uvmBlockTable :: {-# UNPACK #-} !(IORef (UnliftedArray (MVar Int)))
, uvmLoop :: {-# UNPACK #-} !(Ptr UVLoop)
, uvmLoopData :: {-# UNPACK #-} !(Ptr UVLoopData)
, uvmRunning :: {-# UNPACK #-} !(MVar Bool)
, uvmCap :: {-# UNPACK #-} !Int
}
instance Show UVManager where
show uvm = "UVManager on capability " ++ show (uvmCap uvm)
instance Eq UVManager where
uvm == uvm' =
uvmCap uvm == uvmCap uvm'
uvManagerArray :: IORef (Array UVManager)
{-# NOINLINE uvManagerArray #-}
uvManagerArray = unsafePerformIO $ do
numCaps <- getNumCapabilities
uvmArray <- newArr numCaps
s <- newQSemN 0
forM_ [0..numCaps-1] $ \ i -> do
forkOn i . withResource (initUVManager INIT_LOOP_SIZE i) $ \ m -> do
myThreadId >>= (`labelThread` ("uv manager on " ++ show i))
writeArr uvmArray i m
signalQSemN s 1
startUVManager m
waitQSemN s numCaps
iuvmArray <- unsafeFreezeArr uvmArray
newIORef iuvmArray
getUVManager :: IO UVManager
{-# INLINABLE getUVManager #-}
getUVManager = do
(cap, _) <- threadCapability =<< myThreadId
uvmArray <- readIORef uvManagerArray
indexArrM uvmArray (cap `rem` sizeofArr uvmArray)
getBlockMVar :: UVManager -> UVSlot -> IO (MVar Int)
{-# INLINABLE getBlockMVar #-}
getBlockMVar uvm slot = do
blockTable <- readIORef (uvmBlockTable uvm)
indexArrM blockTable slot
pokeBufferTable :: UVManager -> UVSlot -> Ptr Word8 -> Int -> IO ()
{-# INLINABLE pokeBufferTable #-}
pokeBufferTable uvm slot buf bufSiz = do
(bufTable, bufSizTable) <- peekUVBufferTable (uvmLoopData uvm)
pokeElemOff bufTable slot buf
pokeElemOff bufSizTable slot (fromIntegral bufSiz)
peekBufferTable :: UVManager -> UVSlot -> IO Int
{-# INLINABLE peekBufferTable #-}
peekBufferTable uvm slot = do
(bufTable, bufSizTable) <- peekUVBufferTable (uvmLoopData uvm)
fromIntegral <$> peekElemOff bufSizTable slot
initUVManager :: HasCallStack => Int -> Int -> Resource UVManager
initUVManager siz cap = do
loop <- initUVLoop (fromIntegral siz)
liftIO $ do
mblockTable <- newArr siz
forM_ [0..siz-1] $ \ i -> writeArr mblockTable i =<< newEmptyMVar
blockTable <- unsafeFreezeArr mblockTable
blockTableRef <- newIORef blockTable
loopData <- peekUVLoopData loop
running <- newMVar False
return (UVManager blockTableRef loop loopData running cap)
where
initUVLoop :: HasCallStack => Int -> Resource (Ptr UVLoop)
initUVLoop siz = initResource
(throwOOMIfNull $ hs_uv_loop_init siz
) hs_uv_loop_close
withUVManager :: HasCallStack => UVManager -> (Ptr UVLoop -> IO a) -> IO a
withUVManager (UVManager _ loop loopData running _) f = go
where
go = do
r <- withMVar running $ \ running ->
if running
then do
throwUVIfMinus_ (hs_uv_wake_up_async loopData)
return Nothing
else do
r <- f loop
return (Just r)
case r of
Just r' -> return r'
_ -> yield >> go
withUVManager_ :: HasCallStack => UVManager -> IO a -> IO a
withUVManager_ uvm f = withUVManager uvm (\ _ -> f)
startUVManager :: HasCallStack => UVManager -> IO ()
startUVManager uvm@(UVManager _ _ _ running _) = loop
where
loop = do
e <- withMVar running $ \ _ -> step uvm False
if e > 0
then yield >> loop
else do
yield
e <- withMVar running $ \ _ -> step uvm False
if e > 0 then yield >> loop
else do
_ <- swapMVar running True
e <- step uvm True
_ <- swapMVar running False
yield
loop
step :: UVManager -> Bool -> IO Int
step (UVManager blockTableRef loop loopData _ _) block = do
blockTable <- readIORef blockTableRef
clearUVEventCounter loopData
if block
then if rtsSupportsBoundThreads
then throwUVIfMinus_ $ uv_run_safe loop UV_RUN_ONCE
else do
throwUVIfMinus_ (hs_uv_wake_up_timer loopData)
throwUVIfMinus_ (uv_run loop UV_RUN_ONCE)
else throwUVIfMinus_ (uv_run loop UV_RUN_NOWAIT)
(c, q) <- peekUVEventQueue loopData
forM_ [0..c-1] $ \ i -> do
slot <- peekElemOff q i
lock <- indexArrM blockTable slot
r <- peekBufferTable uvm slot
tryPutMVar lock r
return c
getUVSlot :: HasCallStack => UVManager -> IO UVSlotUnSafe -> IO UVSlot
{-# INLINE getUVSlot #-}
getUVSlot (UVManager blockTableRef _ _ _ _) f = do
slot <- throwUVIfMinus (unsafeGetSlot <$> f)
blockTable <- readIORef blockTableRef
let oldSiz = sizeofArr blockTable
when (slot == oldSiz) $ do
let newSiz = oldSiz `shiftL` 2
blockTable' <- newArr newSiz
copyArr blockTable' 0 blockTable 0 oldSiz
forM_ [oldSiz..newSiz-1] $ \ i ->
writeArr blockTable' i =<< newEmptyMVar
!iBlockTable' <- unsafeFreezeArr blockTable'
writeIORef blockTableRef iBlockTable'
return slot
cancelUVReq :: UVManager -> UVSlot -> (Int -> IO ()) -> IO ()
cancelUVReq uvm slot extra_cleanup = withUVManager uvm $ \ loop -> do
m <- getBlockMVar uvm slot
r <- tryTakeMVar m
case r of
Just r' -> extra_cleanup r'
_ -> do
pokeBufferTable uvm slot nullPtr 0
hs_uv_cancel loop slot
withUVRequest :: HasCallStack
=> UVManager -> (Ptr UVLoop -> IO UVSlotUnSafe) -> IO Int
withUVRequest uvm f = do
(slot, m) <- withUVManager uvm $ \ loop -> mask_ $ do
slot <- getUVSlot uvm (f loop)
m <- getBlockMVar uvm slot
tryTakeMVar m
return (slot, m)
throwUVIfMinus (takeMVar m `onException` cancelUVReq uvm slot no_extra_cleanup)
where no_extra_cleanup = const $ return ()
withUVRequest_ :: HasCallStack
=> UVManager -> (Ptr UVLoop -> IO UVSlotUnSafe) -> IO ()
withUVRequest_ uvm f = void (withUVRequest uvm f)
withUVRequest' :: HasCallStack
=> UVManager
-> (Ptr UVLoop -> IO UVSlotUnSafe)
-> (Int -> IO b)
-> IO b
withUVRequest' uvm f g = do
(slot, m) <- withUVManager uvm $ \ loop -> mask_ $ do
slot <- getUVSlot uvm (f loop)
m <- getBlockMVar uvm slot
tryTakeMVar m
return (slot, m)
g =<< (takeMVar m `onException` cancelUVReq uvm slot no_extra_cleanup)
where no_extra_cleanup = const $ return ()
withUVRequestEx :: HasCallStack
=> UVManager -> (Ptr UVLoop -> IO UVSlotUnSafe) -> (Int -> IO ()) -> IO Int
withUVRequestEx uvm f extra_cleanup = do
(slot, m) <- withUVManager uvm $ \ loop -> mask_ $ do
slot <- getUVSlot uvm (f loop)
m <- getBlockMVar uvm slot
tryTakeMVar m
return (slot, m)
throwUVIfMinus (takeMVar m `onException` cancelUVReq uvm slot extra_cleanup)
forkBa :: IO () -> IO ThreadId
forkBa io = do
i <- atomicAddCounter counter 1
forkOn i io
where
counter :: Counter
{-# NOINLINE counter #-}
counter = unsafePerformIO $ newCounter 0
data UVStream = UVStream
{ uvsHandle :: {-# UNPACK #-} !(Ptr UVHandle)
, uvsSlot :: {-# UNPACK #-} !UVSlot
, uvsManager :: UVManager
, uvsClosed :: {-# UNPACK #-} !(IORef Bool)
}
instance Show UVStream where
show (UVStream handle slot uvm _) =
"UVStream{uvsHandle = " ++ show handle ++
",uvsSlot = " ++ show slot ++
",uvsManager =" ++ show uvm ++ "}"
initUVStream :: HasCallStack
=> (Ptr UVLoop -> Ptr UVHandle -> IO ())
-> UVManager
-> Resource UVStream
initUVStream init uvm = initResource
(withUVManager uvm $ \ loop -> do
handle <- hs_uv_handle_alloc loop
slot <- getUVSlot uvm (peekUVHandleData handle)
tryTakeMVar =<< getBlockMVar uvm slot
init loop handle `onException` hs_uv_handle_free handle
closed <- newIORef False
return (UVStream handle slot uvm closed))
closeUVStream
closeUVStream :: UVStream -> IO ()
closeUVStream (UVStream handle _ uvm closed) = withUVManager_ uvm $ do
c <- readIORef closed
unless c $ writeIORef closed True >> hs_uv_handle_close handle
instance Input UVStream where
readInput uvs@(UVStream handle slot uvm closed) buf len = mask_ $ do
c <- readIORef closed
when c throwECLOSED
m <- getBlockMVar uvm slot
withUVManager_ uvm $ do
throwUVIfMinus_ (hs_uv_read_start handle)
pokeBufferTable uvm slot buf len
tryTakeMVar m
r <- catch (takeMVar m) (\ (e :: SomeException) -> do
withUVManager_ uvm (uv_read_stop handle)
r <- tryTakeMVar m
case r of Just r -> return r
_ -> throwIO e)
if | r > 0 -> return r
| r == fromIntegral UV_EOF -> return 0
| r < 0 -> throwUVIfMinus (return r)
instance Output UVStream where
writeOutput uvs@(UVStream handle _ uvm closed) buf len = mask_ $ do
c <- readIORef closed
when c throwECLOSED
(slot, m) <- withUVManager_ uvm $ do
slot <- getUVSlot uvm (hs_uv_write handle buf len)
m <- getBlockMVar uvm slot
tryTakeMVar m
return (slot, m)
throwUVIfMinus_ (uninterruptibleMask_ $ takeMVar m)