blockio-0.1.0.0: src/System/FS/BlockIO/Serial.hs
module System.FS.BlockIO.Serial (
serialHasBlockIO
) where
import Control.Concurrent.Class.MonadMVar
import Control.Monad (unless)
import Control.Monad.Class.MonadThrow
import Control.Monad.Primitive (PrimMonad, PrimState, RealWorld)
import Data.Primitive (MutableByteArray, isMutableByteArrayPinned)
import qualified Data.Vector as V
import qualified Data.Vector.Unboxed as VU
import qualified Data.Vector.Unboxed.Mutable as VUM
import GHC.Stack (HasCallStack)
import System.FS.API
import qualified System.FS.BlockIO.API as API
import System.FS.BlockIO.API (IOOp (..), IOResult (..), LockMode (..))
import qualified System.FS.BlockIO.IO.Internal as IOI
{-# SPECIALISE serialHasBlockIO ::
Eq h
=> (Handle h -> Bool -> IO ())
-> (Handle h -> API.FileOffset -> API.FileOffset -> API.Advice -> IO ())
-> (Handle h -> API.FileOffset -> API.FileOffset -> IO ())
-> (FsPath -> LockMode -> IO (Maybe (API.LockFileHandle IO)))
-> (Handle h -> IO ())
-> (FsPath -> IO ())
-> (FsPath -> FsPath -> IO ())
-> HasFS IO h
-> IO (API.HasBlockIO IO h)
#-}
-- | IO instantiation of 'HasBlockIO', using an existing 'HasFS'. Thus this
-- implementation does not take advantage of parallel I\/O.
serialHasBlockIO ::
(MonadThrow m, MonadMVar m, PrimMonad m, Eq h)
=> (Handle h -> Bool -> m ())
-> (Handle h -> API.FileOffset -> API.FileOffset -> API.Advice -> m ())
-> (Handle h -> API.FileOffset -> API.FileOffset -> m ())
-> (FsPath -> LockMode -> m (Maybe (API.LockFileHandle m)))
-> (Handle h -> m ())
-> (FsPath -> m ())
-> (FsPath -> FsPath -> m ())
-> HasFS m h
-> m (API.HasBlockIO m h)
serialHasBlockIO hSetNoCache hAdvise hAllocate tryLockFile hSynchronise synchroniseDirectory createHardLink hfs = do
ctx <- initIOCtx (SomeHasFS hfs)
pure $ API.HasBlockIO {
API.close = close ctx
, API.submitIO = submitIO hfs ctx
, API.hSetNoCache
, API.hAdvise
, API.hAllocate
, API.tryLockFile
, API.hSynchronise
, API.synchroniseDirectory
, API.createHardLink
}
data IOCtx m = IOCtx { ctxFS :: SomeHasFS m, openVar :: MVar m Bool }
{-# SPECIALISE guardIsOpen :: IOCtx IO -> IO () #-}
guardIsOpen :: (HasCallStack, MonadMVar m, MonadThrow m) => IOCtx m -> m ()
guardIsOpen ctx = readMVar (openVar ctx) >>= \b ->
case ctxFS ctx of
SomeHasFS hfs ->
unless b $ throwIO $ IOI.mkClosedError hfs "submitIO"
{-# SPECIALISE initIOCtx :: SomeHasFS IO -> IO (IOCtx IO) #-}
initIOCtx :: MonadMVar m => SomeHasFS m -> m (IOCtx m)
initIOCtx someHasFS = IOCtx someHasFS <$> newMVar True
{-# SPECIALISE close :: IOCtx IO -> IO () #-}
close :: MonadMVar m => IOCtx m -> m ()
close ctx = modifyMVar_ (openVar ctx) $ const (pure False)
{-# SPECIALISE submitIO ::
HasFS IO h
-> IOCtx IO -> V.Vector (IOOp RealWorld h)
-> IO (VU.Vector IOResult) #-}
submitIO ::
(HasCallStack, MonadMVar m, MonadThrow m, PrimMonad m)
=> HasFS m h
-> IOCtx m
-> V.Vector (IOOp (PrimState m) h)
-> m (VU.Vector IOResult)
submitIO hfs ctx ioops = do
guardIsOpen ctx
hmapM (ioop hfs) ioops
{-# SPECIALISE ioop :: HasFS IO h -> IOOp RealWorld h -> IO IOResult #-}
-- | Perform the IOOp using synchronous I\/O.
ioop ::
MonadThrow m
=> HasFS m h
-> IOOp (PrimState m) h
-> m IOResult
ioop hfs (IOOpRead h off buf bufOff c) = do
guardPinned hfs buf "submitIO"
IOResult <$> hGetBufExactlyAt hfs h buf bufOff c (fromIntegral off)
ioop hfs (IOOpWrite h off buf bufOff c) = do
guardPinned hfs buf "submitIO"
IOResult <$> hPutBufExactlyAt hfs h buf bufOff c (fromIntegral off)
{-# SPECIALISE guardPinned :: HasFS IO h -> MutableByteArray RealWorld -> String -> IO () #-}
guardPinned ::
MonadThrow m
=> HasFS m h
-> MutableByteArray (PrimState m)
-> String
-> m ()
guardPinned hfs buf loc =
unless (isMutableByteArrayPinned buf) $
throwIO (IOI.mkNotPinnedError hfs loc)
{-# SPECIALISE hmapM ::
VUM.Unbox b
=> (a -> IO b)
-> V.Vector a
-> IO (VU.Vector b) #-}
-- | Heterogeneous blend of 'V.mapM' and 'VU.mapM'.
--
-- The @vector@ package does not provide functions that take distinct vector
-- containers as arguments, so we write it by hand to prevent having to convert
-- one vector type to the other.
hmapM ::
forall m a b. (PrimMonad m, VUM.Unbox b)
=> (a -> m b)
-> V.Vector a
-> m (VU.Vector b)
hmapM f v = do
res <- VUM.unsafeNew n
loop res 0
where
!n = V.length v
loop !res !i
| i == n = VU.unsafeFreeze res
| otherwise = do
let !x = v `V.unsafeIndex` i
!z <- f x
VUM.unsafeWrite res i z
loop res (i+1)