Z-IO 0.8.1.1 → 1.0.0.0
raw patch · 40 files changed
+1483/−914 lines, 40 filesdep +foldldep ~Z-DataPVP ok
version bump matches the API change (PVP)
Dependencies added: foldl
Dependency ranges changed: Z-Data
API changes (from Hackage documentation)
- Z.IO.BIO: consumedNode :: IORef Bool -> BIO a a
- Z.IO.BIO: counterNode :: Counter -> BIO a a
- Z.IO.BIO: filterM :: (a -> IO Bool) -> BIO a a
- Z.IO.BIO: hexEncoder :: Bool -> BIO Bytes Bytes
- Z.IO.BIO: ioBIO :: HasCallStack => (a -> IO b) -> BIO a b
- Z.IO.BIO: newGroupingNode :: Int -> IO (BIO a (Vector a))
- Z.IO.BIO: newParserNode :: HasCallStack => Parser a -> IO (BIO Bytes a)
- Z.IO.BIO: pureBIO :: (a -> b) -> BIO a b
- Z.IO.BIO: runBIO :: HasCallStack => BIO inp out -> IO [out]
- Z.IO.BIO: runBIO_ :: HasCallStack => BIO inp out -> IO ()
- Z.IO.BIO: seqNumNode :: Counter -> BIO a (Int, a)
- Z.IO.BIO: stepBIO :: HasCallStack => BIO inp out -> inp -> IO [out]
- Z.IO.BIO: stepBIO_ :: HasCallStack => BIO inp out -> inp -> IO ()
- Z.IO.BIO: ungroupingNode :: BIO (Vector a) a
- Z.IO.BIO.Concurrent: newBroadcastTChanNode :: Int -> IO (Sink a, IO (Source a))
- Z.IO.BIO.Concurrent: newTBQueueNode :: Int -> Natural -> IO (Sink a, Source a)
- Z.IO.BIO.Concurrent: newTQueueNode :: Int -> IO (Sink a, Source a)
- Z.IO.BIO.Concurrent: zipBIO :: BIO a b -> BIO a c -> BIO a (b, c)
+ Z.IO: forkBa :: IO () -> IO ThreadId
+ Z.IO.BIO: CompressConfig :: CompressLevel -> WindowBits -> MemLevel -> Bytes -> Strategy -> Int -> CompressConfig
+ Z.IO.BIO: DecompressConfig :: WindowBits -> Bytes -> Int -> DecompressConfig
+ Z.IO.BIO: [compressBufferSize] :: CompressConfig -> Int
+ Z.IO.BIO: [compressDictionary] :: CompressConfig -> Bytes
+ Z.IO.BIO: [compressLevel] :: CompressConfig -> CompressLevel
+ Z.IO.BIO: [compressMemoryLevel] :: CompressConfig -> MemLevel
+ Z.IO.BIO: [compressStrategy] :: CompressConfig -> Strategy
+ Z.IO.BIO: [compressWindowBits] :: CompressConfig -> WindowBits
+ Z.IO.BIO: [decompressBufferSize] :: DecompressConfig -> Int
+ Z.IO.BIO: [decompressDictionary] :: DecompressConfig -> Bytes
+ Z.IO.BIO: [decompressWindowBits] :: DecompressConfig -> WindowBits
+ Z.IO.BIO: compressReset :: ZStream -> IO ()
+ Z.IO.BIO: consumed :: TVar Bool -> BIO a a
+ Z.IO.BIO: counter :: Counter -> BIO a a
+ Z.IO.BIO: data CompressConfig
+ Z.IO.BIO: data DecompressConfig
+ Z.IO.BIO: data ZStream
+ Z.IO.BIO: decompressReset :: ZStream -> IO ()
+ Z.IO.BIO: defaultCompressConfig :: CompressConfig
+ Z.IO.BIO: defaultDecompressConfig :: DecompressConfig
+ Z.IO.BIO: defaultMemLevel :: MemLevel
+ Z.IO.BIO: filterIO :: (a -> IO Bool) -> BIO a a
+ Z.IO.BIO: fold' :: Fold a b -> Source a -> IO b
+ Z.IO.BIO: foldIO' :: FoldM IO a b -> Source a -> IO b
+ Z.IO.BIO: fromIO :: HasCallStack => (a -> IO b) -> BIO a b
+ Z.IO.BIO: fromPure :: (a -> b) -> BIO a b
+ Z.IO.BIO: hexEncode :: Bool -> BIO Bytes Bytes
+ Z.IO.BIO: newBroadcastTChanPair :: Int -> IO (Sink a, IO (Source a))
+ Z.IO.BIO: newCompress :: HasCallStack => CompressConfig -> IO (ZStream, BIO Bytes Bytes)
+ Z.IO.BIO: newDecompress :: DecompressConfig -> IO (ZStream, BIO Bytes Bytes)
+ Z.IO.BIO: newGrouping :: Vec v a => Int -> IO (BIO a (v a))
+ Z.IO.BIO: newParser :: HasCallStack => Parser a -> IO (BIO Bytes a)
+ Z.IO.BIO: newTBQueuePair :: Int -> Natural -> IO (Sink a, Source a)
+ Z.IO.BIO: newTQueuePair :: Int -> IO (Sink a, Source a)
+ Z.IO.BIO: run :: HasCallStack => BIO inp out -> IO [out]
+ Z.IO.BIO: run_ :: HasCallStack => BIO inp out -> IO ()
+ Z.IO.BIO: seqNum :: Counter -> BIO a (Int, a)
+ Z.IO.BIO: step :: HasCallStack => BIO inp out -> inp -> IO [out]
+ Z.IO.BIO: step_ :: HasCallStack => BIO inp out -> inp -> IO ()
+ Z.IO.BIO: type MemLevel = CInt
+ Z.IO.BIO: ungrouping :: BIO (Vector a) a
+ Z.IO.BIO: zip :: BIO a b -> BIO a c -> BIO a (b, c)
+ Z.IO.BIO.Base: appendSource :: HasCallStack => Source a -> Source a -> Source a
+ Z.IO.BIO.Base: concatSource :: HasCallStack => [Source a] -> Source a
+ Z.IO.BIO.Base: concatSource' :: HasCallStack => Source (Source a) -> Source a
+ Z.IO.BIO.Base: consumed :: TVar Bool -> BIO a a
+ Z.IO.BIO.Base: counter :: Counter -> BIO a a
+ Z.IO.BIO.Base: discard :: a -> IO ()
+ Z.IO.BIO.Base: filter :: (a -> Bool) -> BIO a a
+ Z.IO.BIO.Base: filterIO :: (a -> IO Bool) -> BIO a a
+ Z.IO.BIO.Base: fold' :: Fold a b -> Source a -> IO b
+ Z.IO.BIO.Base: foldIO' :: FoldM IO a b -> Source a -> IO b
+ Z.IO.BIO.Base: fromIO :: HasCallStack => (a -> IO b) -> BIO a b
+ Z.IO.BIO.Base: fromPure :: (a -> b) -> BIO a b
+ Z.IO.BIO.Base: fuseSink :: HasCallStack => [Sink out] -> Sink out
+ Z.IO.BIO.Base: hexEncode :: Bool -> BIO Bytes Bytes
+ Z.IO.BIO.Base: initSinkToFile :: HasCallStack => CBytes -> Resource (Sink Bytes)
+ Z.IO.BIO.Base: initSourceFromFile :: HasCallStack => CBytes -> Resource (Source Bytes)
+ Z.IO.BIO.Base: initSourceFromFile' :: HasCallStack => CBytes -> Int -> Resource (Source Bytes)
+ Z.IO.BIO.Base: joinSink :: HasCallStack => Sink out -> Sink out -> Sink out
+ Z.IO.BIO.Base: newBase64Decoder :: HasCallStack => IO (BIO Bytes Bytes)
+ Z.IO.BIO.Base: newBase64Encoder :: IO (BIO Bytes Bytes)
+ Z.IO.BIO.Base: newGrouping :: Vec v a => Int -> IO (BIO a (v a))
+ Z.IO.BIO.Base: newHexDecoder :: IO (BIO Bytes Bytes)
+ Z.IO.BIO.Base: newLineSplitter :: IO (BIO Bytes Bytes)
+ Z.IO.BIO.Base: newMagicSplitter :: Word8 -> IO (BIO Bytes Bytes)
+ Z.IO.BIO.Base: newParser :: HasCallStack => Parser a -> IO (BIO Bytes a)
+ Z.IO.BIO.Base: newReChunk :: Int -> IO (BIO Bytes Bytes)
+ Z.IO.BIO.Base: newUTF8Decoder :: HasCallStack => IO (BIO Bytes Text)
+ Z.IO.BIO.Base: pattern EOF :: Maybe a
+ Z.IO.BIO.Base: run :: HasCallStack => BIO inp out -> IO [out]
+ Z.IO.BIO.Base: runBlock :: HasCallStack => BIO inp out -> inp -> IO [out]
+ Z.IO.BIO.Base: runBlock_ :: HasCallStack => BIO inp out -> inp -> IO ()
+ Z.IO.BIO.Base: runBlocks :: HasCallStack => BIO inp out -> [inp] -> IO [out]
+ Z.IO.BIO.Base: runBlocks_ :: HasCallStack => BIO inp out -> [inp] -> IO ()
+ Z.IO.BIO.Base: run_ :: HasCallStack => BIO inp out -> IO ()
+ Z.IO.BIO.Base: seqNum :: Counter -> BIO a (Int, a)
+ Z.IO.BIO.Base: sinkBuilderToBuffered :: HasCallStack => BufferedOutput -> Sink (Builder a)
+ Z.IO.BIO.Base: sinkToBuffered :: HasCallStack => BufferedOutput -> Sink Bytes
+ Z.IO.BIO.Base: sinkToIO :: HasCallStack => (a -> IO ()) -> Sink a
+ Z.IO.BIO.Base: sinkToList :: IO (MVar [a], Sink a)
+ Z.IO.BIO.Base: sourceFromBuffered :: HasCallStack => BufferedInput -> Source Bytes
+ Z.IO.BIO.Base: sourceFromIO :: HasCallStack => IO (Maybe a) -> Source a
+ Z.IO.BIO.Base: sourceFromList :: Foldable f => f a -> Source a
+ Z.IO.BIO.Base: sourceJSONFromBuffered :: forall a. (JSON a, HasCallStack) => BufferedInput -> Source a
+ Z.IO.BIO.Base: sourceParseChunkFromBuffered :: (HasCallStack, Print e) => (Bytes -> Result e a) -> BufferedInput -> Source a
+ Z.IO.BIO.Base: sourceParserFromBuffered :: HasCallStack => Parser a -> BufferedInput -> Source a
+ Z.IO.BIO.Base: sourceTextFromBuffered :: HasCallStack => BufferedInput -> Source Text
+ Z.IO.BIO.Base: step :: HasCallStack => BIO inp out -> inp -> IO [out]
+ Z.IO.BIO.Base: step_ :: HasCallStack => BIO inp out -> inp -> IO ()
+ Z.IO.BIO.Base: type BIO inp out = (Maybe out -> IO ()) " Pass 'EOF' to indicate current node reaches EOF" -> Maybe inp " 'EOF' indicates upstream reaches EOF" -> IO ()
+ Z.IO.BIO.Base: type Sink x = BIO x ()
+ Z.IO.BIO.Base: type Source x = BIO Void x
+ Z.IO.BIO.Base: ungrouping :: BIO (Vector a) a
+ Z.IO.BIO.Base: unsafeRunBlock :: HasCallStack => IO (BIO inp out) -> inp -> [out]
+ Z.IO.BIO.Base: unsafeRunBlocks :: HasCallStack => IO (BIO inp out) -> [inp] -> [out]
+ Z.IO.BIO.Concurrent: newBroadcastTChanPair :: Int -> IO (Sink a, IO (Source a))
+ Z.IO.BIO.Concurrent: newTBQueuePair :: Int -> Natural -> IO (Sink a, Source a)
+ Z.IO.BIO.Concurrent: newTQueuePair :: Int -> IO (Sink a, Source a)
+ Z.IO.BIO.Concurrent: zip :: BIO a b -> BIO a c -> BIO a (b, c)
+ Z.IO.Buffered: writeBuilder' :: HasCallStack => BufferedOutput -> Builder () -> IO ()
+ Z.IO.StdStream: printStdLnP :: (HasCallStack, Print a) => a -> Parser ()
- Z.IO.BIO: type Sink x = BIO x Void
+ Z.IO.BIO: type Sink x = BIO x ()
- Z.IO.StdStream: getStdoutWinSize :: HasCallStack => IO (CInt, CInt)
+ Z.IO.StdStream: getStdoutWinSize :: HasCallStack => IO (Int, Int)
- Z.IO.StdStream.Ansi: getCursorPosition :: IO (Int, Int)
+ Z.IO.StdStream.Ansi: getCursorPosition :: BufferedInput -> IO (Int, Int)
Files
- ChangeLog.md +8/−0
- Z-IO.cabal +7/−5
- Z/IO.hs +4/−3
- Z/IO/BIO.hs +33/−671
- Z/IO/BIO/Base.hs +751/−0
- Z/IO/BIO/Concurrent.hs +17/−13
- Z/IO/BIO/Zlib.hsc +16/−1
- Z/IO/Buffered.hs +61/−44
- Z/IO/Environment.hs +23/−1
- Z/IO/Exception.hs +6/−0
- Z/IO/FileSystem/Base.hs +40/−1
- Z/IO/FileSystem/FilePath.hsc +27/−2
- Z/IO/FileSystem/Threaded.hs +41/−1
- Z/IO/FileSystem/Watch.hs +9/−8
- Z/IO/Logger.hs +30/−15
- Z/IO/LowResTimer.hs +8/−3
- Z/IO/Network/DNS.hsc +12/−3
- Z/IO/Network/IPC.hs +5/−0
- Z/IO/Network/SocketAddr.hsc +71/−11
- Z/IO/Network/TCP.hs +9/−0
- Z/IO/Network/UDP.hs +28/−7
- Z/IO/Process.hsc +12/−1
- Z/IO/Resource.hs +10/−5
- Z/IO/StdStream.hs +11/−5
- Z/IO/StdStream/Ansi.hs +81/−24
- Z/IO/Time.hs +10/−2
- Z/IO/UV/Errno.hsc +2/−0
- Z/IO/UV/FFI.hsc +14/−0
- Z/IO/UV/FFI_Env.hsc +8/−0
- Z/IO/UV/Manager.hs +13/−2
- Z/IO/UV/UVStream.hs +7/−0
- Z/IO/UV/Win.hs +1/−1
- include/fs_shared.hs +31/−8
- test/Z/IO/BIO/BaseSpec.hs +45/−0
- test/Z/IO/BIO/ConcurrentSpec.hs +16/−17
- test/Z/IO/BIO/ZlibSpec.hs +1/−0
- test/Z/IO/BIOSpec.hs +0/−45
- test/Z/IO/LowResTimerSpec.hs +4/−4
- test/Z/IO/Network/UDPSpec.hs +2/−2
- test/Z/IO/ResourceSpec.hs +9/−9
ChangeLog.md view
@@ -1,5 +1,13 @@ # Revision history for Z-IO +## 1.0.0.0 -- 2020-07-08++* Clean function names in `Z.IO.BIO` module, now no `BIO` or `Node` suffix anymore.+* `Z.IO.BIO` is not re-exported from `Z.IO` anymore, user are recommended to import it with qualified name, e.g. `import qualified Z.IO.BIO as BIO`.+* Add `foldl'` and `foldIO'` to `Z.IO.BIO` to use with `Fold` and `FoldM` from `foldl` package.+* Add `INLINABLE` pragmas to many functions.+* Add `printStdLnP` to `Z.IO.StdStream`, a `Parser` debug tool.+ ## 0.8.1.0 -- 2020-06-12 * Remove `-march=native` flag to improve binary portability.
Z-IO.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: Z-IO-version: 0.8.1.1+version: 1.0.0.0 synopsis: Simple and high performance IO toolkit for Haskell description: Simple and high performance IO toolkit for Haskell, including@@ -85,6 +85,7 @@ exposed-modules: Z.IO Z.IO.BIO+ Z.IO.BIO.Base Z.IO.BIO.Concurrent Z.IO.BIO.Zlib Z.IO.Buffered@@ -119,12 +120,13 @@ , base >=4.12 && <5.0 , containers ^>=0.6 , exceptions ^>=0.10+ , foldl >= 1.3 && <2.0 , primitive >=0.7.1 && <0.7.2 , stm ^>=2.5 , time >=1.9 && <2.0 , unix-time >=0.4.7 && <0.5 , unordered-containers ^>=0.2- , Z-Data >=0.8.1 && <0.9+ , Z-Data >=1.0 && <1.1 default-language: Haskell2010 default-extensions:@@ -301,7 +303,7 @@ third_party/libuv/include third_party/libuv/src/unix third_party/libuv/src - cc-options: -Wall + cc-options: -Wall -D_GNU_SOURCE -D_POSIX_C_SOURCE=200112 else if os(osx)@@ -346,7 +348,7 @@ third_party/libuv/include third_party/libuv/src/unix third_party/libuv/src - cc-options: -Wall + cc-options: -Wall -D_DARWIN_UNLIMITED_SELECT=1 -D_DARWIN_USE_64_BIT_INODE=1 else if flag(no-pkg-config)@@ -376,7 +378,7 @@ other-modules: Z.IO.BIO.ConcurrentSpec Z.IO.BIO.ZlibSpec- Z.IO.BIOSpec+ Z.IO.BIO.BaseSpec Z.IO.FileSystem.ThreadedSpec Z.IO.FileSystemSpec Z.IO.LowResTimerSpec
Z/IO.hs view
@@ -13,19 +13,20 @@ -} module Z.IO- ( module Z.IO.BIO- , module Z.IO.Buffered+ ( module Z.IO.Buffered , module Z.IO.Environment , module Z.IO.Exception , module Z.IO.Logger , module Z.IO.Resource , module Z.IO.StdStream+ , forkBa ) where -import Z.IO.BIO import Z.IO.Buffered import Z.IO.Environment import Z.IO.Exception import Z.IO.Logger import Z.IO.Resource import Z.IO.StdStream+import Z.IO.UV.Manager (forkBa)+
Z/IO/BIO.hs view
@@ -1,5 +1,5 @@ {-|-Module : Z.IO.BIO+Module : Z.IO.BIO.Base Description : Composable IO Loops Copyright : (c) Dong Han, 2017-2020 License : BSD@@ -36,7 +36,7 @@ withResource (initSourceFromFile origin) $ \ src -> withResource (initSinkToFile target) $ \ sink ->- runBIO_ $ src . base64Enc . zlibCompressor . sink+ run_ $ src . base64Enc . zlibCompressor . sink > base64AndCompressFile "test" "test.gz" -- run 'zcat "test.gz" | base64 -d' will give you original file@@ -47,6 +47,7 @@ import Z.IO.BIO (BIO, Source, Sink) import qualified Z.IO.BIO as BIO @+ -} module Z.IO.BIO ( -- * The BIO type@@ -56,13 +57,15 @@ , joinSink, fuseSink -- * Run BIO chain , discard- , stepBIO, stepBIO_- , runBIO, runBIO_+ , step, step_+ , run, run_ , runBlock, runBlock_, unsafeRunBlock , runBlocks, runBlocks_, unsafeRunBlocks -- * Make new BIO- , pureBIO, ioBIO- , filter, filterM+ , fromPure, fromIO+ , filter, filterIO+ -- * Use with fold+ , fold', foldIO' -- ** Source , initSourceFromFile , initSourceFromFile'@@ -82,672 +85,31 @@ -- ** Bytes specific , newReChunk , newUTF8Decoder- , newParserNode, newMagicSplitter, newLineSplitter+ , newParser, newMagicSplitter, newLineSplitter , newBase64Encoder, newBase64Decoder- , hexEncoder, newHexDecoder+ , hexEncode+ , newHexDecoder -- ** Generic BIO- , counterNode- , seqNumNode- , newGroupingNode- , ungroupingNode- , consumedNode+ , counter+ , seqNum+ , newGrouping+ , ungrouping+ , consumed+ -- * Concurrent helpers+ , zip, newTQueuePair, newTBQueuePair, newBroadcastTChanPair+ -- * Zlib BIO+ , newCompress, compressReset+ , CompressConfig(..)+ , defaultCompressConfig+ , newDecompress, decompressReset+ , DecompressConfig(..)+ , defaultDecompressConfig+ , MemLevel+ , defaultMemLevel+ , ZStream ) where -import Prelude hiding (filter)-import Control.Concurrent.MVar-import Control.Monad hiding (filterM)-import Control.Monad.IO.Class-import Data.Bits ((.|.))-import Data.IORef-import qualified Data.List as List-import Data.Void-import Data.Word-import System.IO.Unsafe (unsafePerformIO)-import qualified Z.Data.Array as A-import qualified Z.Data.Builder as B-import Z.Data.CBytes (CBytes)-import qualified Z.Data.JSON as JSON-import qualified Z.Data.Parser as P-import Z.Data.PrimRef-import qualified Z.Data.Text as T-import qualified Z.Data.Text.UTF8Codec as T-import qualified Z.Data.Vector as V-import qualified Z.Data.Vector.Base as V-import Z.Data.Vector.Base64-import Z.Data.Vector.Hex-import Z.IO.Buffered-import Z.IO.Exception-import qualified Z.IO.FileSystem.Base as FS-import Z.IO.Resource---- | A 'BIO'(blocked IO) node.------ A 'BIO' node is a push based stream transformer. It can be used to describe different kinds of IO--- devices:------ * @BIO inp out@ describe an IO state machine(e.g. z_stream in zlib),--- which takes some input in block, then outputs.--- * @type Source out = BIO Void out@ described an IO source, which never takes input,--- but gives output until EOF by looping.--- * @type Sink inp = BIO inp Void@ described an IO sink, which takes input and perform some IO effects,--- such as writing to terminal or files.------ You can connect these 'BIO' nodes with '>|>', which connect left node's output to right node's input,--- and return a new 'BIO' node with left node's input type and right node's output type.------ You can run a 'BIO' node in different ways:------ * 'stepBIO'\/'stepBIO_' to supply a single chunk of input and step the BIO node.--- * 'runBIO'\/'runBIO_' will supply EOF directly, which will effectively pull all values from source,--- and push to sink until source reaches EOF.--- * 'runBlock'\/'runBlock_' will supply a single block of input as whole input and run the BIO node.--- * 'runBlocks'\/'runBlocks_' will supply a list of blocks as whole input and run the BIO node.------ Note 'BIO' usually contains some IO states, you can consider it as an opaque 'IORef':------ * You shouldn't use a 'BIO' node across multiple 'BIO' chain unless the state can be reset.--- * You shouldn't use a 'BIO' node across multiple threads unless document states otherwise.------ 'BIO' is simply a convenient way to construct single-thread streaming computation, to use 'BIO'--- in multiple threads, check "Z.IO.BIO.Concurrent" module.----type BIO inp out = (Maybe out -> IO ()) -- ^ Pass 'EOF' to indicate current node reaches EOF- -> Maybe inp -- ^ 'EOF' indicates upstream reaches EOF- -> IO ()---- | Patterns for more meaningful pattern matching.-pattern EOF :: Maybe a-pattern EOF = Nothing---- | Type alias for 'BIO' node which never takes input.------ Note when implement a 'Source', you should assume 'EOF' argument is supplied only once, and you--- should loop to call downstream continuation with all available chunks, then write a final 'EOF'--- to indicate EOF.-type Source x = BIO Void x---- | Type alias for 'BIO' node which only takes input and perform effects.------ Note when implement a 'Sink', you should assume 'EOF' argument is supplied only once(when upstream--- reaches EOF), you do not need to call downstream continuation before EOF, and--- do a flush(also write a final 'EOF') when upstream reach EOF.-type Sink x = BIO x Void---- | Connect two 'BIO' source, after first reach EOF, draw elements from second.-appendSource :: HasCallStack => Source a -> Source a -> Source a-{-# INLINE appendSource #-}-b1 `appendSource` b2 = \ k _ ->- b1 (\ y ->- case y of Just _ -> k y- _ -> b2 k EOF) EOF---- | Fuse two 'BIO' sinks, i.e. everything written to the fused sink will be written to left and right sink.------ Flush result 'BIO' will effectively flush both sink.-joinSink :: HasCallStack => Sink out -> Sink out -> Sink out-{-# INLINE joinSink #-}-b1 `joinSink` b2 = \ k mx ->- case mx of- Just _ -> do- b1 discard mx- b2 discard mx- _ -> do- b1 discard EOF- b2 discard EOF- k EOF---- | Fuse a list of 'BIO' sinks, everything written to the fused sink will be written to every sink in the list.------ Flush result 'BIO' will effectively flush every sink in the list.-fuseSink :: HasCallStack => [Sink out] -> Sink out-{-# INLINABLE fuseSink #-}-fuseSink ss = \ k mx ->- case mx of- Just _ -> mapM_ (\ s -> s discard mx) ss- _ -> do- mapM_ (\ s -> s discard mx) ss- k EOF---- | Connect list of 'BIO' sources, after one reach EOF, draw element from next.-concatSource :: HasCallStack => [Source a] -> Source a-{-# INLINABLE concatSource #-}-concatSource = List.foldl' appendSource emptySource---- | A 'Source' directly write EOF to downstream.-emptySource :: Source a-{-# INLINABLE emptySource #-}-emptySource = \ k _ -> k EOF---- | Connect list of 'BIO' sources, after one reach EOF, draw element from next.-concatSource' :: HasCallStack => Source (Source a) -> Source a-{-# INLINABLE concatSource' #-}-concatSource' ssrc = \ k _ -> ssrc (\ msrc ->- case msrc of- Just src -> src (\ mx ->- case mx of Just _ -> k mx- _ -> return ()) EOF- _ -> k EOF) EOF------------------------------------------------------------------------------------ Run BIO---- | Discards a value.-discard :: a -> IO ()-{-# INLINABLE discard #-}-discard _ = return ()---- | Supply a single chunk of input to a 'BIO' and collect result.-stepBIO :: HasCallStack => BIO inp out -> inp -> IO [out]-{-# INLINABLE stepBIO #-}-stepBIO bio inp = do- accRef <- newIORef []- bio (mapM_ $ \ x -> modifyIORef' accRef (x:)) (Just inp)- reverse <$> readIORef accRef---- | Supply a single chunk of input to a 'BIO' without collecting result.-stepBIO_ :: HasCallStack => BIO inp out -> inp -> IO ()-{-# INLINABLE stepBIO_ #-}-stepBIO_ bio = bio discard . Just---- | Run a 'BIO' loop without providing input.------ When used on 'Source', it starts the streaming loop.--- When used on 'Sink', it performs a flush.-runBIO_ :: HasCallStack => BIO inp out -> IO ()-{-# INLINABLE runBIO_ #-}-runBIO_ bio = bio discard EOF---- | Run a 'BIO' loop without providing input, and collect result.------ When used on 'Source', it will collect all input chunks.-runBIO :: HasCallStack => BIO inp out -> IO [out]-{-# INLINABLE runBIO #-}-runBIO bio = do- accRef <- newIORef []- bio (mapM_ $ \ x -> modifyIORef' accRef (x:)) EOF- reverse <$> readIORef accRef---- | Run a 'BIO' loop with a single chunk of input and EOF, and collect result.----runBlock :: HasCallStack => BIO inp out -> inp -> IO [out]-{-# INLINABLE runBlock #-}-runBlock bio inp = do- accRef <- newIORef []- bio (mapM_ $ \ x -> modifyIORef' accRef (x:)) (Just inp)- bio (mapM_ $ \ x -> modifyIORef' accRef (x:)) EOF- reverse <$> readIORef accRef---- | Run a 'BIO' loop with a single chunk of input and EOF, without collecting result.----runBlock_ :: HasCallStack => BIO inp out -> inp -> IO ()-{-# INLINABLE runBlock_ #-}-runBlock_ bio inp = do- bio discard (Just inp)- bio discard EOF---- | Wrap 'runBlock' into a pure interface.------ You can wrap a stateful BIO computation(including the creation of 'BIO' node),--- when you can guarantee a computation is pure, e.g. compressing, decoding, etc.-unsafeRunBlock :: HasCallStack => IO (BIO inp out) -> inp -> [out]-{-# INLINABLE unsafeRunBlock #-}-unsafeRunBlock new inp = unsafePerformIO (new >>= \ bio -> runBlock bio inp)---- | Supply blocks of input and EOF to a 'BIO', and collect results.------ Note many 'BIO' node will be closed or not be able to take new input after drained.-runBlocks :: HasCallStack => BIO inp out -> [inp] -> IO [out]-{-# INLINABLE runBlocks #-}-runBlocks bio inps = do- accRef <- newIORef []- forM_ inps $ bio (mapM_ $ \ x -> modifyIORef' accRef (x:)) . Just- bio (mapM_ $ \ x -> modifyIORef' accRef (x:)) EOF- reverse <$> readIORef accRef---- | Supply blocks of input and EOF to a 'BIO', without collecting results.------ Note many 'BIO' node will be closed or not be able to take new input after drained.-runBlocks_ :: HasCallStack => BIO inp out -> [inp] -> IO ()-{-# INLINABLE runBlocks_ #-}-runBlocks_ bio inps = do- forM_ inps $ bio discard . Just- bio discard EOF---- | Wrap 'runBlocks' into a pure interface.------ Similar to 'unsafeRunBlock', but with a list of input blocks.-unsafeRunBlocks :: HasCallStack => IO (BIO inp out) -> [inp] -> [out]-{-# INLINABLE unsafeRunBlocks #-}-unsafeRunBlocks new inps = unsafePerformIO (new >>= \ bio -> runBlocks bio inps)------------------------------------------------------------------------------------ Source---- | Source a list(or any 'Foldable') from memory.----sourceFromList :: Foldable f => f a -> Source a-sourceFromList xs0 = \ k _ -> do- mapM_ (k . Just) xs0- k EOF---- | Turn a 'BufferedInput' into 'BIO' source, map EOF to EOF.----sourceFromBuffered :: HasCallStack => BufferedInput -> Source V.Bytes-{-# INLINABLE sourceFromBuffered #-}-sourceFromBuffered i = \ k _ ->- let loop = readBuffer i >>= \ x ->- if V.null x then k EOF else k (Just x) >> loop- in loop---- | Turn a `IO` action into 'Source'-sourceFromIO :: HasCallStack => IO (Maybe a) -> Source a-{-# INLINABLE sourceFromIO #-}-sourceFromIO io = \ k _ ->- let loop = io >>= \ x ->- case x of- Just _ -> k x >> loop- _ -> k EOF- in loop---- | Turn a UTF8 encoded 'BufferedInput' into 'BIO' source, map EOF to EOF.----sourceTextFromBuffered :: HasCallStack => BufferedInput -> Source T.Text-{-# INLINABLE sourceTextFromBuffered #-}-sourceTextFromBuffered i = \ k _ ->- let loop = readBufferText i >>= \ x ->- if T.null x then k EOF else k (Just x) >> loop- in loop---- | Turn a 'JSON' encoded 'BufferedInput' into 'BIO' source, ignoring any--- whitespaces bewteen JSON objects. If EOF reached, then return 'EOF'.--- Throw 'OtherError' with name "EJSON" if JSON value is not parsed or converted.----sourceJSONFromBuffered :: forall a. (JSON.JSON a, HasCallStack) => BufferedInput -> Source a-{-# INLINABLE sourceJSONFromBuffered #-}-sourceJSONFromBuffered = sourceParseChunkFromBuffered JSON.decodeChunk---- | Turn buffered input device into a packet source, throw 'OtherError' with name @EPARSE@ if parsing fail.-sourceParserFromBuffered :: HasCallStack => P.Parser a -> BufferedInput -> Source a-{-# INLINABLE sourceParserFromBuffered #-}-sourceParserFromBuffered p = sourceParseChunkFromBuffered (P.parseChunk p)---- | Turn buffered input device into a packet source, throw 'OtherError' with name @EPARSE@ if parsing fail.-sourceParseChunkFromBuffered :: (HasCallStack, T.Print e)- => (V.Bytes -> P.Result e a) -> BufferedInput -> Source a-{-# INLINABLE sourceParseChunkFromBuffered #-}-sourceParseChunkFromBuffered pc bi = \ k _ ->- let loopA = do- bs <- readBuffer bi- if V.null bs- then k EOF- else loopB bs- loopB bs = do- (rest, r) <- P.parseChunks pc (readBuffer bi) bs- case r of Right v -> k (Just v)- Left e -> throwOtherError "EPARSE" (T.toText e)- if V.null rest- then loopA- else loopB rest- in loopA---- | Turn a file into a 'V.Bytes' source.-initSourceFromFile :: HasCallStack => CBytes -> Resource (Source V.Bytes)-{-# INLINABLE initSourceFromFile #-}-initSourceFromFile p = do- f <- FS.initFile p FS.O_RDONLY FS.DEFAULT_FILE_MODE- liftIO (sourceFromBuffered <$> newBufferedInput f)---- | Turn a file into a 'V.Bytes' source with given chunk size.-initSourceFromFile' :: HasCallStack => CBytes -> Int -> Resource (Source V.Bytes)-{-# INLINABLE initSourceFromFile' #-}-initSourceFromFile' p bufSiz = do- f <- FS.initFile p FS.O_RDONLY FS.DEFAULT_FILE_MODE- liftIO (sourceFromBuffered <$> newBufferedInput' bufSiz f)------------------------------------------------------------------------------------- Sink---- | Turn a 'BufferedOutput' into a 'V.Bytes' sink.-sinkToBuffered :: HasCallStack => BufferedOutput -> Sink V.Bytes-{-# INLINABLE sinkToBuffered #-}-sinkToBuffered bo = \ k mbs ->- case mbs of- Just bs -> writeBuffer bo bs- _ -> flushBuffer bo >> k EOF---- | Turn a 'BufferedOutput' into a 'B.Builder' sink.----sinkBuilderToBuffered :: HasCallStack => BufferedOutput -> Sink (B.Builder a)-{-# INLINABLE sinkBuilderToBuffered #-}-sinkBuilderToBuffered bo = \ k mbs ->- case mbs of- Just bs -> writeBuilder bo bs- _ -> flushBuffer bo >> k EOF---- | Turn a file into a 'V.Bytes' sink.------ Note the file will be opened in @'FS.O_APPEND' .|. 'FS.O_CREAT' .|. 'FS.O_WRONLY'@ mode,--- bytes will be written after the end of the original file if there'are old bytes.-initSinkToFile :: HasCallStack => CBytes -> Resource (Sink V.Bytes)-{-# INLINABLE initSinkToFile #-}-initSinkToFile p = do- f <- FS.initFile p (FS.O_APPEND .|. FS.O_CREAT .|. FS.O_WRONLY) FS.DEFAULT_FILE_MODE- liftIO (sinkToBuffered <$> newBufferedOutput f)---- | Turn an `IO` action into 'BIO' sink.----sinkToIO :: HasCallStack => (a -> IO ()) -> Sink a-{-# INLINABLE sinkToIO #-}-sinkToIO f = \ k ma ->- case ma of- Just a -> f a- _ -> k EOF---- | Turn an `IO` action(and a flush action), into 'BIO' sink.----sinkToIO' :: HasCallStack => (a -> IO ()) -> IO () -> Sink a-{-# INLINABLE sinkToIO' #-}-sinkToIO' f flush = \ k ma ->- case ma of- Just a -> f a- _ -> flush >> k EOF---- | Sink to a list in memory.------ The 'MVar' will be empty during sinking, and will be filled after sink receives an EOF.-sinkToList :: IO (MVar [a], Sink a)-sinkToList = do- xsRef <- newIORef []- rRef <- newEmptyMVar- return (rRef, sinkToIO' (\ x -> modifyIORef xsRef (x:))- (do modifyIORef xsRef reverse- xs <- readIORef xsRef- putMVar rRef xs))------------------------------------------------------------------------------------- Nodes---- | BIO node from a pure function.------ BIO node made with this funtion are stateless, thus can be reused across chains.-pureBIO :: (a -> b) -> BIO a b-{-# INLINE pureBIO #-}-pureBIO f = \ k x -> k (f <$> x)---- | BIO node from an IO function.------ BIO node made with this funtion may not be stateless, it depends on if the IO function use--- IO state.-ioBIO :: HasCallStack => (a -> IO b) -> BIO a b-{-# INLINE ioBIO #-}-ioBIO f = \ k x ->- case x of Just x' -> f x' >>= k . Just- _ -> k EOF---- | BIO node from a pure filter.------ BIO node made with this funtion are stateless, thus can be reused across chains.-filter :: (a -> Bool) -> BIO a a-filter f k = go- where- go (Just a) = when (f a) $ k (Just a)- go Nothing = k Nothing---- | BIO node from an impure filter.------ BIO node made with this funtion may not be stateless, it depends on if the IO function use-filterM :: (a -> IO Bool) -> BIO a a-filterM f k = go- where- go (Just a) = do- mbool <- f a- when mbool $ k (Just a)- go Nothing = k Nothing---- | Make a chunk size divider.------ A divider size divide each chunk's size to the nearest multiplier to granularity,--- last trailing chunk is directly returned.-newReChunk :: Int -- ^ chunk granularity- -> IO (BIO V.Bytes V.Bytes)-{-# INLINABLE newReChunk #-}-newReChunk n = do- trailingRef <- newIORef V.empty- return $ \ k mbs ->- case mbs of- Just bs -> do- trailing <- readIORef trailingRef- let chunk = trailing `V.append` bs- l = V.length chunk- if l >= n- then do- let l' = l - (l `rem` n)- (chunk', rest) = V.splitAt l' chunk- writeIORef trailingRef rest- k (Just chunk')- else writeIORef trailingRef chunk- _ -> do- trailing <- readIORef trailingRef- unless (V.null trailing) $ do- writeIORef trailingRef V.empty- k (Just trailing)- k EOF---- | Read buffer and parse with 'Parser'.------ This function will turn a 'Parser' into a 'BIO', throw 'OtherError' with name @EPARSE@ if parsing fail.----newParserNode :: HasCallStack => P.Parser a -> IO (BIO V.Bytes a)-{-# INLINABLE newParserNode #-}-newParserNode p = do- -- type LastParseState = Maybe (V.Bytes -> P.Result)- resultRef <- newIORef EOF- return $ \ k mbs -> do- let loop f chunk = case f chunk of- P.Success a trailing -> do- k (Just a)- unless (V.null trailing) (loop f trailing)- P.Partial f' ->- writeIORef resultRef (Just f')- P.Failure e _ ->- throwOtherError "EPARSE" (T.toText e)- lastResult <- readIORef resultRef- case mbs of- Just bs -> do- let f = case lastResult of- Just x -> x- _ -> P.parseChunk p- loop f bs- _ ->- case lastResult of- Just f -> loop f V.empty- _ -> k EOF---- | Make a new UTF8 decoder, which decode bytes streams into text streams.------ If there're invalid UTF8 bytes, an 'OtherError' with name 'EINVALIDUTF8' will be thrown.`------ Note this node is supposed to be used with preprocess node such as decompressor, parser, etc.--- where bytes boundary cannot be controlled, UTF8 decoder will concat trailing bytes from last block to next one.--- Use this node directly with 'sourceFromBuffered' will not be as efficient as directly use--- 'sourceTextFromBuffered', because 'BufferedInput' provides push back capability,--- trailing bytes can be pushed back to reading buffer then returned with next block input together.----newUTF8Decoder :: HasCallStack => IO (BIO V.Bytes T.Text)-{-# INLINABLE newUTF8Decoder #-}-newUTF8Decoder = do- trailingRef <- newIORef V.empty- return $ \ k mbs -> do- case mbs of- Just bs -> do- trailing <- readIORef trailingRef- let chunk = trailing `V.append` bs- (V.PrimVector arr s l) = chunk- if l > 0 && T.decodeCharLen arr s <= l- then do- let (i, _) = V.findR (\ w -> w >= 0b11000000 || w <= 0b01111111) chunk- if (i == -1)- then throwOtherError "EINVALIDUTF8" "invalid UTF8 bytes"- else do- if T.decodeCharLen arr (s + i) > l - i- then do- writeIORef trailingRef (V.fromArr arr (s+i) (l-i))- k (Just (T.validate (V.fromArr arr s i)))- else do- writeIORef trailingRef V.empty- k (Just (T.validate chunk))- else writeIORef trailingRef chunk-- _ -> do- trailing <- readIORef trailingRef- if V.null trailing- then k EOF- else throwOtherError "EINVALIDUTF8" "invalid UTF8 bytes"---- | Make a new stream splitter based on magic byte.----newMagicSplitter :: Word8 -> IO (BIO V.Bytes V.Bytes)-{-# INLINABLE newMagicSplitter #-}-newMagicSplitter magic = do- trailingRef <- newIORef V.empty- return $ \ k mx ->- case mx of- Just bs -> do- trailing <- readIORef trailingRef- let loop chunk = case V.elemIndex magic chunk of- Just i -> do- -- TODO: looping- let (line, rest) = V.splitAt (i+1) chunk- k (Just line)- loop rest- _ -> writeIORef trailingRef chunk- loop (trailing `V.append` bs)- _ -> do- chunk <- readIORef trailingRef- unless (V.null chunk) $ do- writeIORef trailingRef V.empty- k (Just chunk)- k EOF---- | Make a new stream splitter based on linefeed(@\r\n@ or @\n@).------ The result bytes doesn't contain linefeed.-newLineSplitter :: IO (BIO V.Bytes V.Bytes)-{-# INLINABLE newLineSplitter #-}-newLineSplitter = do- s <- newMagicSplitter 10- return (s . pureBIO dropLineEnd)- where- dropLineEnd bs@(V.PrimVector arr s l) =- case bs `V.indexMaybe` (l-2) of- Just r | r == 13 -> V.PrimVector arr s (l-2)- | otherwise -> V.PrimVector arr s (l-1)- _ | V.head bs == 10 -> V.PrimVector arr s (l-1)- | otherwise -> V.PrimVector arr s l---- | Make a new base64 encoder node.-newBase64Encoder :: IO (BIO V.Bytes V.Bytes)-{-# INLINABLE newBase64Encoder #-}-newBase64Encoder = do- re <- newReChunk 3- return (re . pureBIO base64Encode)---- | Make a new base64 decoder node.-newBase64Decoder :: HasCallStack => IO (BIO V.Bytes V.Bytes)-{-# INLINABLE newBase64Decoder #-}-newBase64Decoder = do- re <- newReChunk 4- return (re . pureBIO base64Decode')---- | Make a hex encoder node.------ Hex encoder is stateless, it can be reused across chains.-hexEncoder :: Bool -- ^ uppercase?- -> BIO V.Bytes V.Bytes-{-# INLINABLE hexEncoder #-}-hexEncoder upper = pureBIO (hexEncode upper)---- | Make a new hex decoder node.-newHexDecoder :: IO (BIO V.Bytes V.Bytes)-{-# INLINABLE newHexDecoder #-}-newHexDecoder = do- re <- newReChunk 2- return (re . pureBIO hexDecode')---- | Make a new BIO node which counts items flow throught it.------ 'Counter' is increased atomically, it's safe to read \/ reset the counter from other threads.-counterNode :: Counter -> BIO a a-{-# INLINABLE counterNode #-}-counterNode c = ioBIO inc- where- inc x = do- atomicAddCounter_ c 1- return x---- | Make a new BIO node which counts items, and label item with a sequence number.------ 'Counter' is increased atomically, it's safe to read \/ reset the counter from other threads.-seqNumNode :: Counter -> BIO a (Int, a)-{-# INLINABLE seqNumNode #-}-seqNumNode c = ioBIO inc- where- inc x = do- i <- atomicAddCounter c 1- return (i, x)---- | Make a BIO node grouping items into fixed size arrays.------ Trailing items are directly returned.-newGroupingNode :: Int -> IO (BIO a (V.Vector a))-{-# INLINABLE newGroupingNode #-}-newGroupingNode n- | n < 1 = newGroupingNode 1- | otherwise = do- c <- newCounter 0- arrRef <- newIORef =<< A.newArr n- return $ \ k mx ->- case mx of- Just x -> do- i <- readPrimIORef c- if i == n - 1- then do- marr <- readIORef arrRef- A.writeArr marr i x- writePrimIORef c 0- writeIORef arrRef =<< A.newArr n- arr <- A.unsafeFreezeArr marr- k . Just $! V.fromArr arr 0 n- else do- marr <- readIORef arrRef- A.writeArr marr i x- writePrimIORef c (i+1)- _ -> do- i <- readPrimIORef c- if i /= 0- then do- writePrimIORef c 0- marr <- readIORef arrRef- A.shrinkMutableArr marr i- arr <- A.unsafeFreezeArr marr- k . Just $! V.fromArr arr 0 i- else k EOF---- | A BIO node flatten items.----ungroupingNode :: BIO (V.Vector a) a-{-# INLINABLE ungroupingNode #-}-ungroupingNode = \ k mx ->- case mx of- Just x -> V.traverseVec_ (k . Just) x- _ -> k EOF---- | A BIO node which write 'True' to 'IORef' when 'EOF' is reached.-consumedNode :: IORef Bool -> BIO a a-{-# INLINABLE consumedNode #-}-consumedNode ref = \ k mx -> case mx of- Just _ -> k mx- _ -> do writeIORef ref True- k EOF--+import Z.IO.BIO.Base+import Z.IO.BIO.Concurrent+import Z.IO.BIO.Zlib+import Prelude hiding (filter, zip)
+ Z/IO/BIO/Base.hs view
@@ -0,0 +1,751 @@+{-|+Module : Z.IO.BIO.Base+Description : Composable IO Loops+Copyright : (c) Dong Han, 2017-2020+License : BSD+Maintainer : winterland1989@gmail.com+Stability : experimental+Portability : non-portable++This module provides 'BIO' (block IO) type to facilitate writing streaming programs. A 'BIO' node usually:++ * Process input in unit of block(or item).+ * Running in constant spaces, which means the memory usage won't accumulate.+ * Keep some state in IO, which is sealed in 'BIO' closure.++-}+module Z.IO.BIO.Base (+ -- * The BIO type+ BIO, pattern EOF, Source, Sink+ -- ** Basic combinators+ , appendSource, concatSource, concatSource'+ , joinSink, fuseSink+ -- * Run BIO chain+ , discard+ , step, step_+ , run, run_+ , runBlock, runBlock_, unsafeRunBlock+ , runBlocks, runBlocks_, unsafeRunBlocks+ -- * Make new BIO+ , fromPure, fromIO+ , filter, filterIO+ -- * Use with fold+ , fold', foldIO'+ -- ** Source+ , initSourceFromFile+ , initSourceFromFile'+ , sourceFromIO+ , sourceFromList+ , sourceFromBuffered+ , sourceTextFromBuffered+ , sourceJSONFromBuffered+ , sourceParserFromBuffered+ , sourceParseChunkFromBuffered+ -- ** Sink+ , sinkToIO+ , sinkToList+ , initSinkToFile+ , sinkToBuffered+ , sinkBuilderToBuffered+ -- ** Bytes specific+ , newReChunk+ , newUTF8Decoder+ , newParser, newMagicSplitter, newLineSplitter+ , newBase64Encoder, newBase64Decoder+ , hexEncode+ , newHexDecoder+ -- ** Generic BIO+ , counter+ , seqNum+ , newGrouping+ , ungrouping+ , consumed+ ) where++import Prelude hiding (filter)+import Control.Concurrent.MVar+import Control.Concurrent.STM+import qualified Control.Foldl as L+import Control.Monad+import Control.Monad.IO.Class+import Data.Bits ((.|.))+import Data.IORef+import qualified Data.List as List+import Data.Void+import Data.Word+import System.IO.Unsafe (unsafePerformIO)+import qualified Z.Data.Array as A+import qualified Z.Data.Builder as B+import Z.Data.CBytes (CBytes)+import qualified Z.Data.JSON as JSON+import qualified Z.Data.Parser as P+import Z.Data.PrimRef+import qualified Z.Data.Text as T+import qualified Z.Data.Text.UTF8Codec as T+import qualified Z.Data.Vector as V+import qualified Z.Data.Vector.Base as V+import Z.Data.Vector.Base64+import qualified Z.Data.Vector.Hex as Hex+import Z.IO.Buffered+import Z.IO.Exception+import qualified Z.IO.FileSystem.Base as FS+import Z.IO.Resource++-- | A 'BIO'(blocked IO) node.+--+-- A 'BIO' node is a push based stream transformer. It can be used to describe different kinds of IO+-- devices:+--+-- * @BIO inp out@ describe an IO state machine(e.g. z_stream in zlib),+-- which takes some input in block, then outputs.+-- * @type Source out = BIO Void out@ described an IO source, which never takes input,+-- but gives output until EOF by looping.+-- * @type Sink inp = BIO inp Void@ described an IO sink, which takes input and perform some IO effects,+-- such as writing to terminal or files.+--+-- You can connect these 'BIO' nodes with '>|>', which connect left node's output to right node's input,+-- and return a new 'BIO' node with left node's input type and right node's output type.+--+-- You can run a 'BIO' node in different ways:+--+-- * 'step'\/'step_' to supply a single chunk of input and step the BIO node.+-- * 'run'\/'run_' will supply EOF directly, which will effectively pull all values from source,+-- and push to sink until source reaches EOF.+-- * 'runBlock'\/'runBlock_' will supply a single block of input as whole input and run the BIO node.+-- * 'runBlocks'\/'runBlocks_' will supply a list of blocks as whole input and run the BIO node.+--+-- Note 'BIO' usually contains some IO states, you can consider it as an opaque 'IORef':+--+-- * You shouldn't use a 'BIO' node across multiple 'BIO' chain unless the state can be reset.+-- * You shouldn't use a 'BIO' node across multiple threads unless document states otherwise.+--+-- 'BIO' is simply a convenient way to construct single-thread streaming computation, to use 'BIO'+-- in multiple threads, check "Z.IO.BIO.Concurrent" module.+--+type BIO inp out = (Maybe out -> IO ()) -- ^ Pass 'EOF' to indicate current node reaches EOF+ -> Maybe inp -- ^ 'EOF' indicates upstream reaches EOF+ -> IO ()++-- | Patterns for more meaningful pattern matching.+pattern EOF :: Maybe a+pattern EOF = Nothing++-- | Type alias for 'BIO' node which never takes input.+--+-- Note when implement a 'Source', you should assume 'EOF' argument is supplied only once, and you+-- should loop to call downstream continuation with all available chunks, then write a final 'EOF'+-- to indicate EOF.+type Source x = BIO Void x++-- | Type alias for 'BIO' node which only takes input and perform effects.+--+-- Note when implement a 'Sink', you should assume 'EOF' argument is supplied only once(when upstream+-- reaches EOF), you do not need to call downstream continuation before EOF, and+-- do a flush(also write a final 'EOF') when upstream reach EOF.+type Sink x = BIO x ()++-- | Connect two 'BIO' source, after first reach EOF, draw elements from second.+appendSource :: HasCallStack => Source a -> Source a -> Source a+{-# INLINABLE appendSource #-}+b1 `appendSource` b2 = \ k _ ->+ b1 (\ y ->+ case y of Just _ -> k y+ _ -> b2 k EOF) EOF++-- | Fuse two 'BIO' sinks, i.e. everything written to the fused sink will be written to left and right sink.+--+-- Flush result 'BIO' will effectively flush both sink.+joinSink :: HasCallStack => Sink out -> Sink out -> Sink out+{-# INLINABLE joinSink #-}+b1 `joinSink` b2 = \ k mx ->+ case mx of+ Just _ -> do+ b1 discard mx+ b2 discard mx+ _ -> do+ b1 discard EOF+ b2 discard EOF+ k EOF++-- | Fuse a list of 'BIO' sinks, everything written to the fused sink will be written to every sink in the list.+--+-- Flush result 'BIO' will effectively flush every sink in the list.+fuseSink :: HasCallStack => [Sink out] -> Sink out+{-# INLINABLE fuseSink #-}+fuseSink ss = \ k mx ->+ case mx of+ Just _ -> mapM_ (\ s -> s discard mx) ss+ _ -> do+ mapM_ (\ s -> s discard mx) ss+ k EOF++-- | Connect list of 'BIO' sources, after one reach EOF, draw element from next.+concatSource :: HasCallStack => [Source a] -> Source a+{-# INLINABLE concatSource #-}+concatSource = List.foldl' appendSource emptySource++-- | A 'Source' directly write EOF to downstream.+emptySource :: Source a+{-# INLINABLE emptySource #-}+emptySource = \ k _ -> k EOF++-- | Connect list of 'BIO' sources, after one reach EOF, draw element from next.+concatSource' :: HasCallStack => Source (Source a) -> Source a+{-# INLINABLE concatSource' #-}+concatSource' ssrc = \ k _ -> ssrc (\ msrc ->+ case msrc of+ Just src -> src (\ mx ->+ case mx of Just _ -> k mx+ _ -> return ()) EOF+ _ -> k EOF) EOF++-------------------------------------------------------------------------------+-- Run BIO++-- | Discards a value.+discard :: a -> IO ()+{-# INLINABLE discard #-}+discard _ = return ()++-- | Supply a single chunk of input to a 'BIO' and collect result.+step :: HasCallStack => BIO inp out -> inp -> IO [out]+{-# INLINABLE step #-}+step bio inp = do+ accRef <- newIORef []+ bio (mapM_ $ \ x -> modifyIORef' accRef (x:)) (Just inp)+ reverse <$> readIORef accRef++-- | Supply a single chunk of input to a 'BIO' without collecting result.+step_ :: HasCallStack => BIO inp out -> inp -> IO ()+{-# INLINABLE step_ #-}+step_ bio = bio discard . Just++-- | Run a 'BIO' loop without providing input.+--+-- When used on 'Source', it starts the streaming loop.+-- When used on 'Sink', it performs a flush.+run_ :: HasCallStack => BIO inp out -> IO ()+{-# INLINABLE run_ #-}+run_ bio = bio discard EOF++-- | Run a 'BIO' loop without providing input, and collect result.+--+-- When used on 'Source', it will collect all input chunks.+run :: HasCallStack => BIO inp out -> IO [out]+{-# INLINABLE run #-}+run bio = do+ accRef <- newIORef []+ bio (mapM_ $ \ x -> modifyIORef' accRef (x:)) EOF+ reverse <$> readIORef accRef++-- | Run a strict fold over a source with 'L.Fold'.+fold' :: L.Fold a b -> Source a -> IO b+{-# INLINABLE fold' #-}+fold' (L.Fold s i e) bio = do+ iref <- newIORef i+ bio (mapM_ (\ x -> modifyIORef' iref (\ i' -> s i' x))) Nothing+ e <$> readIORef iref++-- | Run a strict fold over a source with 'L.FoldM'.+foldIO' :: L.FoldM IO a b -> Source a -> IO b+{-# INLINABLE foldIO' #-}+foldIO' (L.FoldM s i e) bio = do+ iref <- newIORef =<< i+ bio (mapM_ (\ x -> do+ i' <- readIORef iref+ !x' <- s i' x+ writeIORef iref x')) Nothing+ e =<< readIORef iref++-- | Run a 'BIO' loop with a single chunk of input and EOF, and collect result.+--+runBlock :: HasCallStack => BIO inp out -> inp -> IO [out]+{-# INLINABLE runBlock #-}+runBlock bio inp = do+ accRef <- newIORef []+ bio (mapM_ $ \ x -> modifyIORef' accRef (x:)) (Just inp)+ bio (mapM_ $ \ x -> modifyIORef' accRef (x:)) EOF+ reverse <$> readIORef accRef++-- | Run a 'BIO' loop with a single chunk of input and EOF, without collecting result.+--+runBlock_ :: HasCallStack => BIO inp out -> inp -> IO ()+{-# INLINABLE runBlock_ #-}+runBlock_ bio inp = do+ bio discard (Just inp)+ bio discard EOF++-- | Wrap 'runBlock' into a pure interface.+--+-- You can wrap a stateful BIO computation(including the creation of 'BIO' node),+-- when you can guarantee a computation is pure, e.g. compressing, decoding, etc.+unsafeRunBlock :: HasCallStack => IO (BIO inp out) -> inp -> [out]+{-# INLINABLE unsafeRunBlock #-}+unsafeRunBlock new inp = unsafePerformIO (new >>= \ bio -> runBlock bio inp)++-- | Supply blocks of input and EOF to a 'BIO', and collect results.+--+-- Note many 'BIO' node will be closed or not be able to take new input after drained.+runBlocks :: HasCallStack => BIO inp out -> [inp] -> IO [out]+{-# INLINABLE runBlocks #-}+runBlocks bio inps = do+ accRef <- newIORef []+ forM_ inps $ bio (mapM_ $ \ x -> modifyIORef' accRef (x:)) . Just+ bio (mapM_ $ \ x -> modifyIORef' accRef (x:)) EOF+ reverse <$> readIORef accRef++-- | Supply blocks of input and EOF to a 'BIO', without collecting results.+--+-- Note many 'BIO' node will be closed or not be able to take new input after drained.+runBlocks_ :: HasCallStack => BIO inp out -> [inp] -> IO ()+{-# INLINABLE runBlocks_ #-}+runBlocks_ bio inps = do+ forM_ inps $ bio discard . Just+ bio discard EOF++-- | Wrap 'runBlocks' into a pure interface.+--+-- Similar to 'unsafeRunBlock', but with a list of input blocks.+unsafeRunBlocks :: HasCallStack => IO (BIO inp out) -> [inp] -> [out]+{-# INLINABLE unsafeRunBlocks #-}+unsafeRunBlocks new inps = unsafePerformIO (new >>= \ bio -> runBlocks bio inps)++-------------------------------------------------------------------------------+-- Source++-- | Source a list(or any 'Foldable') from memory.+--+sourceFromList :: Foldable f => f a -> Source a+{-# INLINABLE sourceFromList #-}+sourceFromList xs0 = \ k _ -> do+ mapM_ (k . Just) xs0+ k EOF++-- | Turn a 'BufferedInput' into 'BIO' source, map EOF to EOF.+--+sourceFromBuffered :: HasCallStack => BufferedInput -> Source V.Bytes+{-# INLINABLE sourceFromBuffered #-}+sourceFromBuffered i = \ k _ -> loop k+ where+ loop k = do+ x <- readBuffer i+ if V.null x then k EOF else k (Just x) >> loop k++-- | Turn a `IO` action into 'Source'+sourceFromIO :: HasCallStack => IO (Maybe a) -> Source a+{-# INLINABLE sourceFromIO #-}+sourceFromIO io = \ k _ -> loop k+ where+ loop k = do+ x <- io+ case x of+ Just _ -> k x >> loop k+ _ -> k EOF++-- | Turn a UTF8 encoded 'BufferedInput' into 'BIO' source, map EOF to EOF.+--+sourceTextFromBuffered :: HasCallStack => BufferedInput -> Source T.Text+{-# INLINABLE sourceTextFromBuffered #-}+sourceTextFromBuffered i = \ k _ -> loop k+ where+ loop k = do+ x <- readBufferText i+ if T.null x then k EOF else k (Just x) >> loop k++-- | Turn a 'JSON' encoded 'BufferedInput' into 'BIO' source, ignoring any+-- whitespaces bewteen JSON objects. If EOF reached, then return 'EOF'.+-- Throw 'OtherError' with name "EJSON" if JSON value is not parsed or converted.+--+sourceJSONFromBuffered :: forall a. (JSON.JSON a, HasCallStack) => BufferedInput -> Source a+{-# INLINABLE sourceJSONFromBuffered #-}+sourceJSONFromBuffered = sourceParseChunkFromBuffered JSON.decodeChunk++-- | Turn buffered input device into a packet source, throw 'OtherError' with name @EPARSE@ if parsing fail.+sourceParserFromBuffered :: HasCallStack => P.Parser a -> BufferedInput -> Source a+{-# INLINABLE sourceParserFromBuffered #-}+sourceParserFromBuffered p = sourceParseChunkFromBuffered (P.parseChunk p)++-- | Turn buffered input device into a packet source, throw 'OtherError' with name @EPARSE@ if parsing fail.+sourceParseChunkFromBuffered :: (HasCallStack, T.Print e)+ => (V.Bytes -> P.Result e a) -> BufferedInput -> Source a+{-# INLINABLE sourceParseChunkFromBuffered #-}+sourceParseChunkFromBuffered pc bi = \ k _ ->+ let loopA = do+ bs <- readBuffer bi+ if V.null bs+ then k EOF+ else loopB bs+ loopB bs = do+ (rest, r) <- P.parseChunks pc (readBuffer bi) bs+ case r of Right v -> k (Just v)+ Left e -> throwOtherError "EPARSE" (T.toText e)+ if V.null rest+ then loopA+ else loopB rest+ in loopA++-- | Turn a file into a 'V.Bytes' source.+initSourceFromFile :: HasCallStack => CBytes -> Resource (Source V.Bytes)+{-# INLINABLE initSourceFromFile #-}+initSourceFromFile p = do+ f <- FS.initFile p FS.O_RDONLY FS.DEFAULT_FILE_MODE+ liftIO (sourceFromBuffered <$> newBufferedInput f)++-- | Turn a file into a 'V.Bytes' source with given chunk size.+initSourceFromFile' :: HasCallStack => CBytes -> Int -> Resource (Source V.Bytes)+{-# INLINABLE initSourceFromFile' #-}+initSourceFromFile' p bufSiz = do+ f <- FS.initFile p FS.O_RDONLY FS.DEFAULT_FILE_MODE+ liftIO (sourceFromBuffered <$> newBufferedInput' bufSiz f)++--------------------------------------------------------------------------------+-- Sink++-- | Turn a 'BufferedOutput' into a 'V.Bytes' sink.+sinkToBuffered :: HasCallStack => BufferedOutput -> Sink V.Bytes+{-# INLINABLE sinkToBuffered #-}+sinkToBuffered bo = \ k mbs ->+ case mbs of+ Just bs -> writeBuffer bo bs >> k (Just ())+ _ -> flushBuffer bo >> k EOF++-- | Turn a 'BufferedOutput' into a 'B.Builder' sink.+--+sinkBuilderToBuffered :: HasCallStack => BufferedOutput -> Sink (B.Builder a)+{-# INLINABLE sinkBuilderToBuffered #-}+sinkBuilderToBuffered bo = \ k mbs ->+ case mbs of+ Just bs -> writeBuilder bo bs >> k (Just ())+ _ -> flushBuffer bo >> k EOF++-- | Turn a file into a 'V.Bytes' sink.+--+-- Note the file will be opened in @'FS.O_APPEND' .|. 'FS.O_CREAT' .|. 'FS.O_WRONLY'@ mode,+-- bytes will be written after the end of the original file if there'are old bytes.+initSinkToFile :: HasCallStack => CBytes -> Resource (Sink V.Bytes)+{-# INLINABLE initSinkToFile #-}+initSinkToFile p = do+ f <- FS.initFile p (FS.O_APPEND .|. FS.O_CREAT .|. FS.O_WRONLY) FS.DEFAULT_FILE_MODE+ liftIO (sinkToBuffered <$> newBufferedOutput f)++-- | Turn an `IO` action into 'BIO' sink.+--+sinkToIO :: HasCallStack => (a -> IO ()) -> Sink a+{-# INLINABLE sinkToIO #-}+sinkToIO f = \ k ma ->+ case ma of+ Just a -> f a >> k (Just ())+ _ -> k EOF++-- | Turn an `IO` action(and a flush action), into 'BIO' sink.+--+sinkToIO' :: HasCallStack => (a -> IO ()) -> IO () -> Sink a+{-# INLINABLE sinkToIO' #-}+sinkToIO' f flush = \ k ma ->+ case ma of+ Just a -> f a >> k (Just ())+ _ -> flush >> k EOF++-- | Sink to a list in memory.+--+-- The 'MVar' will be empty during sinking, and will be filled after sink receives an EOF.+sinkToList :: IO (MVar [a], Sink a)+sinkToList = do+ xsRef <- newIORef []+ rRef <- newEmptyMVar+ return (rRef, sinkToIO' (\ x -> modifyIORef xsRef (x:))+ (do modifyIORef xsRef reverse+ xs <- readIORef xsRef+ putMVar rRef xs))++--------------------------------------------------------------------------------+-- s++-- | BIO node from a pure function.+--+-- BIO node made with this funtion are stateless, thus can be reused across chains.+fromPure :: (a -> b) -> BIO a b+{-# INLINABLE fromPure #-}+fromPure f = \ k x -> k (f <$> x)++-- | BIO node from an IO function.+--+-- BIO node made with this funtion may not be stateless, it depends on if the IO function use+-- IO state.+fromIO :: HasCallStack => (a -> IO b) -> BIO a b+{-# INLINABLE fromIO #-}+fromIO f = \ k x ->+ case x of Just x' -> f x' >>= k . Just+ _ -> k EOF++-- | BIO node from a pure filter.+--+-- BIO node made with this funtion are stateless, thus can be reused across chains.+filter :: (a -> Bool) -> BIO a a+{-# INLINABLE filter #-}+filter f k = go+ where+ go (Just a) = when (f a) $ k (Just a)+ go Nothing = k Nothing++-- | BIO node from an impure filter.+--+-- BIO node made with this funtion may not be stateless, it depends on if the IO function use+filterIO :: (a -> IO Bool) -> BIO a a+{-# INLINABLE filterIO #-}+filterIO f k = go+ where+ go (Just a) = do+ mbool <- f a+ when mbool $ k (Just a)+ go Nothing = k Nothing++-- | Make a chunk size divider.+--+-- A divider size divide each chunk's size to the nearest multiplier to granularity,+-- last trailing chunk is directly returned.+newReChunk :: Int -- ^ chunk granularity+ -> IO (BIO V.Bytes V.Bytes)+{-# INLINABLE newReChunk #-}+newReChunk n = do+ trailingRef <- newIORef V.empty+ return $ \ k mbs ->+ case mbs of+ Just bs -> do+ trailing <- readIORef trailingRef+ let chunk = trailing `V.append` bs+ l = V.length chunk+ if l >= n+ then do+ let l' = l - (l `rem` n)+ (chunk', rest) = V.splitAt l' chunk+ writeIORef trailingRef rest+ k (Just chunk')+ else writeIORef trailingRef chunk+ _ -> do+ trailing <- readIORef trailingRef+ unless (V.null trailing) $ do+ writeIORef trailingRef V.empty+ k (Just trailing)+ k EOF++-- | Read buffer and parse with 'Parser'.+--+-- This function will turn a 'Parser' into a 'BIO', throw 'OtherError' with name @EPARSE@ if parsing fail.+--+newParser :: HasCallStack => P.Parser a -> IO (BIO V.Bytes a)+{-# INLINABLE newParser #-}+newParser p = do+ -- type LastParseState = Maybe (V.Bytes -> P.Result)+ resultRef <- newIORef EOF+ return $ \ k mbs -> do+ let loop f chunk = case f chunk of+ P.Success a trailing -> do+ k (Just a)+ unless (V.null trailing) (loop f trailing)+ P.Partial f' ->+ writeIORef resultRef (Just f')+ P.Failure e _ ->+ throwOtherError "EPARSE" (T.toText e)+ lastResult <- readIORef resultRef+ case mbs of+ Just bs -> do+ let f = case lastResult of+ Just x -> x+ _ -> P.parseChunk p+ loop f bs+ _ ->+ case lastResult of+ Just f -> loop f V.empty+ _ -> k EOF++-- | Make a new UTF8 decoder, which decode bytes streams into text streams.+--+-- If there're invalid UTF8 bytes, an 'OtherError' with name 'EINVALIDUTF8' will be thrown.`+--+-- Note this node is supposed to be used with preprocess node such as decompressor, parser, etc.+-- where bytes boundary cannot be controlled, UTF8 decoder will concat trailing bytes from last block to next one.+-- Use this node directly with 'sourceFromBuffered' will not be as efficient as directly use+-- 'sourceTextFromBuffered', because 'BufferedInput' provides push back capability,+-- trailing bytes can be pushed back to reading buffer then returned with next block input together.+--+newUTF8Decoder :: HasCallStack => IO (BIO V.Bytes T.Text)+{-# INLINABLE newUTF8Decoder #-}+newUTF8Decoder = do+ trailingRef <- newIORef V.empty+ return $ \ k mbs -> do+ case mbs of+ Just bs -> do+ trailing <- readIORef trailingRef+ let chunk = trailing `V.append` bs+ (V.PrimVector arr s l) = chunk+ if l > 0 && T.decodeCharLen arr s <= l+ then do+ let (i, _) = V.findR (\ w -> w >= 0b11000000 || w <= 0b01111111) chunk+ if (i == -1)+ then throwOtherError "EINVALIDUTF8" "invalid UTF8 bytes"+ else do+ if T.decodeCharLen arr (s + i) > l - i+ then do+ writeIORef trailingRef (V.fromArr arr (s+i) (l-i))+ k (Just (T.validate (V.fromArr arr s i)))+ else do+ writeIORef trailingRef V.empty+ k (Just (T.validate chunk))+ else writeIORef trailingRef chunk++ _ -> do+ trailing <- readIORef trailingRef+ if V.null trailing+ then k EOF+ else throwOtherError "EINVALIDUTF8" "invalid UTF8 bytes"++-- | Make a new stream splitter based on magic byte.+--+newMagicSplitter :: Word8 -> IO (BIO V.Bytes V.Bytes)+{-# INLINABLE newMagicSplitter #-}+newMagicSplitter magic = do+ trailingRef <- newIORef V.empty+ return $ \ k mx ->+ case mx of+ Just bs -> do+ trailing <- readIORef trailingRef+ let loop chunk = case V.elemIndex magic chunk of+ Just i -> do+ -- TODO: looping+ let (line, rest) = V.splitAt (i+1) chunk+ k (Just line)+ loop rest+ _ -> writeIORef trailingRef chunk+ loop (trailing `V.append` bs)+ _ -> do+ chunk <- readIORef trailingRef+ unless (V.null chunk) $ do+ writeIORef trailingRef V.empty+ k (Just chunk)+ k EOF++-- | Make a new stream splitter based on linefeed(@\r\n@ or @\n@).+--+-- The result bytes doesn't contain linefeed.+newLineSplitter :: IO (BIO V.Bytes V.Bytes)+{-# INLINABLE newLineSplitter #-}+newLineSplitter = do+ s <- newMagicSplitter 10+ return (s . fromPure dropLineEnd)+ where+ dropLineEnd bs@(V.PrimVector arr s l) =+ case bs `V.indexMaybe` (l-2) of+ Just r | r == 13 -> V.PrimVector arr s (l-2)+ | otherwise -> V.PrimVector arr s (l-1)+ _ | V.head bs == 10 -> V.PrimVector arr s (l-1)+ | otherwise -> V.PrimVector arr s l++-- | Make a new base64 encoder node.+newBase64Encoder :: IO (BIO V.Bytes V.Bytes)+{-# INLINABLE newBase64Encoder #-}+newBase64Encoder = do+ re <- newReChunk 3+ return (re . fromPure base64Encode)++-- | Make a new base64 decoder node.+newBase64Decoder :: HasCallStack => IO (BIO V.Bytes V.Bytes)+{-# INLINABLE newBase64Decoder #-}+newBase64Decoder = do+ re <- newReChunk 4+ return (re . fromPure base64Decode')++-- | Make a hex encoder node.+--+-- Hex encoder is stateless, it can be reused across chains.+hexEncode :: Bool -- ^ uppercase?+ -> BIO V.Bytes V.Bytes+{-# INLINABLE hexEncode #-}+hexEncode upper = fromPure (Hex.hexEncode upper)++-- | Make a new hex decoder node.+newHexDecoder :: IO (BIO V.Bytes V.Bytes)+{-# INLINABLE newHexDecoder #-}+newHexDecoder = do+ re <- newReChunk 2+ return (re . fromPure Hex.hexDecode')++-- | Make a new BIO node which counts items flow throught it.+--+-- 'Counter' is increased atomically, it's safe to read \/ reset the counter from other threads.+counter :: Counter -> BIO a a+{-# INLINABLE counter #-}+counter c = fromIO inc+ where+ inc x = do+ atomicAddCounter_ c 1+ return x++-- | Make a new BIO node which counts items, and label item with a sequence number.+--+-- 'Counter' is increased atomically, it's safe to read \/ reset the counter from other threads.+seqNum :: Counter -> BIO a (Int, a)+{-# INLINABLE seqNum #-}+seqNum c = fromIO inc+ where+ inc x = do+ i <- atomicAddCounter c 1+ return (i, x)++-- | Make a BIO node grouping items into fixed size arrays.+--+-- Trailing items are directly returned.+newGrouping :: V.Vec v a => Int -> IO (BIO a (v a))+{-# INLINABLE newGrouping #-}+newGrouping n+ | n < 1 = newGrouping 1+ | otherwise = do+ c <- newCounter 0+ arrRef <- newIORef =<< A.newArr n+ return $ \ k mx ->+ case mx of+ Just x -> do+ i <- readPrimRef c+ if i == n - 1+ then do+ marr <- readIORef arrRef+ A.writeArr marr i x+ writePrimRef c 0+ writeIORef arrRef =<< A.newArr n+ arr <- A.unsafeFreezeArr marr+ k . Just $! V.fromArr arr 0 n+ else do+ marr <- readIORef arrRef+ A.writeArr marr i x+ writePrimRef c (i+1)+ _ -> do+ i <- readPrimRef c+ if i /= 0+ then do+ writePrimRef c 0+ marr <- readIORef arrRef+ A.shrinkMutableArr marr i+ arr <- A.unsafeFreezeArr marr+ k . Just $! V.fromArr arr 0 i+ else k EOF++-- | A BIO node flatten items.+--+ungrouping :: BIO (V.Vector a) a+{-# INLINABLE ungrouping #-}+ungrouping = \ k mx ->+ case mx of+ Just x -> V.traverse_ (k . Just) x+ _ -> k EOF++-- | A BIO node which write 'True' to 'IORef' when 'EOF' is reached.+consumed :: TVar Bool -> BIO a a+{-# INLINABLE consumed #-}+consumed ref = \ k mx -> case mx of+ Just _ -> k mx+ _ -> do atomically (writeTVar ref True)+ k EOF++++
Z/IO/BIO/Concurrent.hs view
@@ -13,9 +13,9 @@ This module provides some concurrent 'BIO' node, to ease the implementation of producer-consumer model. All sources and sinks return by this module are safe to be used in multiple threads. - * Use 'newTQueueNode' for common cases.- * Use 'newTBQueueNode' if you have a fast producer and you don't want input get piled up in memory.- * Use 'newBroadcastTChanNode' if you want messages get broadcasted, i.e. every message written by+ * Use 'newTQueuePair' for common cases.+ * Use 'newTBQueuePair' if you have a fast producer and you don't want input get piled up in memory.+ * Use 'newBroadcastTChanPair' if you want messages get broadcasted, i.e. every message written by producers will be received by every consumers. It's important to correctly set the numebr of producers, internally it keeps a counter on how many producers@@ -23,7 +23,7 @@ exceptions and pull the sink(which indicate EOF) on producer side. @-(sink, src) <- newTQueueNode 2 -- it's important to correctly set the numebr of producers+(sink, src) <- newTQueuePair 2 -- it's important to correctly set the numebr of producers -------------------------------------------------------------------------------- -- producers@@ -63,7 +63,7 @@ import qualified Data.Sequence as Seq import Data.Sequence (Seq((:<|),(:|>))) import GHC.Natural-import Z.IO.BIO+import Z.IO.BIO.Base import Z.Data.PrimRef import Z.IO.Exception @@ -74,8 +74,9 @@ -- * Two node should output same numebr of results. -- * If the number differs, one node maybe ---zipBIO :: BIO a b -> BIO a c -> BIO a (b,c)-zipBIO b1 b2 = \ k mx -> do+zip :: BIO a b -> BIO a c -> BIO a (b,c)+{-# INLINABLE zip #-}+zip b1 b2 = \ k mx -> do bEOF <- newTVarIO False cEOF <- newTVarIO False bBuf <- newTVarIO Seq.empty@@ -104,9 +105,10 @@ _ -> if beof then return (k EOF) else retry -- | Make an unbounded queue and a pair of sink and souce connected to it.-newTQueueNode :: Int -- ^ number of producers+newTQueuePair :: Int -- ^ number of producers -> IO (Sink a, Source a)-newTQueueNode n = do+{-# INLINABLE newTQueuePair #-}+newTQueuePair n = do q <- newTQueueIO ec <- newCounter 0 return@@ -127,10 +129,11 @@ in loop) -- | Make an bounded queue and a pair of sink and souce connected to it.-newTBQueueNode :: Int -- ^ number of producers+newTBQueuePair :: Int -- ^ number of producers -> Natural -- ^ queue buffer bound -> IO (Sink a, Source a)-newTBQueueNode n bound = do+{-# INLINABLE newTBQueuePair #-}+newTBQueuePair n bound = do q <- newTBQueueIO bound ec <- newCounter 0 return@@ -151,9 +154,10 @@ in loop) -- | Make a broadcast chan and a sink connected to it, and a function return sources to receive broadcast message.-newBroadcastTChanNode :: Int -- ^ number of producers+newBroadcastTChanPair :: Int -- ^ number of producers -> IO (Sink a, IO (Source a)) -- ^ (Sink, IO Source)-newBroadcastTChanNode n = do+{-# INLINABLE newBroadcastTChanPair #-}+newBroadcastTChanPair n = do b <- newBroadcastTChanIO ec <- newCounter 0 let dupSrc = do
Z/IO/BIO/Zlib.hsc view
@@ -65,7 +65,7 @@ import Z.Data.Vector.Base as V import Z.Foreign import Z.Foreign.CPtr-import Z.IO.BIO+import Z.IO.BIO.Base import Z.IO.Exception #include "zlib.h"@@ -120,6 +120,7 @@ deriving anyclass (Print, JSON) defaultCompressConfig :: CompressConfig+{-# INLINABLE defaultCompressConfig #-} defaultCompressConfig = CompressConfig Z_DEFAULT_COMPRESSION defaultWindowBits defaultMemLevel V.empty Z_DEFAULT_STRATEGY V.defaultChunkSize@@ -134,6 +135,7 @@ newCompress :: HasCallStack => CompressConfig -> IO (ZStream, BIO V.Bytes V.Bytes)+{-# INLINABLE newCompress #-} newCompress (CompressConfig level windowBits memLevel dict strategy bufSiz) = do zs <- newCPtr' (do ps <- throwOOMIfNull create_z_stream@@ -189,15 +191,18 @@ -- | Reset compressor's state so that related 'BIO' can be reused. compressReset :: ZStream -> IO ()+{-# INLINABLE compressReset #-} compressReset (ZStream fp) = do throwZlibIfMinus_ (withCPtr fp deflateReset) -- | Compress some bytes. compress :: HasCallStack => CompressConfig -> V.Bytes -> V.Bytes+{-# INLINABLE compress #-} compress conf = V.concat . unsafeRunBlock (snd <$> newCompress conf) -- | Compress some bytes in blocks. compressBlocks :: HasCallStack => CompressConfig -> [V.Bytes] -> [V.Bytes]+{-# INLINABLE compressBlocks #-} compressBlocks conf = unsafeRunBlocks (snd <$> newCompress conf) data DecompressConfig = DecompressConfig@@ -208,12 +213,14 @@ deriving anyclass (Print, JSON) defaultDecompressConfig :: DecompressConfig+{-# INLINABLE defaultDecompressConfig #-} defaultDecompressConfig = DecompressConfig defaultWindowBits V.empty V.defaultChunkSize -- | Make a new decompress node. -- -- The returned 'BIO' node can be reused only if you call 'decompressReset' on the 'ZStream'. newDecompress :: DecompressConfig -> IO (ZStream, BIO V.Bytes V.Bytes)+{-# INLINABLE newDecompress #-} newDecompress (DecompressConfig windowBits dict bufSiz) = do zs <- newCPtr' (do ps <- throwOOMIfNull create_z_stream@@ -283,20 +290,24 @@ -- | Reset decompressor's state so that related 'BIO' can be reused. decompressReset :: ZStream -> IO ()+{-# INLINABLE decompressReset #-} decompressReset (ZStream fp) = do throwZlibIfMinus_ (withCPtr fp inflateReset) -- | Decompress some bytes. decompress :: HasCallStack => DecompressConfig -> V.Bytes -> V.Bytes+{-# INLINABLE decompress #-} decompress conf = V.concat . unsafeRunBlock (snd <$> newDecompress conf) -- | Decompress some bytes in blocks. decompressBlocks :: HasCallStack => DecompressConfig -> [V.Bytes] -> [V.Bytes]+{-# INLINABLE decompressBlocks #-} decompressBlocks conf = unsafeRunBlocks (snd <$> newDecompress conf) -------------------------------------------------------------------------------- toZErrorMsg :: CInt -> CBytes+{-# INLINABLE toZErrorMsg #-} toZErrorMsg (#const Z_OK ) = "Z_OK" toZErrorMsg (#const Z_STREAM_END ) = "Z_STREAM_END" toZErrorMsg (#const Z_NEED_DICT ) = "Z_NEED_DICT"@@ -315,6 +326,7 @@ fromException = ioExceptionFromException throwZlibIfMinus :: HasCallStack => IO CInt -> IO CInt+{-# INLINABLE throwZlibIfMinus #-} throwZlibIfMinus f = do r <- f if r < 0 && r /= (#const Z_BUF_ERROR)@@ -322,6 +334,7 @@ else return r throwZlibIfMinus_ :: HasCallStack => IO CInt -> IO ()+{-# INLINABLE throwZlibIfMinus_ #-} throwZlibIfMinus_ = void . throwZlibIfMinus foreign import ccall unsafe@@ -358,6 +371,7 @@ inflateReset :: Ptr ZStream -> IO CInt set_avail_in :: CPtr ZStream -> V.Bytes -> Int -> IO ()+{-# INLINABLE set_avail_in #-} set_avail_in zs buf buflen = do withPrimVectorSafe buf $ \ pbuf _ -> withCPtr zs $ \ ps -> do@@ -365,6 +379,7 @@ (#poke struct z_stream_s, avail_in) ps (fromIntegral buflen :: CUInt) set_avail_out :: CPtr ZStream -> MutablePrimArray RealWorld Word8 -> Int -> IO ()+{-# INLINABLE set_avail_out #-} set_avail_out zs buf bufSiz = do withMutablePrimArrayContents buf $ \ pbuf -> withCPtr zs $ \ ps -> do
Z/IO/Buffered.hs view
@@ -33,7 +33,7 @@ , newBufferedOutput , newBufferedOutput' , writeBuffer, writeBuffer'- , writeBuilder+ , writeBuilder, writeBuilder' , flushBuffer , clearOutputBuffer -- * Buffered Input and Output@@ -51,14 +51,13 @@ import Data.Word import Data.Bits (unsafeShiftR) import Foreign.Ptr-import Z.Data.Array import qualified Z.Data.Builder.Base as B import qualified Z.Data.Parser as P import qualified Z.Data.Vector as V import qualified Z.Data.Text as T import qualified Z.Data.Text.UTF8Codec as T import qualified Z.Data.Vector.Base as V-import Z.Data.PrimRef.PrimIORef+import Z.Data.PrimRef import Z.Foreign import Z.IO.Exception @@ -125,14 +124,16 @@ newBufferedOutput = newBufferedOutput' V.defaultChunkSize -- | Open a new buffered output with given buffer size, e.g. 'V.defaultChunkSize'.+--+-- Size smaller than 'V.smallChunkSize' will be taken as 'V.smallChunkSize'. newBufferedOutput' :: Output o => Int -- ^ Output buffer size -> o -> IO BufferedOutput {-# INLINABLE newBufferedInput' #-} newBufferedOutput' bufSiz o = do- index <- newPrimIORef 0- buf <- newPinnedPrimArray (max bufSiz 0)+ index <- newPrimRef 0+ buf <- newPinnedPrimArray (max bufSiz V.smallChunkSize) return (BufferedOutput (writeOutput o) index buf) -- | Open a new buffered input with given buffer size, e.g. 'V.defaultChunkSize'.@@ -143,18 +144,18 @@ {-# INLINABLE newBufferedOutput' #-} newBufferedInput' bufSiz i = do pb <- newIORef V.empty- buf <- newPinnedPrimArray (max bufSiz 0)+ buf <- newPinnedPrimArray (max bufSiz V.smallChunkSize) inputBuffer <- newIORef buf return (BufferedInput (readInput i) pb inputBuffer) -- | Open a new buffered input and output with 'V.defaultChunkSize' as buffer size. newBufferedIO :: IODev dev => dev -> IO (BufferedInput, BufferedOutput)-{-# INLINE newBufferedIO #-}+{-# INLINABLE newBufferedIO #-} newBufferedIO dev = newBufferedIO' dev V.defaultChunkSize V.defaultChunkSize -- | Open a new buffered input and output with given buffer size, e.g. 'V.defaultChunkSize'. newBufferedIO' :: IODev dev => dev -> Int -> Int -> IO (BufferedInput, BufferedOutput)-{-# INLINE newBufferedIO' #-}+{-# INLINABLE newBufferedIO' #-} newBufferedIO' dev inSize outSize = do i <- newBufferedInput' inSize dev o <- newBufferedOutput' outSize dev@@ -184,9 +185,8 @@ ba <- unsafeFreezePrimArray mba return $! V.fromArr ba 0 l else do -- freeze buf into result- when (bufSiz /= 0) $ do- buf' <- newPinnedPrimArray bufSiz- writeIORef inputBuffer buf'+ buf' <- newPinnedPrimArray bufSiz+ writeIORef inputBuffer buf' shrinkMutablePrimArray rbuf l ba <- unsafeFreezePrimArray rbuf return $! V.fromArr ba 0 l@@ -196,8 +196,8 @@ -- | Request UTF8 'T.Text' chunk from 'BufferedInput'. ----- The buffer size must be larger than 4 bytes to guarantee decoding progress. If there're--- trailing bytes before EOF, an 'OtherError' with name 'EINCOMPLETE' will be thrown, if there're+-- The buffer size must be larger than 4 bytes to guarantee decoding progress(which is guaranteed by 'newBufferedInput').+-- If there're trailing bytes before EOF, an 'OtherError' with name 'EINCOMPLETE' will be thrown, if there're -- invalid UTF8 bytes, an 'OtherError' with name 'EINVALIDUTF8' will be thrown.` readBufferText :: HasCallStack => BufferedInput -> IO T.Text {-# INLINABLE readBufferText #-}@@ -235,9 +235,8 @@ ba <- unsafeFreezePrimArray mba splitLastChar (V.PrimVector ba 0 l) else do -- freeze buf into result- when (bufSiz /= 0) $ do- buf' <- newPinnedPrimArray bufSiz- writeIORef inputBuffer buf'+ buf' <- newPinnedPrimArray bufSiz+ writeIORef inputBuffer buf' shrinkMutablePrimArray rbuf l ba <- unsafeFreezePrimArray rbuf splitLastChar (V.PrimVector ba 0 l)@@ -257,6 +256,7 @@ -- | Clear already buffered input. clearInputBuffer :: BufferedInput -> IO ()+{-# INLINABLE clearInputBuffer #-} clearInputBuffer BufferedInput{..} = writeIORef bufPushBack V.empty -- | Read exactly N bytes.@@ -264,23 +264,31 @@ -- If EOF reached before N bytes read, an 'OtherError' with name 'EINCOMPLETE' will be thrown. readExactly :: HasCallStack => Int -> BufferedInput -> IO V.Bytes {-# INLINABLE readExactly #-}-readExactly n0 h0 = V.concat `fmap` (go h0 n0)+readExactly n0 h = do+ chunk <- readBuffer h+ let l = V.length chunk+ if n0 < l+ then do+ let (!chunk', !rest) = V.splitAt n0 chunk+ unReadBuffer rest h+ return chunk'+ else if n0 == l+ then return chunk+ else V.concatR <$> (go (n0 - l) [chunk]) where- go h n = do+ go !n acc = do chunk <- readBuffer h let l = V.length chunk if l > n then do- let (chunk', rest) = V.splitAt n chunk+ let (!chunk', !rest) = V.splitAt n chunk unReadBuffer rest h- return [chunk']+ return (chunk':acc) else if l == n- then return [chunk]+ then return (chunk:acc) else if l == 0 then throwOtherError "EINCOMPLETE" "input is incomplete"- else do- chunks <- go h (n - l)- return (chunk : chunks)+ else go (n - l) (chunk:acc) -- | Read all chunks from a 'BufferedInput' until EOF. --@@ -293,7 +301,7 @@ loop acc = do chunk <- readBuffer h if V.null chunk- then return $! reverse (chunk:acc)+ then return $! reverse acc else loop (chunk:acc) -- | Read all chunks from a 'BufferedInput', and concat chunks together.@@ -302,7 +310,13 @@ -- Useful for reading small file into memory. readAll' :: HasCallStack => BufferedInput -> IO V.Bytes {-# INLINABLE readAll' #-}-readAll' i = V.concat <$> readAll i+readAll' h = loop []+ where+ loop acc = do+ chunk <- readBuffer h+ if V.null chunk+ then return $! V.concatR acc+ else loop (chunk:acc) -- | Push bytes back into buffer(if not empty). --@@ -403,18 +417,18 @@ writeBuffer :: HasCallStack => BufferedOutput -> V.Bytes -> IO () {-# INLINABLE writeBuffer #-} writeBuffer o@BufferedOutput{..} v@(V.PrimVector ba s l) = do- i <- readPrimIORef bufIndex+ i <- readPrimRef bufIndex bufSiz <- getSizeofMutablePrimArray outputBuffer if i /= 0 then if i + l <= bufSiz then do -- current buffer can hold it copyPrimArray outputBuffer i ba s l -- copy to buffer- writePrimIORef bufIndex (i+l) -- update index+ writePrimRef bufIndex (i+l) -- update index else do -- flush the buffer first withMutablePrimArrayContents outputBuffer $ \ ptr -> bufOutput ptr i- writePrimIORef bufIndex 0+ writePrimRef bufIndex 0 -- try write to buffer again writeBuffer o v else@@ -422,18 +436,13 @@ then withPrimVectorSafe v bufOutput else do copyPrimArray outputBuffer i ba s l -- copy to buffer- writePrimIORef bufIndex l -- update index+ writePrimRef bufIndex l -- update index -- | Write 'V.Bytes' into buffered handle then flush the buffer into output device (if buffer is not empty). ----- * If buffer is empty and bytes are larger than half of buffer, directly write bytes,--- otherwise copy bytes to buffer.------ * If buffer is not empty, then copy bytes to buffer if it can hold, otherwise--- write buffer first, then try again.---+-- Equivalent to add a 'flushBuffer' after write. writeBuffer' :: HasCallStack => BufferedOutput -> V.Bytes -> IO ()-{-# INLINE writeBuffer' #-}+{-# INLINABLE writeBuffer' #-} writeBuffer' bo o = writeBuffer bo o >> flushBuffer bo -- | Directly write 'B.Builder' into buffered handle.@@ -443,21 +452,21 @@ writeBuilder :: HasCallStack => BufferedOutput -> B.Builder a -> IO () {-# INLINABLE writeBuilder #-} writeBuilder BufferedOutput{..} (B.Builder b) = do- i <- readPrimIORef bufIndex+ i <- readPrimRef bufIndex originBufSiz <- getSizeofMutablePrimArray outputBuffer loop originBufSiz =<< b (\ _ -> return . B.Done) (B.Buffer outputBuffer i) where loop originBufSiz r = case r of B.Done buffer@(B.Buffer buf' i') -> do if sameMutablePrimArray buf' outputBuffer- then writePrimIORef bufIndex i'+ then writePrimRef bufIndex i' else if i' >= originBufSiz then do action =<< freezeBuffer buffer- writePrimIORef bufIndex 0+ writePrimRef bufIndex 0 else do copyMutablePrimArray outputBuffer 0 buf' 0 i'- writePrimIORef bufIndex i'+ writePrimRef bufIndex i' B.BufferFull buffer@(B.Buffer _ i') wantSiz k -> do when (i' /= 0) (action =<< freezeBuffer buffer) if wantSiz <= originBufSiz@@ -483,16 +492,24 @@ !arr <- unsafeFreezePrimArray buf return (V.PrimVector arr 0 offset) +-- | Directly write 'B.Builder' into buffered handle then flush the buffer into output device (if buffer is not empty).+--+-- Equivalent to add a 'flushBuffer' after write.+writeBuilder' :: HasCallStack => BufferedOutput -> B.Builder () -> IO ()+{-# INLINABLE writeBuilder' #-}+writeBuilder' bo o = writeBuilder bo o >> flushBuffer bo+ -- | Flush the buffer into output device(if buffer is not empty). -- flushBuffer :: HasCallStack => BufferedOutput -> IO () {-# INLINABLE flushBuffer #-} flushBuffer BufferedOutput{..} = do- i <- readPrimIORef bufIndex+ i <- readPrimRef bufIndex when (i /= 0) $ do withMutablePrimArrayContents outputBuffer $ \ ptr -> bufOutput ptr i- writePrimIORef bufIndex 0+ writePrimRef bufIndex 0 -- | Clear already buffered output. clearOutputBuffer :: BufferedOutput -> IO ()-clearOutputBuffer BufferedOutput{..} = writePrimIORef bufIndex 0+{-# INLINABLE clearOutputBuffer #-}+clearOutputBuffer BufferedOutput{..} = writePrimRef bufIndex 0
Z/IO/Environment.hs view
@@ -36,7 +36,7 @@ import Control.Monad import Data.Word-import Z.Data.Vector.Base as V+import qualified Z.Data.Vector.Base as V import Z.Data.CBytes import Z.Foreign import Z.IO.Exception@@ -51,6 +51,7 @@ -- This is different from base's 'System.Environment.getArgs' since result -- includes the program path(more like C's *argv). getArgs :: IO [CBytes]+{-# INLINABLE getArgs #-} getArgs = do (argc :: CInt, (p_argv :: Ptr CString, _)) <- allocPrimUnsafe $ \ p_argc -> do allocPrimUnsafe $ \ p_p_argv -> do@@ -62,6 +63,7 @@ -- -- Warning: This function is not thread safe. getAllEnv :: HasCallStack => IO [(CBytes, CBytes)]+{-# INLINABLE getAllEnv #-} getAllEnv = bracket (do (p_env :: Ptr CString, (envc :: CInt, _)) <- allocPrimUnsafe $ \ p_p_env -> do allocPrimUnsafe $ \ p_envc ->@@ -78,6 +80,7 @@ -- -- Warning: This function is not thread safe. getEnv :: HasCallStack => CBytes -> IO (Maybe CBytes)+{-# INLINABLE getEnv #-} getEnv k = go 512 where go siz = do@@ -96,6 +99,7 @@ -- -- Warning: This function is not thread safe. getEnv' :: HasCallStack => CBytes -> IO CBytes+{-# INLINABLE getEnv' #-} getEnv' k = getEnv k >>= \ mv -> case mv of Just v -> return v _ -> throwUVError UV_ENOENT (IOEInfo "ENOENT" "no such environment variable" callStack)@@ -104,6 +108,7 @@ -- -- Warning: This function is not thread safe. setEnv :: HasCallStack => CBytes -> CBytes -> IO ()+{-# INLINABLE setEnv #-} setEnv k v = withCBytesUnsafe k $ \ p_k -> withCBytesUnsafe v $ \ p_v -> throwUVIfMinus_ (uv_os_setenv p_k p_v)@@ -112,10 +117,12 @@ -- -- Warning: This function is not thread safe. unsetEnv :: HasCallStack => CBytes -> IO ()+{-# INLINABLE unsetEnv #-} unsetEnv k = void . withCBytesUnsafe k $ \ p -> throwUVIfMinus_ (uv_os_unsetenv p) -- | Gets the resident set size (RSS) for the current process. getResidentSetMemory :: HasCallStack => IO CSize+{-# INLINABLE getResidentSetMemory #-} getResidentSetMemory = do (size, r) <- allocPrimUnsafe uv_resident_set_memory throwUVIfMinus_ (return r)@@ -123,6 +130,7 @@ -- | Gets the current system uptime. getUpTime :: HasCallStack => IO Double+{-# INLINABLE getUpTime #-} getUpTime = do (size, r) <- allocPrimUnsafe uv_uptime throwUVIfMinus_ (return r)@@ -134,6 +142,7 @@ -- It is not related to the time of day and therefore not subject to clock drift. -- The primary use is for measuring performance between intervals. getHighResolutionTime :: IO Word64+{-# INLINABLE getHighResolutionTime #-} getHighResolutionTime = uv_hrtime -- | Gets the resource usage measures for the current process.@@ -141,6 +150,7 @@ -- On Windows not all fields are set, the unsupported fields are filled with zeroes. -- See 'ResUsage' for more details. getResUsage :: HasCallStack => IO ResUsage+{-# INLINABLE getResUsage #-} getResUsage = do (MutableByteArray mba#) <- newByteArray sizeOfResUsage throwUVIfMinus_ (uv_getrusage mba#)@@ -148,15 +158,18 @@ -- | Returns the current process ID. getPID :: IO PID+{-# INLINABLE getPID #-} getPID = uv_os_getpid -- | Returns the parent process ID. getPPID :: IO PID+{-# INLINABLE getPPID #-} getPPID = uv_os_getppid -- | Returns the hostname as a null-terminated string. -- getHostname :: HasCallStack => IO CBytes+{-# INLINABLE getHostname #-} getHostname = do (n, _) <- allocCBytesUnsafe (fromIntegral UV_MAXHOSTNAMESIZE) $ \ p_n -> withPrimUnsafe UV_MAXHOSTNAMESIZE $ \ p_siz ->@@ -168,6 +181,7 @@ -- The function may block indefinitely when not enough entropy is available, don't use it to get -- long random sequences. getRandom :: Int -> IO V.Bytes+{-# INLINABLE getRandom #-} getRandom siz = do (v, _) <- allocPrimVectorUnsafe siz $ \ mba# -> throwUVIfMinus_ (hs_uv_random mba# (fromIntegral siz) 0)@@ -177,6 +191,7 @@ -- -- The function run 'getRandom' in libuv's threadpool, suitable for get long random byte sequences. getRandomT :: Int -> IO V.Bytes+{-# INLINABLE getRandomT #-} getRandomT siz = do (v, _) <- allocPrimVectorSafe siz $ \ p -> do uvm <- getUVManager@@ -186,6 +201,7 @@ -- | Gets the current working directory. -- getCWD :: HasCallStack => IO CBytes+{-# INLINABLE getCWD #-} getCWD = go 512 where go siz = do@@ -201,6 +217,7 @@ -- | Changes the current working directory. -- chDir :: HasCallStack => CBytes -> IO ()+{-# INLINABLE chDir #-} chDir p = throwUVIfMinus_ (withCBytesUnsafe p $ \ pp -> uv_chdir pp) -- | Gets the current user’s home directory.@@ -212,6 +229,7 @@ -- -- Warning 'getHomeDir' is not thread safe. getHomeDir :: HasCallStack => IO CBytes+{-# INLINABLE getHomeDir #-} getHomeDir = go 512 where go siz = do@@ -232,6 +250,7 @@ -- -- Warning 'getHomeDir' is not thread safe. getTempDir :: HasCallStack => IO CBytes+{-# INLINABLE getTempDir #-} getTempDir = go 512 where go siz = do@@ -246,10 +265,12 @@ -- | Gets the amount of free memory available in the system, as reported by the kernel (in bytes). getFreeMem :: IO Word64+{-# INLINABLE getFreeMem #-} getFreeMem = uv_get_free_memory -- | Gets the total amount of physical memory in the system (in bytes). getTotalMem :: IO Word64+{-# INLINABLE getTotalMem #-} getTotalMem = uv_get_total_memory -- | Gets the amount of memory available to the process (in bytes) based on limits imposed by the OS.@@ -258,6 +279,7 @@ -- Note that it is not unusual for this value to be less than or greater than 'getTotalMem'. -- Note This function currently only returns a non-zero value on Linux, based on cgroups if it is present. getConstrainedMem :: IO Word64+{-# INLINABLE getConstrainedMem #-} getConstrainedMem = uv_get_constrained_memory --------------------------------------------------------------------------------
Z/IO/Exception.hs view
@@ -103,9 +103,11 @@ instance Exception SomeIOException ioExceptionToException :: Exception e => e -> SomeException+{-# INLINABLE ioExceptionToException #-} ioExceptionToException = toException . SomeIOException ioExceptionFromException :: Exception e => SomeException -> Maybe e+{-# INLINABLE ioExceptionFromException #-} ioExceptionFromException x = do SomeIOException a <- fromException x cast a@@ -211,6 +213,7 @@ -- | Check if the given exception is synchronous -- isSyncException :: Exception e => e -> Bool+{-# INLINABLE isSyncException #-} isSyncException e = case fromException (toException e) of Just (SomeAsyncException _) -> False@@ -219,12 +222,14 @@ -- | Same as upstream 'C.catch', but will not catch asynchronous exceptions -- catchSync :: Exception e => IO a -> (e -> IO a) -> IO a+{-# INLINABLE catchSync #-} catchSync f g = f `catch` \ e -> if isSyncException e then g e else throwIO e -- | Ingore all synchronous exceptions. -- ignoreSync :: IO a -> IO ()+{-# INLINABLE ignoreSync #-} ignoreSync f = catchSync (void f) (\ (_ :: SomeException) -> return ()) --------------------------------------------------------------------------------@@ -240,6 +245,7 @@ instance Show IOEInfo where show = T.toString instance T.Print IOEInfo where+ {-# INLINE toUTF8BuilderP #-} toUTF8BuilderP _ (IOEInfo errno desc cstack) = do "{name:" T.text errno
Z/IO/FileSystem/Base.hs view
@@ -120,7 +120,7 @@ import qualified Z.Data.Builder as B import Z.Data.CBytes as CBytes import qualified Z.Data.JSON as JSON-import Z.Data.PrimRef.PrimIORef+import Z.Data.PrimRef import qualified Z.Data.Text as T import qualified Z.Data.Text.Print as T import qualified Z.Data.Vector as V@@ -153,12 +153,14 @@ -- | Return File fd. getFileFD :: File -> IO FD+{-# INLINABLE getFileFD #-} getFileFD (File fd closedRef) = do closed <- readIORef closedRef if closed then throwECLOSED else return fd -- | If fd is -1 (closed), throw 'ResourceVanished' ECLOSED. checkFileClosed :: HasCallStack => File -> (FD -> IO a) -> IO a+{-# INLINABLE checkFileClosed #-} checkFileClosed (File fd closedRef) f = do closed <- readIORef closedRef if closed then throwECLOSED else f fd@@ -167,11 +169,13 @@ -- -- Equivalent to <https://linux.die.net/man/3/lseek64 lseek64(3)>. seek :: HasCallStack => File -> Int64 -> Whence -> IO Int64+{-# INLINABLE seek #-} seek uvf off w = checkFileClosed uvf $ \ fd -> throwUVIfMinus $ hs_seek fd off w instance Input File where -- readInput :: HasCallStack => File -> Ptr Word8 -> Int -> IO Int -- use -1 offset to use fd's default offset+ {-# INLINABLE readInput #-} readInput f buf bufSiz = readFileP f buf bufSiz (-1) -- | Read file with given offset@@ -183,10 +187,12 @@ -> Int -- ^ buffer size -> Int64 -- ^ file offset, pass -1 to use default(system) offset -> IO Int -- ^ read length+{-# INLINABLE readFileP #-} readFileP uvf buf bufSiz off = checkFileClosed uvf $ \ fd -> throwUVIfMinus $ hs_uv_fs_read fd buf bufSiz off instance Output File where+ {-# INLINABLE writeOutput #-} writeOutput f buf bufSiz = writeFileP f buf bufSiz (-1) -- | Write buffer to file@@ -207,6 +213,7 @@ -> Int -- ^ buffer size -> Int64 -- ^ file offset, pass -1 to use default(system) offset -> IO ()+{-# INLINABLE writeFileP #-} writeFileP uvf buf0 bufSiz0 off0 = checkFileClosed uvf $ \fd -> if off0 == -1 then go fd buf0 bufSiz0 else go' fd buf0 bufSiz0 off0@@ -236,6 +243,7 @@ -> FileMode -- ^ Sets the file mode (permission and sticky bits), -- but only if the file was created, see 'DEFAULT_FILE_MODE'. -> Resource File+{-# INLINABLE initFile #-} initFile path flags mode = initResource (do !fd <- withCBytesUnsafe path $ \ p ->@@ -256,6 +264,7 @@ -- Note mode is currently not implemented on Windows. On unix you should set execute bit -- if you want the directory is accessable, e.g. 0o777. mkdir :: HasCallStack => CBytes -> FileMode -> IO ()+{-# INLINABLE mkdir #-} mkdir path mode = throwUVIfMinus_ . withCBytesUnsafe path $ \ p -> hs_uv_fs_mkdir p mode @@ -269,6 +278,7 @@ -- can be created), e.g. 'DEFAULT_DIR_MODE'. -- mkdirp :: HasCallStack => CBytes -> FileMode -> IO ()+{-# INLINABLE mkdirp #-} mkdirp path mode = do r <- withCBytesUnsafe path $ \ p -> hs_uv_fs_mkdir p mode case fromIntegral r of@@ -294,6 +304,7 @@ -- | Equivalent to <http://linux.die.net/man/2/unlink unlink(2)>. unlink :: HasCallStack => CBytes -> IO ()+{-# INLINABLE unlink #-} unlink path = throwUVIfMinus_ (withCBytesUnsafe path hs_uv_fs_unlink) @@ -309,6 +320,7 @@ -- so no need to add XXXXXX ending. -- mkdtemp :: HasCallStack => CBytes -> IO CBytes+{-# INLINABLE mkdtemp #-} mkdtemp path = do let size = CBytes.length path withCBytesUnsafe path $ \ p -> do@@ -329,6 +341,7 @@ -> CBytes -- ^ A file path prefix template, no "XXXXXX" needed. -> Bool -- ^ Keep file after used? -> Resource (CBytes, File)+{-# INLINABLE mkstemp #-} mkstemp dir template keep = do initResource (do template' <- dir `P.join` template@@ -349,12 +362,14 @@ -- -- Note this function may inherent OS limitations such as argument must be an empty folder. rmdir :: HasCallStack => CBytes -> IO ()+{-# INLINABLE rmdir #-} rmdir path = throwUVIfMinus_ (withCBytesUnsafe path hs_uv_fs_rmdir) -- | Removes a file or directory at path together with its contents and -- subdirectories. Symbolic links are removed without affecting their targets. -- If the path does not exist, nothing happens. rmrf :: HasCallStack => CBytes -> IO ()+{-# INLINABLE rmrf #-} rmrf path = withCBytesUnsafe path $ \path' -> allocaBytes uvStatSize $ \s -> do@@ -385,6 +400,7 @@ -- systems (btrfs, ext2, ext3 and ext4 at the time of this writing), check the -- <http://linux.die.net/man/2/getdents getdents(2)> man page. scandir :: HasCallStack => CBytes -> IO [(CBytes, DirEntType)]+{-# INLINABLE scandir #-} scandir path = do bracket (withCBytesUnsafe path $ \ p ->@@ -403,6 +419,7 @@ -- | Equivalent to <http://linux.die.net/man/2/stat stat(2)> stat :: HasCallStack => CBytes -> IO FStat+{-# INLINABLE stat #-} stat path = withCBytesUnsafe path $ \ p -> allocaBytes uvStatSize $ \ s -> do throwUVIfMinus_ (hs_uv_fs_stat p s)@@ -410,6 +427,7 @@ -- | Equivalent to <http://linux.die.net/man/2/lstat lstat(2)> lstat :: HasCallStack => CBytes -> IO FStat+{-# INLINABLE lstat #-} lstat path = withCBytesUnsafe path $ \ p -> allocaBytes uvStatSize $ \ s -> do throwUVIfMinus_ (hs_uv_fs_lstat p s)@@ -419,6 +437,7 @@ -- -- Return 'Nothing' instead of throwing 'NoSuchThing' if the file doesn't exist. stat' :: HasCallStack => CBytes -> IO (Maybe FStat)+{-# INLINABLE stat' #-} stat' path = withCBytesUnsafe path $ \ p -> allocaBytes uvStatSize $ \ s -> do r <- fromIntegral <$> hs_uv_fs_stat p s@@ -430,6 +449,7 @@ -- -- Return 'Nothing' instead of throwing 'NoSuchThing' if the link doesn't exist. lstat' :: HasCallStack => CBytes -> IO (Maybe FStat)+{-# INLINABLE lstat' #-} lstat' path = withCBytesUnsafe path $ \ p -> allocaBytes uvStatSize $ \ s -> do r <- fromIntegral <$> hs_uv_fs_lstat p s@@ -439,6 +459,7 @@ -- | Equivalent to <http://linux.die.net/man/2/fstat fstat(2)> fstat :: HasCallStack => File -> IO FStat+{-# INLINABLE fstat #-} fstat uvf = checkFileClosed uvf $ \ fd -> allocaBytes uvStatSize $ \ s -> do throwUVIfMinus_ (hs_uv_fs_fstat fd s)@@ -450,25 +471,30 @@ -- -- Note On Windows if this function fails with UV_EBUSY, UV_EPERM or UV_EACCES, it will retry to rename the file up to four times with 250ms wait between attempts before giving up. If both path and new_path are existing directories this function will work only if target directory is empty. rename :: HasCallStack => CBytes -> CBytes -> IO ()+{-# INLINABLE rename #-} rename path path' = throwUVIfMinus_ . withCBytesUnsafe path $ \ p -> withCBytesUnsafe path' (hs_uv_fs_rename p) -- | Equivalent to <http://linux.die.net/man/2/fsync fsync(2)>. fsync :: HasCallStack => File -> IO ()+{-# INLINABLE fsync #-} fsync uvf = checkFileClosed uvf $ \ fd -> throwUVIfMinus_ $ hs_uv_fs_fsync fd -- | Equivalent to <http://linux.die.net/man/2/fdatasync fdatasync(2)>. fdatasync :: HasCallStack => File -> IO ()+{-# INLINABLE fdatasync #-} fdatasync uvf = checkFileClosed uvf $ \ fd -> throwUVIfMinus_ $ hs_uv_fs_fdatasync fd -- | Equivalent to <http://linux.die.net/man/2/ftruncate ftruncate(2)>. ftruncate :: HasCallStack => File -> Int64 -> IO ()+{-# INLINABLE ftruncate #-} ftruncate uvf off = checkFileClosed uvf $ \ fd -> throwUVIfMinus_ $ hs_uv_fs_ftruncate fd off -- | Copies a file from path to new_path. -- -- Warning: If the destination path is created, but an error occurs while copying the data, then the destination path is removed. There is a brief window of time between closing and removing the file where another process could access the file. copyfile :: HasCallStack => CBytes -> CBytes -> CopyFileFlag -> IO ()+{-# INLINABLE copyfile #-} copyfile path path' flag = throwUVIfMinus_ . withCBytesUnsafe path $ \ p -> withCBytesUnsafe path' $ \ p' -> hs_uv_fs_copyfile p p' flag @@ -476,6 +502,7 @@ -- -- Windows uses GetFileAttributesW(). access :: HasCallStack => CBytes -> AccessMode -> IO AccessResult+{-# INLINABLE access #-} access path mode = do r <- withCBytesUnsafe path $ \ p -> fromIntegral <$> hs_uv_fs_access p mode if | r == 0 -> return AccessOK@@ -488,10 +515,12 @@ -- | Equivalent to <http://linux.die.net/man/2/chmod chmod(2)>. chmod :: HasCallStack => CBytes -> FileMode -> IO ()+{-# INLINABLE chmod #-} chmod path mode = throwUVIfMinus_ . withCBytesUnsafe path $ \ p -> hs_uv_fs_chmod p mode -- | Equivalent to <http://linux.die.net/man/2/fchmod fchmod(2)>. fchmod :: HasCallStack => File -> FileMode -> IO ()+{-# INLINABLE fchmod #-} fchmod uvf mode = checkFileClosed uvf $ \ fd -> throwUVIfMinus_ $ hs_uv_fs_fchmod fd mode -- | Equivalent to <http://linux.die.net/man/2/utime utime(2)>.@@ -502,12 +531,14 @@ -> Double -- ^ atime, i.e. access time -> Double -- ^ mtime, i.e. modify time -> IO ()+{-# INLINABLE utime #-} utime path atime mtime = throwUVIfMinus_ . withCBytesUnsafe path $ \ p -> hs_uv_fs_utime p atime mtime -- | Equivalent to <https://man7.org/linux/man-pages/man3/futimes.3.html futime(3)>. -- -- Same precision notes with 'utime'. futime :: HasCallStack => File -> Double -> Double -> IO ()+{-# INLINABLE futime #-} futime uvf atime mtime = checkFileClosed uvf $ \ fd -> throwUVIfMinus_ (hs_uv_fs_futime fd atime mtime) @@ -519,10 +550,12 @@ -> Double -- ^ atime, i.e. access time -> Double -- ^ mtime, i.e. modify time -> IO ()+{-# INLINABLE lutime #-} lutime path atime mtime = throwUVIfMinus_ . withCBytesUnsafe path $ \ p -> hs_uv_fs_lutime p atime mtime -- | Equivalent to <http://linux.die.net/man/2/link link(2)>. link :: HasCallStack => CBytes -> CBytes -> IO ()+{-# INLINABLE link #-} link path path' = throwUVIfMinus_ . withCBytesUnsafe path $ \ p -> withCBytesUnsafe path' $ hs_uv_fs_link p @@ -535,11 +568,13 @@ -- -- On other platforms these flags are ignored. symlink :: HasCallStack => CBytes -> CBytes -> SymlinkFlag -> IO ()+{-# INLINABLE symlink #-} symlink path path' flag = throwUVIfMinus_ . withCBytesUnsafe path $ \ p -> withCBytesUnsafe path' $ \ p' -> hs_uv_fs_symlink p p' flag -- | Equivalent to <http://linux.die.net/man/2/readlink readlink(2)>. readlink :: HasCallStack => CBytes -> IO CBytes+{-# INLINABLE readlink #-} readlink path = do bracket (withCBytesUnsafe path $ \ p ->@@ -567,6 +602,7 @@ -- -- Note This function is not implemented on Windows XP and Windows Server 2003. On these systems, UV_ENOSYS is returned. realpath :: HasCallStack => CBytes -> IO CBytes+{-# INLINABLE realpath #-} realpath path = do bracket (withCBytesUnsafe path $ \ p ->@@ -577,12 +613,15 @@ -- | Equivalent to <http://linux.die.net/man/2/chown chown(2)>. chown :: HasCallStack => CBytes -> UID -> GID -> IO ()+{-# INLINABLE chown #-} chown path uid gid = throwUVIfMinus_ . withCBytesUnsafe path $ \ p -> hs_uv_fs_chown p uid gid -- | Equivalent to <http://linux.die.net/man/2/fchown fchown(2)>. fchown :: HasCallStack => File -> UID -> GID -> IO ()+{-# INLINABLE fchown #-} fchown uvf uid gid = checkFileClosed uvf $ \ fd -> throwUVIfMinus_ $ hs_uv_fs_fchown fd uid gid -- | Equivalent to <http://linux.die.net/man/2/lchown lchown(2)>. lchown :: HasCallStack => CBytes -> UID -> GID -> IO ()+{-# INLINABLE lchown #-} lchown path uid gid = throwUVIfMinus_ . withCBytesUnsafe path $ \ p -> hs_uv_fs_lchown p uid gid
Z/IO/FileSystem/FilePath.hsc view
@@ -75,10 +75,12 @@ deriving anyclass (T.Print, JSON) enumToPathStyle_ :: CInt -> PathStyle+{-# INLINABLE enumToPathStyle_ #-} enumToPathStyle_ (#const CWK_STYLE_WINDOWS) = WindowsStyle enumToPathStyle_ _ = UnixStyle pathStyleToEnum_ :: PathStyle -> CInt+{-# INLINABLE pathStyleToEnum_ #-} pathStyleToEnum_ WindowsStyle = (#const CWK_STYLE_WINDOWS) pathStyleToEnum_ _ = (#const CWK_STYLE_UNIX) @@ -96,10 +98,12 @@ -- * If nothing was found to determine the style -> UNIX -- pathStyle :: CBytes -> IO PathStyle+{-# INLINABLE pathStyle #-} pathStyle p = enumToPathStyle_ <$> withCBytesUnsafe p cwk_path_guess_style -- | Gets the path style currently using. getPathStyle :: IO PathStyle+{-# INLINABLE getPathStyle #-} getPathStyle = enumToPathStyle_ <$> cwk_path_get_style -- | Configures which path style is used afterwards.@@ -108,10 +112,12 @@ -- call to this function is only required if a non-native behaviour is required. -- The style defaults to 'WindowsStyle' on windows builds and to 'UnixStyle' otherwise. setPathStyle :: PathStyle -> IO ()+{-# INLINABLE setPathStyle #-} setPathStyle = cwk_path_set_style . pathStyleToEnum_ -- | Get the default character that separates directories. pathSeparator :: IO Word8+{-# INLINABLE pathSeparator #-} pathSeparator = do s <- getPathStyle case s of UnixStyle -> return (#const SLASH)@@ -119,6 +125,7 @@ -- | Get characters that separates directories. pathSeparators :: IO [Word8]+{-# INLINABLE pathSeparators #-} pathSeparators = do s <- getPathStyle case s of UnixStyle -> return [(#const SLASH)]@@ -126,6 +133,7 @@ -- | Test if a character is a path separator. isPathSeparator :: Word8 -> IO Bool+{-# INLINABLE isPathSeparator #-} isPathSeparator w = do s <- getPathStyle case s of UnixStyle -> return (w == #const SLASH)@@ -137,6 +145,7 @@ -- * Unix: searchPathSeparator is ASCII @:@ -- searchPathSeparator :: IO Word8+{-# INLINABLE searchPathSeparator #-} searchPathSeparator = do s <- getPathStyle case s of UnixStyle -> return (#const COLON)@@ -144,6 +153,7 @@ -- | Test if a character is a file separator. isSearchPathSeparator :: Word8 -> IO Bool+{-# INLINABLE isSearchPathSeparator #-} isSearchPathSeparator w = do w' <- searchPathSeparator return (w == w')@@ -152,10 +162,12 @@ -- -- ExtSeparator is ASCII @.@ extensionSeparator :: Word8+{-# INLINABLE extensionSeparator #-} extensionSeparator = #const DOT -- | Test if a character is a file extension separator. isExtensionSeparator :: Word8 -> Bool+{-# INLINABLE isExtensionSeparator #-} isExtensionSeparator = (== #const DOT) -- | Get the basename of a file path.@@ -205,6 +217,7 @@ changeBaseName :: CBytes -> CBytes -- ^ new base name -> IO CBytes+{-# INLINABLE changeBaseName #-} changeBaseName p b = do let l = CB.length p + CB.length b + (#const BUF_EXT_SIZ) (p', _) <- withCBytesUnsafe p $ \ pp ->@@ -268,6 +281,7 @@ changeRoot :: CBytes -> CBytes -- ^ new base name -> IO CBytes+{-# INLINABLE changeRoot #-} changeRoot p r = do let l = CB.length p + CB.length r + (#const BUF_EXT_SIZ) (p', _) <- withCBytesUnsafe p $ \ pp ->@@ -329,6 +343,7 @@ -- +---------+--------------------------+-----------+ -- isAbsolute :: CBytes -> IO Bool+{-# INLINABLE isAbsolute #-} isAbsolute p = (/=0) <$> withCBytesUnsafe p cwk_path_is_absolute -- | Determine whether the path is relative or not.@@ -365,6 +380,7 @@ -- +---------+--------------------------+-----------+ -- isRelative :: CBytes -> IO Bool+{-# INLINABLE isRelative #-} isRelative p = (/=0) <$> withCBytesUnsafe p cwk_path_is_relative -- | Joins two paths together.@@ -392,6 +408,7 @@ -- +---------+-----------------------+---------------------------+--------------------------------------+ -- join :: CBytes -> CBytes -> IO CBytes+{-# INLINABLE join #-} join p p2 = do let l = CB.length p + CB.length p2 + (#const BUF_EXT_SIZ) (p', _) <- withCBytesUnsafe p $ \ pp ->@@ -406,6 +423,7 @@ -- It will remove double separators, and unlike 'absolute', -- it permits the use of multiple relative paths to combine. concat :: [CBytes] -> IO CBytes+{-# INLINABLE concat #-} concat ps = do (p', _) <- withCBytesListUnsafe ps $ \ pp l -> do let l' = sum (List.map (\ p -> CB.length p + (#const BUF_EXT_SIZ)) ps)@@ -440,6 +458,7 @@ -- +--------------------------------------------------+-------------------+ -- normalize :: CBytes -> IO CBytes+{-# INLINABLE normalize #-} normalize p = do let l = CB.length p + (#const BUF_EXT_SIZ) (p', _) <- withCBytesUnsafe p $ \ pp ->@@ -478,6 +497,7 @@ intersection :: CBytes -- ^ The base path which will be compared with the other path. -> CBytes -- ^ The other path which will compared with the base path. -> IO CBytes+{-# INLINABLE intersection #-} intersection p1 p2 = do len <- withCBytesUnsafe p1 $ \ pp1 -> withCBytesUnsafe p2 $ \ pp2 ->@@ -511,6 +531,7 @@ absolute :: CBytes -- ^ The absolute base path on which the relative path will be applied. -> CBytes -- ^ The relative path which will be applied on the base path. -> IO CBytes+{-# INLINABLE absolute #-} absolute p p2 = do let l = CB.length p + CB.length p2 + (#const BUF_EXT_SIZ) (p', _) <- withCBytesUnsafe p $ \ pp ->@@ -557,6 +578,7 @@ => CBytes -- ^ The base path from which the relative path will start. -> CBytes -- ^ The target path where the relative path will point to. -> IO CBytes+{-# INLINABLE relative #-} relative p p2 = do let l = (CB.length p `unsafeShiftL` 1) + CB.length p2 + (#const BUF_EXT_SIZ) (p', r) <- withCBytesUnsafe p $ \ pp ->@@ -615,7 +637,7 @@ -- dropExtension :: CBytes -- ^ file path -> IO CBytes-{-# INLINE dropExtension #-}+{-# INLINABLE dropExtension #-} dropExtension p = fst <$> splitExtension p -- | Get the extension of a file from a file path, returns @\"\"@ for no@@ -637,12 +659,13 @@ -- takeExtension :: CBytes -- ^ file path -> IO CBytes-{-# INLINE takeExtension #-}+{-# INLINABLE takeExtension #-} takeExtension p = snd <$> splitExtension p -- | Test if a file from a file path has an extension. hasExtension :: CBytes -- ^ file path -> IO Bool+{-# INLINABLE hasExtension #-} hasExtension p = do withCBytesUnsafe p $ \ p' -> cwk_path_has_extension p'@@ -667,6 +690,7 @@ changeExtension :: CBytes -- ^ The path which will be used to make the change. -> CBytes -- ^ The extension which will be placed within the new path. -> IO CBytes+{-# INLINABLE changeExtension #-} changeExtension p p2 = do let l = CB.length p + CB.length p2 + (#const BUF_EXT_SIZ) (p', _) <- withCBytesUnsafe p $ \ pp ->@@ -679,6 +703,7 @@ -- | Get a list of paths in the @$PATH@ variable. getSearchPath :: IO [CBytes]+{-# INLINABLE getSearchPath #-} getSearchPath = do s <- getEnv' "PATH" sp <- searchPathSeparator
Z/IO/FileSystem/Threaded.hs view
@@ -132,7 +132,7 @@ import qualified Z.Data.Builder as B import Z.Data.CBytes as CBytes import qualified Z.Data.JSON as JSON-import Z.Data.PrimRef.PrimIORef+import Z.Data.PrimRef import qualified Z.Data.Text as T import qualified Z.Data.Text.Print as T import qualified Z.Data.Vector as V@@ -165,16 +165,19 @@ instance Show File where show = T.toString instance T.Print File where+ {-# INLINABLE toUTF8BuilderP #-} toUTF8BuilderP _ (File fd _) = "File " >> T.int fd -- | Return File fd. getFileFD :: File -> IO FD+{-# INLINABLE getFileFD #-} getFileFD (File fd closedRef) = do closed <- readIORef closedRef if closed then throwECLOSED else return fd -- | If fd is -1 (closed), throw 'ResourceVanished' ECLOSED. checkFileClosed :: HasCallStack => File -> (FD -> IO a) -> IO a+{-# INLINABLE checkFileClosed #-} checkFileClosed (File fd closedRef) f = do closed <- readIORef closedRef if closed then throwECLOSED else f fd@@ -183,9 +186,11 @@ -- -- Equivalent to <https://linux.die.net/man/3/lseek64 lseek64(3)>. seek :: HasCallStack => File -> Int64 -> Whence -> IO Int64+{-# INLINABLE seek #-} seek uvf off w = checkFileClosed uvf $ \ fd -> throwUVIfMinus $ hs_seek fd off w instance Input File where+ {-# INLINABLE readInput #-} readInput f buf bufSiz = readFileP f buf bufSiz (-1) -- | Read file with given offset@@ -197,12 +202,14 @@ -> Int -- ^ buffer size -> Int64 -- ^ file offset, pass -1 to use default(system) offset -> IO Int -- ^ read length+{-# INLINABLE readFileP #-} readFileP uvf buf bufSiz off = checkFileClosed uvf $ \ fd -> do uvm <- getUVManager withUVRequest uvm (hs_uv_fs_read_threaded fd buf bufSiz off) instance Output File where+ {-# INLINABLE writeOutput #-} writeOutput f buf bufSiz = writeFileP f buf bufSiz (-1) -- | Write buffer to file@@ -223,6 +230,7 @@ -> Int -- ^ buffer size -> Int64 -- ^ file offset, pass -1 to use default(system) offset -> IO ()+{-# INLINABLE writeFileP #-} writeFileP uvf buf0 bufSiz0 off0 = checkFileClosed uvf $ \ fd -> do (if off0 == -1 then go fd buf0 bufSiz0@@ -259,6 +267,7 @@ -> FileMode -- ^ Sets the file mode (permission and sticky bits), -- but only if the file was created, see 'DEFAULT_FILE_MODE'. -> Resource File+{-# INLINABLE initFile #-} initFile path flags mode = initResource (do uvm <- getUVManager@@ -278,6 +287,7 @@ -- Note mode is currently not implemented on Windows. On unix you should set execute bit -- if you want the directory is accessable, e.g. 0o777. mkdir :: HasCallStack => CBytes -> FileMode -> IO ()+{-# INLINABLE mkdir #-} mkdir path mode = do uvm <- getUVManager withCBytesUnsafe path $ \ p ->@@ -293,6 +303,7 @@ -- can be created), e.g. 'DEFAULT_DIR_MODE'. -- mkdirp :: HasCallStack => CBytes -> FileMode -> IO ()+{-# INLINABLE mkdirp #-} mkdirp path mode = do uvm <- getUVManager r <- withCBytesUnsafe path $ \ p ->@@ -320,6 +331,7 @@ -- | Equivalent to <http://linux.die.net/man/2/unlink unlink(2)>. unlink :: HasCallStack => CBytes -> IO ()+{-# INLINABLE unlink #-} unlink path = do uvm <- getUVManager withCBytesUnsafe path $ \ p ->@@ -337,6 +349,7 @@ -- so no need to add XXXXXX ending. -- mkdtemp :: HasCallStack => CBytes -> IO CBytes+{-# INLINABLE mkdtemp #-} mkdtemp path = do let size = CBytes.length path withCBytesUnsafe path $ \ p -> do@@ -358,6 +371,7 @@ -> CBytes -- ^ A file path prefix template, no "XXXXXX" needed. -> Bool -- ^ Keep file after used? -> Resource (CBytes, File)+{-# INLINABLE mkstemp #-} mkstemp dir template keep = do initResource (do template' <- dir `P.join` template@@ -377,6 +391,7 @@ -- -- Note this function may inherent OS limitations such as argument must be an empty folder. rmdir :: HasCallStack => CBytes -> IO ()+{-# INLINABLE rmdir #-} rmdir path = do uvm <- getUVManager withCBytesUnsafe path (\ p -> void . withUVRequest uvm $ hs_uv_fs_rmdir_threaded p)@@ -385,6 +400,7 @@ -- subdirectories. Symbolic links are removed without affecting their targets. -- If the path does not exist, nothing happens. rmrf :: HasCallStack => CBytes -> IO ()+{-# INLINABLE rmrf #-} rmrf path = withCBytesUnsafe path $ \path' -> allocaBytes uvStatSize $ \s -> do@@ -416,6 +432,7 @@ -- -- Note On Linux, getting the type of an entry is only supported by some file systems (btrfs, ext2, ext3 and ext4 at the time of this writing), check the <http://linux.die.net/man/2/getdents getdents(2)> man page. scandir :: HasCallStack => CBytes -> IO [(CBytes, DirEntType)]+{-# INLINABLE scandir #-} scandir path = do uvm <- getUVManager bracket@@ -436,6 +453,7 @@ -- | Equivalent to <http://linux.die.net/man/2/stat stat(2)> stat :: HasCallStack => CBytes -> IO FStat+{-# INLINABLE stat #-} stat path = do withCBytesUnsafe path $ \ p -> allocaBytes uvStatSize $ \ s -> do@@ -445,6 +463,7 @@ -- | Equivalent to <http://linux.die.net/man/2/lstat lstat(2)> lstat :: HasCallStack => CBytes -> IO FStat+{-# INLINABLE lstat #-} lstat path = withCBytesUnsafe path $ \ p -> allocaBytes uvStatSize $ \ s -> do@@ -456,6 +475,7 @@ -- -- Return 'Nothing' instead of throwing 'NoSuchThing' if the file doesn't exist. stat' :: HasCallStack => CBytes -> IO (Maybe FStat)+{-# INLINABLE stat' #-} stat' path = do withCBytesUnsafe path $ \ p -> allocaBytes uvStatSize $ \ s -> do@@ -469,6 +489,7 @@ -- -- Return 'Nothing' instead of throwing 'NoSuchThing' if the link doesn't exist. lstat' :: HasCallStack => CBytes -> IO (Maybe FStat)+{-# INLINABLE lstat' #-} lstat' path = withCBytesUnsafe path $ \ p -> allocaBytes uvStatSize $ \ s -> do@@ -480,6 +501,7 @@ -- | Equivalent to <http://linux.die.net/man/2/fstat fstat(2)> fstat :: HasCallStack => File -> IO FStat+{-# INLINABLE fstat #-} fstat uvf = checkFileClosed uvf $ \ fd -> (allocaBytes uvStatSize $ \ s -> do uvm <- getUVManager@@ -492,6 +514,7 @@ -- -- Note On Windows if this function fails with UV_EBUSY, UV_EPERM or UV_EACCES, it will retry to rename the file up to four times with 250ms wait between attempts before giving up. If both path and new_path are existing directories this function will work only if target directory is empty. rename :: HasCallStack => CBytes -> CBytes -> IO ()+{-# INLINABLE rename #-} rename path path' = do uvm <- getUVManager withCBytesUnsafe path $ \ p ->@@ -500,18 +523,21 @@ -- | Equivalent to <http://linux.die.net/man/2/fsync fsync(2)>. fsync :: HasCallStack => File -> IO ()+{-# INLINABLE fsync #-} fsync uvf = checkFileClosed uvf $ \ fd -> do uvm <- getUVManager withUVRequest_ uvm (hs_uv_fs_fsync_threaded fd) -- | Equivalent to <http://linux.die.net/man/2/fdatasync fdatasync(2)>. fdatasync :: HasCallStack => File -> IO ()+{-# INLINABLE fdatasync #-} fdatasync uvf = checkFileClosed uvf $ \ fd -> do uvm <- getUVManager withUVRequest_ uvm (hs_uv_fs_fdatasync_threaded fd) -- | Equivalent to <http://linux.die.net/man/2/ftruncate ftruncate(2)>. ftruncate :: HasCallStack => File -> Int64 -> IO ()+{-# INLINABLE ftruncate #-} ftruncate uvf off = checkFileClosed uvf $ \ fd -> do uvm <- getUVManager withUVRequest_ uvm (hs_uv_fs_ftruncate_threaded fd off)@@ -520,6 +546,7 @@ -- -- Warning: If the destination path is created, but an error occurs while copying the data, then the destination path is removed. There is a brief window of time between closing and removing the file where another process could access the file. copyfile :: HasCallStack => CBytes -> CBytes -> CopyFileFlag -> IO ()+{-# INLINABLE copyfile #-} copyfile path path' flag = do uvm <- getUVManager withCBytesUnsafe path $ \ p ->@@ -529,6 +556,7 @@ -- | Equivalent to <http://linux.die.net/man/2/access access(2)> on Unix. -- Windows uses GetFileAttributesW(). access :: HasCallStack => CBytes -> AccessMode -> IO AccessResult+{-# INLINABLE access #-} access path mode = do uvm <- getUVManager withCBytesUnsafe path $ \ p ->@@ -545,6 +573,7 @@ -- | Equivalent to <http://linux.die.net/man/2/chmod chmod(2)>. chmod :: HasCallStack => CBytes -> FileMode -> IO ()+{-# INLINABLE chmod #-} chmod path mode = do uvm <- getUVManager withCBytesUnsafe path $ \ p ->@@ -552,6 +581,7 @@ -- | Equivalent to <http://linux.die.net/man/2/fchmod fchmod(2)>. fchmod :: HasCallStack => File -> FileMode -> IO ()+{-# INLINABLE fchmod #-} fchmod uvf mode = checkFileClosed uvf $ \ fd -> do uvm <- getUVManager withUVRequest_ uvm (hs_uv_fs_fchmod_threaded fd mode)@@ -565,6 +595,7 @@ -> Double -- ^ atime, i.e. access time -> Double -- ^ mtime, i.e. modify time -> IO ()+{-# INLINABLE utime #-} utime path atime mtime = do uvm <- getUVManager withCBytesUnsafe path $ \ p ->@@ -574,6 +605,7 @@ -- -- Same precision notes with 'utime'. futime :: HasCallStack => File -> Double -> Double -> IO ()+{-# INLINABLE futime #-} futime uvf atime mtime = checkFileClosed uvf $ \ fd -> do uvm <- getUVManager withUVRequest_ uvm (hs_uv_fs_futime_threaded fd atime mtime)@@ -586,6 +618,7 @@ -> Double -- ^ atime, i.e. access time -> Double -- ^ mtime, i.e. modify time -> IO ()+{-# INLINABLE lutime #-} lutime path atime mtime = do uvm <- getUVManager withCBytesUnsafe path $ \ p ->@@ -593,6 +626,7 @@ -- | Equivalent to <http://linux.die.net/man/2/link link(2)>. link :: HasCallStack => CBytes -> CBytes -> IO ()+{-# INLINABLE link #-} link path path' = do uvm <- getUVManager withCBytesUnsafe path $ \ p ->@@ -608,6 +642,7 @@ -- -- On other platforms these flags are ignored. symlink :: HasCallStack => CBytes -> CBytes -> SymlinkFlag -> IO ()+{-# INLINABLE symlink #-} symlink path path' flag = do uvm <- getUVManager withCBytesUnsafe path $ \ p ->@@ -616,6 +651,7 @@ -- | Equivalent to <http://linux.die.net/man/2/readlink readlink(2)>. readlink :: HasCallStack => CBytes -> IO CBytes+{-# INLINABLE readlink #-} readlink path = do uvm <- getUVManager bracket@@ -645,6 +681,7 @@ -- -- Note This function is not implemented on Windows XP and Windows Server 2003. On these systems, UV_ENOSYS is returned. realpath :: HasCallStack => CBytes -> IO CBytes+{-# INLINABLE realpath #-} realpath path = do uvm <- getUVManager bracket@@ -658,6 +695,7 @@ -- | Equivalent to <http://linux.die.net/man/2/chown chown(2)>. chown :: HasCallStack => CBytes -> UID -> GID -> IO ()+{-# INLINABLE chown #-} chown path uid gid = do uvm <- getUVManager withCBytesUnsafe path $ \ p ->@@ -665,12 +703,14 @@ -- | Equivalent to <http://linux.die.net/man/2/fchown fchown(2)>. fchown :: HasCallStack => FD -> UID -> GID -> IO ()+{-# INLINABLE fchown #-} fchown fd uid gid = do uvm <- getUVManager withUVRequest_ uvm (hs_uv_fs_fchown_threaded fd uid gid) -- | Equivalent to <http://linux.die.net/man/2/lchown lchown(2)>. lchown :: HasCallStack => CBytes -> UID -> GID -> IO ()+{-# INLINABLE lchown #-} lchown path uid gid = do uvm <- getUVManager withCBytesUnsafe path $ \ p ->
Z/IO/FileSystem/Watch.hs view
@@ -16,7 +16,7 @@ -- dup a file event source src <- srcf -- print event to stdout- runBIO_ $ src . sinkToIO printStd+ BIO.run_ $ src . sinkToIO printStd @ -} @@ -37,7 +37,6 @@ import Data.Primitive.PrimArray import Data.Word import GHC.Generics-import Z.Data.Array import Z.Data.Array.Unaligned import Z.Data.CBytes (CBytes) import qualified Z.Data.CBytes as CBytes@@ -45,8 +44,7 @@ import Z.Data.Text.Print (Print) import Z.Data.Vector (defaultChunkSize) import Z.Foreign-import Z.IO.BIO-import Z.IO.BIO.Concurrent+import Z.IO.BIO as BIO import Z.IO.Exception import Z.IO.FileSystem.Base import qualified Z.IO.FileSystem.FilePath as P@@ -65,15 +63,17 @@ -> Bool -- ^ recursively watch? -> (FileEvent -> IO ()) -- ^ Callback function to handle 'FileEvent' -> IO ()+{-# INLINABLE watchDirs #-} watchDirs dirs rec callback = do withResource (initWatchDirs dirs rec) $ \ srcf -> do src <- srcf- runBIO_ $ src . sinkToIO callback+ run_ $ src . sinkToIO callback -- | Start watching a list of given directories, stream version. initWatchDirs :: [CBytes] -- ^ watching list -> Bool -- ^ recursively watch? -> Resource (IO (Source FileEvent))+{-# INLINABLE initWatchDirs #-} initWatchDirs dirs False = do liftIO . forM_ dirs $ \ dir -> do b <- isDir dir@@ -91,11 +91,12 @@ -- Internal function to start watching watch_ :: CUInt -> [CBytes] -> Resource (IO (Source FileEvent))+{-# INLINABLE watch_ #-} watch_ flag dirs = fst <$> initResource (do -- HashMap to store all watchers mRef <- newMVar HM.empty -- there's only one place to pull the sink, that is cleanUpWatcher- (sink, srcf) <- newBroadcastTChanNode 1+ (sink, srcf) <- newBroadcastTChanPair 1 -- lock UVManager first (forM_ dirs $ \ dir -> do dir' <- P.normalize dir@@ -207,7 +208,7 @@ case me of Just e -> (Nothing, Just e) _ -> (Nothing, Nothing)- forM_ me' (stepBIO_ sink)+ forM_ me' (BIO.step_ sink) me' <- atomicModifyIORef' eRef $ \ me -> case me of@@ -215,4 +216,4 @@ then (me, Nothing) else (Just event, Just e) _ -> (Just event, Nothing)- forM_ me' (stepBIO_ sink)+ forM_ me' (BIO.step_ sink)
Z/IO/Logger.hs view
@@ -94,7 +94,7 @@ import qualified Z.Data.Builder as B import qualified Z.Data.CBytes as CB import qualified Z.Data.JSON.Builder as JB-import Z.Data.Vector.Base as V+import qualified Z.Data.Vector.Base as V import Z.IO.Buffered import Z.IO.Exception import qualified Z.IO.FileSystem as ZF@@ -144,6 +144,7 @@ -- * show everything by default -- * 'defaultFmt' defaultLoggerConfig :: LoggerConfig+{-# INLINABLE defaultLoggerConfig #-} defaultLoggerConfig = LoggerConfig 1 240 NOTSET defaultFmt -- | A default logger config with@@ -153,6 +154,7 @@ -- * show everything by default -- * 'defaultJSONFmt' defaultJSONLoggerConfig :: LoggerConfig+{-# INLINABLE defaultJSONLoggerConfig #-} defaultJSONLoggerConfig = LoggerConfig 5 1000 NOTSET defaultJSONFmt -- | A default timestamp cache with format @%Y-%m-%dT%H:%M:%S%Z@('iso8061DateFormat').@@ -171,6 +173,7 @@ newLogger :: LoggerConfig -> MVar BufferedOutput -> IO Logger+{-# INLINABLE newLogger #-} newLogger LoggerConfig{..} oLock = do logsRef <- newIORef [] let flush = flushLogIORef oLock logsRef@@ -186,12 +189,14 @@ -- | Make a new logger write to 'stderrBuf'. newStdLogger :: LoggerConfig -> IO Logger+{-# INLINABLE newStdLogger #-} newStdLogger config = newLogger config stderrBuf -- | Make a new file based logger with 'defaultFmt'. -- -- The file will be opened in append mode. newFileLogger :: LoggerConfig -> CB.CBytes -> IO Logger+{-# INLINABLE newFileLogger #-} newFileLogger config path = do let res = ZF.initFile path (ZF.O_CREAT .|. ZF.O_RDWR .|. ZF.O_APPEND) ZF.DEFAULT_FILE_MODE (file, _closeFunc) <- acquire res@@ -215,11 +220,13 @@ -> Int -- ^ buffer size to build each log -> B.Builder () -- ^ formatted log -> IO ()+{-# INLINABLE pushLogIORef #-} pushLogIORef logsRef loggerLineBufSize b = do let !bs = B.buildWith loggerLineBufSize b unless (V.null bs) $ atomicModifyIORef' logsRef (\ bss -> (bs:bss, ())) flushLogIORef :: HasCallStack => MVar BufferedOutput -> IORef [V.Bytes] -> IO ()+{-# INLINABLE flushLogIORef #-} flushLogIORef oLock logsRef = withMVar oLock $ \ o -> do bss <- atomicModifyIORef' logsRef (\ bss -> ([], bss))@@ -230,6 +237,7 @@ -- -- @[FATAL][2021-02-01T15:03:30+0800][<interactive>:31:1][thread#669]...@ defaultFmt :: LogFormatter+{-# INLINABLE defaultFmt #-} defaultFmt ts level content cstack (ThreadId tid#) = do B.square (defaultLevelFmt level) B.square ts@@ -242,6 +250,7 @@ -- -- DEBUG level is 'Cyan', WARNING level is 'Yellow', FATAL and CRITICAL level are 'Red'. defaultColoredFmt :: LogFormatter+{-# INLINABLE defaultColoredFmt #-} defaultColoredFmt ts level content cstack (ThreadId tid#) = do let blevel = defaultLevelFmt level B.square (case level of@@ -260,6 +269,7 @@ -- -- > {"level":"FATAL","time":"2021-02-01T15:02:19+0800","loc":"<interactive>:27:1","theadId":606,"content":"..."}\n defaultJSONFmt :: LogFormatter+{-# INLINABLE defaultJSONFmt #-} defaultJSONFmt ts level content cstack (ThreadId tid#) = do B.curly $ do "level" `JB.kv` B.quotes (defaultLevelFmt level)@@ -275,6 +285,7 @@ -- | Default stack formatter which fetch the logging source and location. defaultFmtCallStack :: CallStack -> B.Builder ()+{-# INLINABLE defaultFmtCallStack #-} defaultFmtCallStack cs = case reverse $ getCallStack cs of [] -> "<no call stack found>"@@ -294,6 +305,7 @@ -- | Change the global logger. setDefaultLogger :: Logger -> IO ()+{-# INLINABLE setDefaultLogger #-} setDefaultLogger !logger = atomicWriteIORef globalLogger logger -- | Get the global logger.@@ -301,16 +313,19 @@ -- This is a logger connected to stderr, if stderr is connect to TTY, -- then use 'defaultColoredFmt', otherwise use 'defaultFmt'. getDefaultLogger :: IO Logger+{-# INLINABLE getDefaultLogger #-} getDefaultLogger = readIORef globalLogger -- | Manually flush global logger. flushDefaultLogger :: IO ()+{-# INLINABLE flushDefaultLogger #-} flushDefaultLogger = do (Logger _ flush) <- getDefaultLogger flush -- | Flush global logger when program exits. withDefaultLogger :: IO () -> IO ()+{-# INLINABLE withDefaultLogger #-} withDefaultLogger = (`finally` flushDefaultLogger) --------------------------------------------------------------------------------@@ -361,7 +376,7 @@ -- Level other than built-in ones, are formatted in decimal numeric format, i.e. -- @defaultLevelFmt 60 == "LEVEL60"@ defaultLevelFmt :: Level -> B.Builder ()-{-# INLINE defaultLevelFmt #-}+{-# INLINABLE defaultLevelFmt #-} defaultLevelFmt level = case level of CRITICAL -> "CRITICAL" FATAL -> "FATAL"@@ -372,23 +387,23 @@ level' -> "LEVEL" >> B.int level' debug :: HasCallStack => B.Builder () -> IO ()-{-# INLINE debug #-}+{-# INLINABLE debug #-} debug = otherLevel_ DEBUG False callStack info :: HasCallStack => B.Builder () -> IO ()-{-# INLINE info #-}+{-# INLINABLE info #-} info = otherLevel_ INFO False callStack warning :: HasCallStack => B.Builder () -> IO ()-{-# INLINE warning #-}+{-# INLINABLE warning #-} warning = otherLevel_ WARNING False callStack fatal :: HasCallStack => B.Builder () -> IO ()-{-# INLINE fatal #-}+{-# INLINABLE fatal #-} fatal = otherLevel_ FATAL True callStack critical :: HasCallStack => B.Builder () -> IO ()-{-# INLINE critical #-}+{-# INLINABLE critical #-} critical = otherLevel_ CRITICAL True callStack otherLevel :: HasCallStack@@ -396,11 +411,11 @@ -> Bool -- ^ flush immediately? -> B.Builder () -- ^ log content -> IO ()-{-# INLINE otherLevel #-}+{-# INLINABLE otherLevel #-} otherLevel level flushNow bu = otherLevel_ level flushNow callStack bu otherLevel_ :: Level -> Bool -> CallStack -> B.Builder () -> IO ()-{-# INLINE otherLevel_ #-}+{-# INLINABLE otherLevel_ #-} otherLevel_ level flushNow cstack bu = do (Logger f _) <- getDefaultLogger f level flushNow cstack bu@@ -408,19 +423,19 @@ -------------------------------------------------------------------------------- debugTo :: HasCallStack => Logger -> B.Builder () -> IO ()-{-# INLINE debugTo #-}+{-# INLINABLE debugTo #-} debugTo = otherLevelTo_ DEBUG False callStack infoTo :: HasCallStack => Logger -> B.Builder () -> IO ()-{-# INLINE infoTo #-}+{-# INLINABLE infoTo #-} infoTo = otherLevelTo_ INFO False callStack warningTo :: HasCallStack => Logger -> B.Builder () -> IO ()-{-# INLINE warningTo #-}+{-# INLINABLE warningTo #-} warningTo = otherLevelTo_ WARNING False callStack fatalTo :: HasCallStack => Logger -> B.Builder () -> IO ()-{-# INLINE fatalTo #-}+{-# INLINABLE fatalTo #-} fatalTo = otherLevelTo_ FATAL True callStack otherLevelTo :: HasCallStack@@ -429,11 +444,11 @@ -> Bool -- ^ flush immediately? -> B.Builder () -- ^ log content -> IO ()-{-# INLINE otherLevelTo #-}+{-# INLINABLE otherLevelTo #-} otherLevelTo logger level flushNow = otherLevelTo_ level flushNow callStack logger otherLevelTo_ :: Level -> Bool -> CallStack -> Logger -> B.Builder () -> IO ()-{-# INLINE otherLevelTo_ #-}+{-# INLINABLE otherLevelTo_ #-} otherLevelTo_ level flushNow cs (Logger f _) = f level flushNow cs foreign import ccall unsafe "rts_getThreadId" getThreadId :: ThreadId# -> CInt
Z/IO/LowResTimer.hs view
@@ -49,12 +49,13 @@ import Data.IORef import GHC.Conc import System.IO.Unsafe-import Z.Data.PrimRef.PrimIORef+import Z.Data.PrimRef import Z.IO.Exception import Z.IO.UV.FFI_Env (uv_hrtime) -- queueSize :: Int+{-# INLINABLE queueSize #-} queueSize = 128 -- | A simple timing wheel@@ -72,6 +73,7 @@ -- running lock newLowResTimerManager :: IO LowResTimerManager+{-# INLINABLE newLowResTimerManager #-} newLowResTimerManager = do indexLock <- newMVar 0 regCounter <- newCounter 0@@ -100,6 +102,7 @@ -- This is not a must though, when we fetch timer manager we always take a modulo. -- lowResTimerManagerCapabilitiesChanged :: IO ()+{-# INLINABLE lowResTimerManagerCapabilitiesChanged #-} lowResTimerManagerCapabilitiesChanged = do lrtmArray <- readIORef lowResTimerManager let oldSize = sizeofArr lrtmArray@@ -193,7 +196,7 @@ -- queryLowResTimer :: LowResTimer -> IO Int {-# INLINABLE queryLowResTimer #-}-queryLowResTimer (LowResTimer c) = readPrimIORef c+queryLowResTimer (LowResTimer c) = readPrimRef c -- | Cancel a timer, return the remaining ticks. --@@ -280,9 +283,10 @@ -- | Start low resolution timer loop, the loop is automatically stopped if there's no more new registrations. -- startLowResTimerManager :: LowResTimerManager -> Word64 -> IO ()+{-# INLINABLE startLowResTimerManager #-} startLowResTimerManager lrtm@(LowResTimerManager _ _ regCounter runningLock) !stdT = do modifyMVar_ runningLock $ \ _ -> do -- we shouldn't receive async exception here- c <- readPrimIORef regCounter -- unless something terribly wrong happened, e.g., stackoverflow+ c <- readPrimRef regCounter -- unless something terribly wrong happened, e.g., stackoverflow if c > 0 then do t <- uv_hrtime@@ -311,6 +315,7 @@ -- | Scan the timeout queue in current tick index, and move tick index forward by one. -- fireLowResTimerQueue :: LowResTimerManager -> IO ()+{-# INLINABLE fireLowResTimerQueue #-} fireLowResTimerQueue (LowResTimerManager queue indexLock regCounter _) = do (tList, tListRef) <- modifyMVar indexLock $ \ index -> do -- get the index lock tListRef <- indexArrM queue index
Z/IO/Network/DNS.hsc view
@@ -118,6 +118,7 @@ -- | Indicate whether the given 'AddrInfoFlag' will have any effect on this system. addrInfoFlagImplemented :: AddrInfoFlag -> Bool+{-# INLINABLE addrInfoFlagImplemented #-} addrInfoFlagImplemented f = packBits addrInfoFlagMapping [f] /= 0 -- | Address info@@ -133,9 +134,12 @@ instance Storable AddrInfo where+ {-# INLINABLE sizeOf #-} sizeOf _ = #const sizeof(struct addrinfo)+ {-# INLINABLE alignment #-} alignment _ = alignment (0 :: CInt) + {-# INLINABLE peek #-} peek p = do ai_flags <- (#peek struct addrinfo, ai_flags) p ai_family <- (#peek struct addrinfo, ai_family) p@@ -154,6 +158,7 @@ , addrCanonName = ai_canonname } + {-# INLINABLE poke #-} poke p (AddrInfo flags family sockType protocol _ _) = do (#poke struct addrinfo, ai_flags) p (packBits addrInfoFlagMapping flags) (#poke struct addrinfo, ai_family) p family@@ -190,7 +195,7 @@ deriving (Eq, Read, Show) nameInfoFlagMapping :: [(NameInfoFlag, CInt)]-+{-# INLINABLE nameInfoFlagMapping #-} nameInfoFlagMapping = [(NI_DGRAM, #const NI_DGRAM), (NI_NAMEREQD, #const NI_NAMEREQD), (NI_NOFQDN, #const NI_NOFQDN),@@ -209,6 +214,7 @@ -- 0 defaultHints :: AddrInfo+{-# INLINABLE defaultHints #-} defaultHints = AddrInfo { addrFlags = [] , addrFamily = AF_UNSPEC@@ -263,6 +269,7 @@ -> HostName -- ^ host name to look up -> ServiceName -- ^ service name to look up -> IO [AddrInfo] -- ^ resolved addresses, with "best" first+{-# INLINABLE getAddrInfo #-} getAddrInfo hints host service = withUVInitDo $ bracket (do withCBytes host $ \ ptr_h ->@@ -286,6 +293,7 @@ -- | Peek @addrinfo@ linked list. -- followAddrInfo :: Ptr AddrInfo -> IO [AddrInfo]+{-# INLINABLE followAddrInfo #-} followAddrInfo ptr_ai | ptr_ai == nullPtr = return [] | otherwise = do@@ -318,6 +326,7 @@ -> Bool -- ^ whether to look up a service name -> SocketAddr -- ^ the address to look up -> IO (HostName, ServiceName)+{-# INLINABLE getNameInfo #-} getNameInfo flags doHost doService addr = withUVInitDo $ do (host, (service, _)) <- allocCBytes (fromIntegral h_len) $ \ ptr_h -> allocCBytes (fromIntegral s_len) $ \ ptr_s ->@@ -338,7 +347,7 @@ -- break if this property is not true. -- packBits :: (Eq a, Num b, Bits b) => [(a, b)] -> [a] -> b-{-# INLINE packBits #-}+{-# INLINABLE packBits #-} packBits mapping xs = List.foldl' go 0 mapping where go acc (k, v) | k `elem` xs = acc .|. v@@ -346,7 +355,7 @@ -- | Unpack a bitmask into a list of values. unpackBits :: (Num b, Bits b) => [(a, b)] -> b -> [a]-{-# INLINE unpackBits #-}+{-# INLINABLE unpackBits #-} -- Be permissive and ignore unknown bit values. At least on OS X, -- getaddrinfo returns an ai_flags field with bits set that have no -- entry in <netdb.h>.
Z/IO/Network/IPC.hs view
@@ -59,11 +59,13 @@ -- | Default config, connect to ".\/ipc". -- defaultIPCClientConfig :: IPCClientConfig+{-# INLINABLE defaultIPCClientConfig #-} defaultIPCClientConfig = IPCClientConfig Nothing "./ipc" -- | init a IPC client 'Resource', which open a new connect when used. -- initIPCClient :: IPCClientConfig -> Resource UVStream+{-# INLINABLE initIPCClient #-} initIPCClient (IPCClientConfig cname tname) = do uvm <- liftIO getUVManager client <- initIPCStream uvm@@ -92,6 +94,7 @@ -- Test it with @main = startIPCServer defaultIPCServerConfig@ -- defaultIPCServerConfig :: IPCServerConfig+{-# INLINABLE defaultIPCServerConfig #-} defaultIPCServerConfig = IPCServerConfig "./ipc" 256@@ -106,6 +109,7 @@ -- run in a seperated haskell thread, -- will be closed upon exception or worker finishes. -> IO ()+{-# INLINABLE startIPCServer #-} startIPCServer IPCServerConfig{..} = startServerLoop (max ipcListenBacklog 128) initIPCStream@@ -122,5 +126,6 @@ -------------------------------------------------------------------------------- initIPCStream :: HasCallStack => UVManager -> Resource UVStream+{-# INLINABLE initIPCStream #-} initIPCStream = initUVStream (\ loop hdl -> throwUVIfMinus_ (uv_pipe_init loop hdl 0))
Z/IO/Network/SocketAddr.hsc view
@@ -205,6 +205,7 @@ T.toUTF8Builder port sockAddrFamily :: SocketAddr -> SocketFamily+{-# INLINABLE sockAddrFamily #-} sockAddrFamily (SocketAddrIPv4 _ _) = AF_INET sockAddrFamily (SocketAddrIPv6 _ _ _ _) = AF_INET6 @@ -215,6 +216,7 @@ -- -- This is partial function, wrong address will throw 'InvalidArgument' exception. ipv4:: HasCallStack => CBytes -> PortNumber -> SocketAddr+{-# INLINABLE ipv4 #-} ipv4 str (PortNumber port) = unsafeDupablePerformIO . withSocketAddrStorageUnsafe $ \ p -> withCBytesUnsafe str $ \ cstr -> throwUVIfMinus_ $ uv_ip4_addr cstr (fromIntegral port) p @@ -222,6 +224,7 @@ -- -- This is partial function, wrong address will throw 'InvalidArgument' exception. ipv6:: HasCallStack => CBytes -> PortNumber -> SocketAddr+{-# INLINABLE ipv6 #-} ipv6 str (PortNumber port) = unsafeDupablePerformIO . withSocketAddrStorageUnsafe $ \ p -> withCBytesUnsafe str $ \ cstr -> throwUVIfMinus_ $ uv_ip6_addr cstr (fromIntegral port) p @@ -257,54 +260,71 @@ -- | @0.0.0.0@ ipv4Any :: IPv4+{-# INLINABLE ipv4Any #-} ipv4Any = IPv4 0 -- | @255.255.255.255@ ipv4Broadcast :: IPv4+{-# INLINABLE ipv4Broadcast #-} ipv4Broadcast = tupleToIPv4Addr (255,255,255,255) -- | @255.255.255.255@ ipv4None :: IPv4+{-# INLINABLE ipv4None #-} ipv4None = tupleToIPv4Addr (255,255,255,255) -- | @127.0.0.1@ ipv4Loopback :: IPv4+{-# INLINABLE ipv4Loopback #-} ipv4Loopback = tupleToIPv4Addr (127, 0, 0, 1) -- | @224.0.0.0@ ipv4UnspecificGroup :: IPv4+{-# INLINABLE ipv4UnspecificGroup #-} ipv4UnspecificGroup = tupleToIPv4Addr (224, 0, 0, 0) -- | @224.0.0.1@ ipv4AllHostsGroup :: IPv4+{-# INLINABLE ipv4AllHostsGroup #-} ipv4AllHostsGroup = tupleToIPv4Addr (224, 0, 0, 1) -- | @224.0.0.255@ ipv4MaxLocalGroup :: IPv4+{-# INLINABLE ipv4MaxLocalGroup #-} ipv4MaxLocalGroup = tupleToIPv4Addr (224, 0, 0,255) instance Storable IPv4 where+ {-# INLINABLE sizeOf #-} sizeOf _ = 4+ {-# INLINABLE alignment #-} alignment _ = alignment (undefined :: Word32)+ {-# INLINABLE peek #-} peek p = (IPv4 . ntohl) `fmap` peekByteOff p 0+ {-# INLINABLE poke #-} poke p (IPv4 ia) = pokeByteOff p 0 (htonl ia) instance Unaligned IPv4 where+ {-# INLINABLE unalignedSize #-} unalignedSize = 4+ {-# INLINABLE pokeMBA #-} pokeMBA p off x = pokeMBA p off (htonl (getIPv4Addr x))+ {-# INLINABLE peekMBA #-} peekMBA p off = IPv4 . ntohl <$> peekMBA p off+ {-# INLINABLE indexBA #-} indexBA p off = IPv4 (ntohl (indexBA p off)) -- | Converts 'IPv4' to representation-independent IPv4 quadruple. -- For example for @127.0.0.1@ the function will return @(127, 0, 0, 1)@ -- regardless of host endianness. ipv4AddrToTuple :: IPv4 -> (Word8, Word8, Word8, Word8)+{-# INLINE ipv4AddrToTuple #-} ipv4AddrToTuple (IPv4 ia) = let byte i = fromIntegral (ia `shiftR` i) :: Word8 in (byte 24, byte 16, byte 8, byte 0) -- | Converts IPv4 quadruple to 'IPv4'. tupleToIPv4Addr :: (Word8, Word8, Word8, Word8) -> IPv4+{-# INLINE tupleToIPv4Addr #-} tupleToIPv4Addr (b3, b2, b1, b0) = let x `sl` i = fromIntegral x `shiftL` i :: Word32 in IPv4 $ (b3 `sl` 24) .|. (b2 `sl` 16) .|. (b1 `sl` 8) .|. (b0 `sl` 0)@@ -336,6 +356,7 @@ instance Show IPv6 where show = T.toString instance Print IPv6 where+ {-# INLINABLE toUTF8BuilderP #-} toUTF8BuilderP _ ia6@(IPv6 a1 a2 a3 a4) -- IPv4-Mapped IPv6 Address | a1 == 0 && a2 == 0 && a3 == 0xffff =@@ -361,15 +382,18 @@ -- | @::@ ipv6Any :: IPv6+{-# INLINABLE ipv6Any #-} ipv6Any = IPv6 0 0 0 0 -- | @::1@ ipv6Loopback :: IPv6+{-# INLINABLE ipv6Loopback #-} ipv6Loopback = IPv6 0 0 0 1 -- | convert 'IPv6' to octets. ipv6AddrToTuple :: IPv6 -> (Word16, Word16, Word16, Word16, Word16, Word16, Word16, Word16)+{-# INLINABLE ipv6AddrToTuple #-} ipv6AddrToTuple (IPv6 w3 w2 w1 w0) = let high, low :: Word32 -> Word16 high w = fromIntegral (w `shiftR` 16)@@ -379,20 +403,25 @@ -- | convert 'IPv6' from octets. tupleToIPv6Addr :: (Word16, Word16, Word16, Word16, Word16, Word16, Word16, Word16) -> IPv6+{-# INLINABLE tupleToIPv6Addr #-} tupleToIPv6Addr (w7, w6, w5, w4, w3, w2, w1, w0) = let add :: Word16 -> Word16 -> Word32 high `add` low = (fromIntegral high `shiftL` 16) .|. (fromIntegral low) in IPv6 (w7 `add` w6) (w5 `add` w4) (w3 `add` w2) (w1 `add` w0) instance Storable IPv6 where+ {-# INLINABLE sizeOf #-} sizeOf _ = #size struct in6_addr+ {-# INLINABLE alignment #-} alignment _ = #alignment struct in6_addr+ {-# INLINABLE peek #-} peek p = do a <- peek32 p 0 b <- peek32 p 1 c <- peek32 p 2 d <- peek32 p 3 return $ IPv6 a b c d+ {-# INLINABLE poke #-} poke p (IPv6 a b c d) = do poke32 p 0 a poke32 p 1 b@@ -401,9 +430,11 @@ s6_addr_offset :: Int+{-# INLINABLE s6_addr_offset #-} s6_addr_offset = (#offset struct in6_addr, s6_addr) peek32 :: Ptr a -> Int -> IO Word32+{-# INLINABLE peek32 #-} peek32 p i0 = do let i' = i0 * 4 peekByte n = peekByteOff p (s6_addr_offset + i' + n) :: IO Word8@@ -415,6 +446,7 @@ return ((a0 `sl` 24) .|. (a1 `sl` 16) .|. (a2 `sl` 8) .|. (a3 `sl` 0)) poke32 :: Ptr a -> Int -> Word32 -> IO ()+{-# INLINABLE poke32 #-} poke32 p i0 a = do let i' = i0 * 4 pokeByte n = pokeByteOff p (s6_addr_offset + i' + n)@@ -425,22 +457,23 @@ pokeByte 3 (a `sr` 0) instance Unaligned IPv6 where+ {-# INLINABLE unalignedSize #-} unalignedSize = (#size struct in6_addr)-+ {-# INLINABLE indexBA #-} indexBA p off = let a = indexBA p (off + s6_addr_offset + 0) b = indexBA p (off + s6_addr_offset + 4) c = indexBA p (off + s6_addr_offset + 8) d = indexBA p (off + s6_addr_offset + 12) in IPv6 (getBE a) (getBE b) (getBE c) (getBE d)-+ {-# INLINABLE peekMBA #-} peekMBA p off = do a <- peekMBA p (off + s6_addr_offset + 0) b <- peekMBA p (off + s6_addr_offset + 4) c <- peekMBA p (off + s6_addr_offset + 8) d <- peekMBA p (off + s6_addr_offset + 12) return $ IPv6 (getBE a) (getBE b) (getBE c) (getBE d)-+ {-# INLINABLE pokeMBA #-} pokeMBA p off (IPv6 a b c d) = do pokeMBA p (off + s6_addr_offset) (BE a) pokeMBA p (off + 4 + s6_addr_offset) (BE b)@@ -450,6 +483,7 @@ -------------------------------------------------------------------------------- peekSocketAddr :: HasCallStack => Ptr SocketAddr -> IO SocketAddr+{-# INLINABLE peekSocketAddr #-} peekSocketAddr p = do family <- (#peek struct sockaddr, sa_family) p case family :: CSaFamily of@@ -469,6 +503,7 @@ throwUVError errno (IOEInfo name desc callStack) pokeSocketAddr :: Ptr SocketAddr -> SocketAddr -> IO ()+{-# INLINABLE pokeSocketAddr #-} pokeSocketAddr p (SocketAddrIPv4 addr port) = do #if defined(darwin_HOST_OS) clearPtr p (#size struct sockaddr_in)@@ -496,6 +531,7 @@ -- | Pass 'SocketAddr' to FFI as pointer. -- withSocketAddr :: SocketAddr -> (Ptr SocketAddr -> IO a) -> IO a+{-# INLINABLE withSocketAddr #-} withSocketAddr sa@(SocketAddrIPv4 _ _) f = do allocaBytesAligned (#size struct sockaddr_in)@@ -510,6 +546,7 @@ -- USE THIS FUNCTION WITH UNSAFE FFI CALL ONLY. -- withSocketAddrUnsafe :: SocketAddr -> (MBA## SocketAddr -> IO a) -> IO a+{-# INLINABLE withSocketAddrUnsafe #-} withSocketAddrUnsafe sa@(SocketAddrIPv4 _ _) f = do (MutableByteArray p) <- newByteArray (#size struct sockaddr_in) pokeSocketAddrMBA p sa@@ -520,11 +557,13 @@ f p sizeOfSocketAddr :: SocketAddr -> CSize+{-# INLINABLE sizeOfSocketAddr #-} sizeOfSocketAddr (SocketAddrIPv4 _ _) = #size struct sockaddr_in sizeOfSocketAddr (SocketAddrIPv6 _ _ _ _) = #size struct sockaddr_in6 -- | Allocate space for 'sockaddr_storage' and pass to FFI. withSocketAddrStorage :: (Ptr SocketAddr -> IO ()) -> IO SocketAddr+{-# INLINABLE withSocketAddrStorage #-} withSocketAddrStorage f = do allocaBytesAligned (#size struct sockaddr_storage)@@ -535,15 +574,18 @@ -- USE THIS FUNCTION WITH UNSAFE FFI CALL ONLY. -- withSocketAddrStorageUnsafe :: (MBA## SocketAddr -> IO ()) -> IO SocketAddr+{-# INLINABLE withSocketAddrStorageUnsafe #-} withSocketAddrStorageUnsafe f = do (MutableByteArray p) <- newByteArray (#size struct sockaddr_storage) f p peekSocketAddrMBA p sizeOfSocketAddrStorage :: CSize+{-# INLINABLE sizeOfSocketAddrStorage #-} sizeOfSocketAddrStorage = (#size struct sockaddr_storage) peekSocketAddrMBA :: HasCallStack => MBA## SocketAddr -> IO SocketAddr+{-# INLINABLE peekSocketAddrMBA #-} peekSocketAddrMBA p = do family <- peekMBA p (#offset struct sockaddr, sa_family) case family :: CSaFamily of@@ -563,6 +605,7 @@ throwUVError errno (IOEInfo name desc callStack) pokeSocketAddrMBA :: MBA## SocketAddr -> SocketAddr -> IO ()+{-# INLINABLE pokeSocketAddrMBA #-} pokeSocketAddrMBA p (SocketAddrIPv4 addr port) = do #if defined(darwin_HOST_OS) clearMBA p (#size struct sockaddr_in)@@ -602,6 +645,7 @@ instance Show Interface where show = T.toString peekInterface :: HasCallStack => Ptr Interface -> IO Interface+{-# INLINABLE peekInterface #-} peekInterface p = do name' <- fromCString =<< (#peek struct uv_interface_address_s, name) p phy <- fromPtr (p `plusPtr` (#offset struct uv_interface_address_s, phys_addr)) 6@@ -649,6 +693,7 @@ -- | Gets address information about the network interfaces on the system. getInterface :: IO [Interface]+{-# INLINABLE getInterface #-} getInterface = bracket (allocPrimUnsafe @(Ptr Interface) $ \ ppiface -> fst <$> allocPrimUnsafe @CInt (uv_interface_addresses ppiface))@@ -681,37 +726,52 @@ -- | @:0@ portAny :: PortNumber+{-# INLINABLE portAny #-} portAny = PortNumber 0 defaultPortNumberHTTP :: PortNumber+{-# INLINABLE defaultPortNumberHTTP #-} defaultPortNumberHTTP = 80 defaultPortNumberHTTPS :: PortNumber+{-# INLINABLE defaultPortNumberHTTPS #-} defaultPortNumberHTTPS = 443 defaultPortNumberSMTP :: PortNumber+{-# INLINABLE defaultPortNumberSMTP #-} defaultPortNumberSMTP = 25 defaultPortNumberPOP3 :: PortNumber+{-# INLINABLE defaultPortNumberPOP3 #-} defaultPortNumberPOP3 = 110 defaultPortNumberIMAP :: PortNumber+{-# INLINABLE defaultPortNumberIMAP #-} defaultPortNumberIMAP = 143 defaultPortNumberIRC :: PortNumber+{-# INLINABLE defaultPortNumberIRC #-} defaultPortNumberIRC = 194 instance Storable PortNumber where- sizeOf _ = sizeOf (0 :: Word16)- alignment _ = alignment (0 :: Word16)- poke p (PortNumber po) = poke (castPtr p) (htons po)- peek p = PortNumber . ntohs <$> peek (castPtr p)+ {-# INLINABLE sizeOf #-}+ sizeOf _ = sizeOf (0 :: Word16)+ {-# INLINABLE alignment #-}+ alignment _ = alignment (0 :: Word16)+ {-# INLINABLE poke #-}+ poke p (PortNumber po) = poke (castPtr p) (htons po)+ {-# INLINABLE peek #-}+ peek p = PortNumber . ntohs <$> peek (castPtr p) instance Unaligned PortNumber where- unalignedSize = 2- indexBA p off = PortNumber . ntohs $ indexBA p off- pokeMBA p off (PortNumber po) = pokeMBA p off (htons po)- peekMBA p off = PortNumber . ntohs <$> peekMBA p off+ {-# INLINABLE unalignedSize #-}+ unalignedSize = 2+ {-# INLINABLE indexBA #-}+ indexBA p off = PortNumber . ntohs $ indexBA p off+ {-# INLINABLE pokeMBA #-}+ pokeMBA p off (PortNumber po) = pokeMBA p off (htons po)+ {-# INLINABLE peekMBA #-}+ peekMBA p off = PortNumber . ntohs <$> peekMBA p off --------------------------------------------------------------------------------
Z/IO/Network/TCP.hs view
@@ -64,11 +64,13 @@ -- | Default config, connect to @localhost:8888@. -- defaultTCPClientConfig :: TCPClientConfig+{-# INLINABLE defaultTCPClientConfig #-} defaultTCPClientConfig = TCPClientConfig Nothing (SocketAddrIPv4 ipv4Loopback 8888) True 30 -- | init a TCP client 'Resource', which open a new connect when used. -- initTCPClient :: HasCallStack => TCPClientConfig -> Resource UVStream+{-# INLINABLE initTCPClient #-} initTCPClient TCPClientConfig{..} = do uvm <- liftIO getUVManager client <- initTCPStream uvm@@ -104,6 +106,7 @@ -- @main = startTCPServer defaultTCPServerConfig echoWorker@, now try @nc -v 127.0.0.1 8888@ -- defaultTCPServerConfig :: TCPServerConfig+{-# INLINABLE defaultTCPServerConfig #-} defaultTCPServerConfig = TCPServerConfig (SocketAddrIPv4 ipv4Any 8888) 256@@ -120,6 +123,7 @@ -- run in a seperated haskell thread, -- will be closed upon exception or worker finishes. -> IO ()+{-# INLINABLE startTCPServer #-} startTCPServer TCPServerConfig{..} = startServerLoop (max tcpListenBacklog 128) initTCPStream@@ -224,10 +228,12 @@ -------------------------------------------------------------------------------- initTCPStream :: UVManager -> Resource UVStream+{-# INLINABLE initTCPStream #-} initTCPStream = initUVStream (\ loop hdl -> throwUVIfMinus_ (uv_tcp_init loop hdl)) -- | Enable or disable @TCP_NODELAY@, which enable or disable Nagle’s algorithm. setTCPNoDelay :: HasCallStack => UVStream -> Bool -> IO ()+{-# INLINABLE setTCPNoDelay #-} setTCPNoDelay uvs nodelay = throwUVIfMinus_ (uv_tcp_nodelay (uvsHandle uvs) (if nodelay then 1 else 0)) @@ -237,12 +243,14 @@ -- will still happen. If the connection is still lost at the end of this procedure, -- then the connection is closed, pending io thread will throw 'TimeExpired' exception. setTCPKeepAlive :: HasCallStack => UVStream -> CUInt -> IO ()+{-# INLINABLE setTCPKeepAlive #-} setTCPKeepAlive uvs delay | delay > 0 = throwUVIfMinus_ (uv_tcp_keepalive (uvsHandle uvs) 1 delay) | otherwise = throwUVIfMinus_ (uv_tcp_keepalive (uvsHandle uvs) 0 0) -- | Get the current address to which the handle is bound. getTCPSockName :: HasCallStack => UVStream -> IO SocketAddr+{-# INLINABLE getTCPSockName #-} getTCPSockName uvs = do withSocketAddrStorageUnsafe $ \ paddr -> void $ withPrimUnsafe (fromIntegral sizeOfSocketAddrStorage :: CInt) $ \ plen ->@@ -250,6 +258,7 @@ -- | Get the address of the peer connected to the handle. getTCPPeerName :: HasCallStack => UVStream -> IO SocketAddr+{-# INLINABLE getTCPPeerName #-} getTCPPeerName uvs = do withSocketAddrStorageUnsafe $ \ paddr -> void $ withPrimUnsafe (fromIntegral sizeOfSocketAddrStorage :: CInt) $ \ plen ->
Z/IO/Network/UDP.hs view
@@ -59,7 +59,7 @@ import Control.Concurrent import Control.Monad-import Data.Primitive.PrimArray as A+import qualified Data.Primitive.PrimArray as A import Data.IORef import Data.Word import Data.Int@@ -68,12 +68,11 @@ import Foreign.Ptr (plusPtr) import Foreign.C import GHC.Generics-import Z.Data.Array as A-import Z.Data.Vector.Base as V-import Z.Data.Vector.Extra as V-import Z.Data.CBytes as CBytes-import qualified Z.Data.Text.Print as T-import Z.Data.JSON (JSON)+import Z.Data.CBytes+import qualified Z.Data.Vector.Base as V+import qualified Z.Data.Vector.Extra as V+import qualified Z.Data.Text.Print as T+import Z.Data.JSON (JSON) import Z.IO.Network.SocketAddr import Z.Foreign import Z.IO.UV.FFI@@ -97,6 +96,7 @@ instance Show UDP where show = T.toString instance T.Print UDP where+ {-# INLINABLE toUTF8BuilderP #-} toUTF8BuilderP _ (UDP hdl slot uvm _ _) = do "UDP{udpHandle=" >> T.toUTF8Builder hdl ",udpSlot=" >> T.toUTF8Builder slot@@ -117,11 +117,13 @@ -- | @UDPConfig 512 Nothing@ defaultUDPConfig :: UDPConfig+{-# INLINABLE defaultUDPConfig #-} defaultUDPConfig = UDPConfig 512 Nothing -- | Initialize a UDP socket. -- initUDP :: UDPConfig -> Resource UDP+{-# INLINABLE initUDP #-} initUDP (UDPConfig sbsiz maddr) = initResource (do uvm <- getUVManager (hdl, slot) <- withUVManager uvm $ \ loop -> do@@ -146,12 +148,14 @@ unless c $ writeIORef closed True >> hs_uv_handle_close hdl) checkUDPClosed :: HasCallStack => UDP -> IO ()+{-# INLINABLE checkUDPClosed #-} checkUDPClosed udp = do c <- readIORef (udpClosed udp) when c throwECLOSED -- | Get the local IP and port of the 'UDP'. getSockName :: HasCallStack => UDP -> IO SocketAddr+{-# INLINABLE getSockName #-} getSockName udp@(UDP hdl _ _ _ _) = do checkUDPClosed udp withSocketAddrStorageUnsafe $ \ paddr ->@@ -172,6 +176,7 @@ -- | Associate the UDP handle to a remote address and port, -- so every message sent by this handle is automatically sent to that destination connectUDP :: HasCallStack => UDP -> SocketAddr -> IO ConnectedUDP+{-# INLINABLE connectUDP #-} connectUDP udp@(UDP hdl _ _ _ _) addr = do checkUDPClosed udp withSocketAddrUnsafe addr $ \ paddr ->@@ -180,6 +185,7 @@ -- | Disconnect the UDP handle from a remote address and port. disconnectUDP :: HasCallStack => ConnectedUDP -> IO UDP+{-# INLINABLE disconnectUDP #-} disconnectUDP (ConnectedUDP udp@(UDP hdl _ _ _ _)) = do checkUDPClosed udp throwUVIfMinus_ (uv_udp_disconnect hdl nullPtr)@@ -187,6 +193,7 @@ -- | Get the remote IP and port on 'ConnectedUDP'. getPeerName :: HasCallStack => ConnectedUDP -> IO SocketAddr+{-# INLINABLE getPeerName #-} getPeerName (ConnectedUDP udp@(UDP hdl _ _ _ _)) = do checkUDPClosed udp withSocketAddrStorageUnsafe $ \ paddr ->@@ -198,6 +205,7 @@ -- WARNING: A 'InvalidArgument' with errno 'UV_EMSGSIZE' will be thrown -- if message is larger than 'sendMsgSize'. sendConnectedUDP :: HasCallStack => ConnectedUDP -> V.Bytes -> IO ()+{-# INLINABLE sendConnectedUDP #-} sendConnectedUDP (ConnectedUDP udp@(UDP hdl _ uvm sbuf _)) (V.PrimVector ba s la) = mask_ $ do checkUDPClosed udp -- copy message to pinned buffer@@ -224,6 +232,7 @@ -- WARNING: A 'InvalidArgument' with errno 'UV_EMSGSIZE' will be thrown -- if message is larger than 'sendMsgSize'. sendUDP :: HasCallStack => UDP -> SocketAddr -> V.Bytes -> IO ()+{-# INLINABLE sendUDP #-} sendUDP udp@(UDP hdl _ uvm sbuf _) addr (V.PrimVector ba s la) = mask_ $ do checkUDPClosed udp -- copy message to pinned buffer@@ -248,12 +257,14 @@ -- | Set IP multicast loop flag. Makes multicast packets loop back to local sockets. setMulticastLoop :: HasCallStack => UDP -> Bool -> IO ()+{-# INLINABLE setMulticastLoop #-} setMulticastLoop udp@(UDP hdl _ _ _ _) loop = do checkUDPClosed udp throwUVIfMinus_ (uv_udp_set_multicast_loop hdl (if loop then 1 else 0)) -- | Set the multicast ttl. setMulticastTTL :: HasCallStack => UDP -> Int -> IO ()+{-# INLINABLE setMulticastTTL #-} setMulticastTTL udp@(UDP hdl _ _ _ _) ttl = do checkUDPClosed udp throwUVIfMinus_ (uv_udp_set_multicast_ttl hdl (fromIntegral ttl'))@@ -261,6 +272,7 @@ -- | Set the multicast interface to send or receive data on. setMulticastInterface :: HasCallStack => UDP -> CBytes ->IO ()+{-# INLINABLE setMulticastInterface #-} setMulticastInterface udp@(UDP hdl _ _ _ _) iaddr = do checkUDPClosed udp withCBytesUnsafe iaddr $ \ iaddrp ->@@ -268,6 +280,7 @@ -- | Set broadcast on or off. setBroadcast :: HasCallStack => UDP -> Bool -> IO ()+{-# INLINABLE setBroadcast #-} setBroadcast udp@(UDP hdl _ _ _ _) b = do checkUDPClosed udp throwUVIfMinus_ (uv_udp_set_broadcast hdl (if b then 1 else 0))@@ -277,6 +290,7 @@ => UDP -> Int -- ^ 1 ~ 255 -> IO ()+{-# INLINABLE setTTL #-} setTTL udp@(UDP hdl _ _ _ _) ttl = do checkUDPClosed udp throwUVIfMinus_ (uv_udp_set_ttl hdl (fromIntegral ttl))@@ -288,6 +302,7 @@ -> CBytes -- ^ Interface address. -> Membership -- ^ UV_JOIN_GROUP | UV_LEAVE_GROUP -> IO ()+{-# INLINABLE setMembership #-} setMembership udp@(UDP hdl _ _ _ _) gaddr iaddr member = do checkUDPClosed udp withCBytesUnsafe gaddr $ \ gaddrp ->@@ -302,6 +317,7 @@ -> CBytes -- ^ Source address. -> Membership -- ^ UV_JOIN_GROUP | UV_LEAVE_GROUP -> IO ()+{-# INLINABLE setSourceMembership #-} setSourceMembership udp@(UDP hdl _ _ _ _) gaddr iaddr source member = do checkUDPClosed udp withCBytesUnsafe gaddr $ \ gaddrp ->@@ -324,6 +340,7 @@ -- | @UDPRecvConfig 512 6@ defaultUDPRecvConfig :: UDPRecvConfig+{-# INLINABLE defaultUDPRecvConfig #-} defaultUDPRecvConfig = UDPRecvConfig 512 6 @@ -355,6 +372,7 @@ -- Then we poke those cells out. -- newRecvBuf :: Int32 -> Int -> IO (A.MutablePrimArray RealWorld Word8, A.MutablePrimArray RealWorld (Ptr Word8))+{-# INLINABLE newRecvBuf #-} newRecvBuf bufSiz bufArrSiz = do rbuf <- A.newPinnedPrimArray (fromIntegral bufsiz' * bufArrSiz') rbufArr <- A.newPinnedPrimArray bufArrSiz'@@ -383,6 +401,7 @@ -> UDP -> ((Maybe SocketAddr, Bool, V.Bytes) -> IO a) -> IO ()+{-# INLINABLE recvUDPLoop #-} recvUDPLoop (UDPRecvConfig bufSiz bufArrSiz) udp@(UDP hdl slot uvm _ _) worker = do bracket (do check <- throwOOMIfNull $ hs_uv_check_alloc@@ -404,6 +423,7 @@ -- to indicate if the message is partial (larger than receive buffer size). -- recvUDP :: HasCallStack => UDPRecvConfig -> UDP -> IO [(Maybe SocketAddr, Bool, V.Bytes)]+{-# INLINABLE recvUDP #-} recvUDP (UDPRecvConfig bufSiz bufArrSiz) udp@(UDP hdl slot uvm _ _) = do bracket (do check <- throwOOMIfNull $ hs_uv_check_alloc@@ -423,6 +443,7 @@ -> (A.MutablePrimArray RealWorld Word8, A.MutablePrimArray RealWorld (Ptr Word8)) -> Int32 -> IO [(Maybe SocketAddr, Bool, V.Bytes)]+{-# INLINABLE recvUDPWith #-} recvUDPWith udp@(UDP hdl slot uvm _ _) (rubf, rbufArr) bufSiz = -- It's important to keep recv buffer alive, even if we don't directly use it mask_ . withMutablePrimArrayContents rubf $ \ _ -> do
Z/IO/Process.hsc view
@@ -70,7 +70,7 @@ import Z.Data.CBytes import Z.Data.CBytes as CBytes import Z.Data.JSON (JSON)-import Z.Data.Vector as V+import qualified Z.Data.Vector as V import qualified Z.Data.Text as T import qualified Data.List as List import Z.Data.Array.Unaligned@@ -88,6 +88,7 @@ -- | Default process options, start @".\/main"@ with no arguments, redirect all std streams to @\/dev\/null@. defaultProcessOptions :: ProcessOptions+{-# INLINABLE defaultProcessOptions #-} defaultProcessOptions = ProcessOptions { processFile = "./main" , processArgs = []@@ -106,6 +107,7 @@ -- | Wait until process exit and return the 'ExitCode'. waitProcessExit :: TVar ProcessState -> IO ExitCode+{-# INLINABLE waitProcessExit #-} waitProcessExit svar = atomically $ do s <- readTVar svar case s of ProcessExited e -> return e@@ -113,6 +115,7 @@ -- | Get process 'PID' if process is running. getProcessPID :: TVar ProcessState -> IO (Maybe PID)+{-# INLINABLE getProcessPID #-} getProcessPID svar = atomically $ do s <- readTVar svar case s of ProcessRunning pid -> return (Just pid)@@ -120,6 +123,7 @@ -- | Send signals to process. killPID :: HasCallStack => PID -> Signal -> IO ()+{-# INLINABLE killPID #-} killPID (PID pid) sig = throwUVIfMinus_ (uv_kill pid sig) pattern SIGTERM :: Signal@@ -151,6 +155,7 @@ -- waitProcessExit pstate -- wait for process exit on current thread. -- @ initProcess :: ProcessOptions -> Resource (Maybe UVStream, Maybe UVStream, Maybe UVStream, TVar ProcessState)+{-# INLINABLE initProcess #-} initProcess opt = initResource (spawn opt) $ \ (s0,s1,s2, pstate) -> do _ <- waitProcessExit pstate@@ -163,6 +168,7 @@ -- The same as 'initProcess', but the clean action will try to send 'SIGTERM' -- to the process first. initProcess' :: ProcessOptions -> Resource (Maybe UVStream, Maybe UVStream, Maybe UVStream, TVar ProcessState)+{-# INLINABLE initProcess' #-} initProcess' opt = initResource (spawn opt) $ \ (s0, s1, s2, pstate) -> do m_pid <- getProcessPID pstate@@ -179,6 +185,7 @@ => ProcessOptions -- ^ processStdStreams options are ignored -> V.Bytes -- ^ stdin -> IO (V.Bytes, V.Bytes, ExitCode) -- ^ stdout, stderr, exit code+{-# INLINABLE readProcess #-} readProcess opts inp = do withResource (initProcess opts{processStdStreams=(ProcessCreate, ProcessCreate, ProcessCreate)}) $ \ (Just s0, Just s1, Just s2, pstate) -> do@@ -202,6 +209,7 @@ => ProcessOptions -- ^ processStdStreams options are ignored -> T.Text -- ^ stdin -> IO (T.Text, T.Text, ExitCode) -- ^ stdout, stderr, exit code+{-# INLINABLE readProcessText #-} readProcessText opts inp = do (out, err, e) <- readProcess opts (T.getUTF8Bytes inp) return (T.validate out, T.validate err, e)@@ -210,6 +218,7 @@ -- -- Please manually close child process's std stream(if any) after process exits. spawn :: HasCallStack => ProcessOptions -> IO (Maybe UVStream, Maybe UVStream, Maybe UVStream, TVar ProcessState)+{-# INLINABLE spawn #-} spawn ProcessOptions{..} = do (MutableByteArray popts##) <- newByteArray (#size uv_process_options_t)@@ -296,6 +305,7 @@ -- The returned value of priority is between -20 (high priority) and 19 (low priority). -- On Windows, the returned priority will equal one of the PRIORITY constants. getPriority :: HasCallStack => PID -> IO Priority+{-# INLINABLE getPriority #-} getPriority pid = do (p, _) <- allocPrimUnsafe $ \ p_p -> throwUVIfMinus_ (uv_os_getpriority pid p_p) return p@@ -307,4 +317,5 @@ -- 'PRIORITY_ABOVE_NORMAL', 'PRIORITY_HIGH', and 'PRIORITY_HIGHEST' are also provided for convenience. -- setPriority :: HasCallStack => PID -> Priority -> IO ()+{-# INLINABLE setPriority #-} setPriority pid p = throwUVIfMinus_ (uv_os_setpriority pid p)
Z/IO/Resource.hs view
@@ -39,7 +39,7 @@ import qualified Control.Monad.Catch as MonadCatch import Control.Monad.IO.Class import qualified Data.Map.Strict as M-import Z.Data.PrimRef.PrimIORef+import Z.Data.PrimRef import Z.Data.Array import qualified Z.Data.Vector as V import Data.IORef@@ -81,7 +81,7 @@ -- 'with' \/ 'with''. -- initResource :: IO a -> (a -> IO ()) -> Resource a-{-# INLINE initResource #-}+{-# INLINABLE initResource #-} initResource create release = Resource $ do r <- create return $ (r, release r)@@ -92,7 +92,7 @@ -- inside 'Resource' monad. -- initResource_ :: IO a -> IO () -> Resource a-{-# INLINE initResource_ #-}+{-# INLINABLE initResource_ #-} initResource_ create release = Resource $ do r <- create return $ (r, release)@@ -188,7 +188,8 @@ -- | Dump the status of pool. statPool :: Pool key res -> IO (SmallArray (M.Map key Int))-statPool (Pool _ _ _ arr) = (`V.traverseVec` arr) $ \ resMapRef -> do+{-# INLINABLE statPool #-}+statPool (Pool _ _ _ arr) = (`V.traverse` arr) $ \ resMapRef -> do mResMap <- readIORef resMapRef case mResMap of Just resMap -> return $ (`fmap` resMap) ( \ es ->@@ -203,6 +204,7 @@ -> Int -- ^ maximum number of resources per local pool per key to be maintained. -> Int -- ^ amount of time after which an unused resource can be released (in seconds). -> Resource (Pool key res)+{-# INLINABLE initPool #-} initPool resf limit itime = initResource createPool closePool where createPool = do@@ -215,7 +217,7 @@ closePool (Pool _ _ _ localPoolArr) = do -- close all existed resource- (`V.traverseVec_` localPoolArr) $ \ resMapRef ->+ (`V.traverse_` localPoolArr) $ \ resMapRef -> atomicModifyIORef resMapRef $ \ mResMap -> case mResMap of Just resMap -> (Nothing, mapM_ closeEntry resMap)@@ -231,6 +233,7 @@ -- resource will be closed(not return to pool). withPool :: (MonadCatch.MonadMask m, MonadIO m, Ord key, HasCallStack) => Pool key res -> key -> (res -> m a) -> m a+{-# INLINABLE withPool #-} withPool (Pool resf limitPerKey itime arr) key f = do !resMapRef <- indexArr arr . fst <$> liftIO (threadCapability =<< myThreadId) fst <$> MonadCatch.generalBracket@@ -300,10 +303,12 @@ -> Int -- ^ maximum number of resources per local pool to be maintained. -> Int -- ^ amount of time after which an unused resource can be released (in seconds). -> Resource (SimplePool res)+{-# INLINABLE initSimplePool #-} initSimplePool f = initPool (const f) -- | Open resource with 'SimplePool', see 'withPool' -- withSimplePool :: (MonadCatch.MonadMask m, MonadIO m, HasCallStack) => SimplePool res -> (res -> m a) -> m a+{-# INLINABLE withSimplePool #-} withSimplePool pool = withPool pool ()
Z/IO/StdStream.hs view
@@ -46,7 +46,7 @@ , stdin, stdout, stderr , stdinBuf, stdoutBuf, stderrBuf -- * utils- , readStd, printStd, putStd, printStdLn, putStdLn+ , readStd, printStd, putStd, printStdLn, printStdLnP, putStdLn -- * re-export , withMVar -- * Constant@@ -57,10 +57,12 @@ ) where import Control.Monad+import Control.Monad.Primitive import Control.Concurrent.MVar import Foreign.Ptr import System.IO.Unsafe import qualified Z.Data.Builder as B+import qualified Z.Data.Parser.Base as P import qualified Z.Data.Text.Print as T import qualified Z.Data.Vector as V import Z.IO.UV.FFI@@ -252,13 +254,13 @@ -- | Get terminal's output window size in (width, height) format, -- return (-1, -1) if stdout is not connected to TTY.-getStdoutWinSize :: HasCallStack => IO (CInt, CInt)+getStdoutWinSize :: HasCallStack => IO (Int, Int) getStdoutWinSize = case stdout of StdStream True hdl _ uvm -> withUVManager' uvm $ do- (w, (h, ())) <- allocPrimUnsafe $ \ w ->- allocPrimUnsafe $ \ h -> throwUVIfMinus_ $ uv_tty_get_winsize hdl w h- return (w, h)+ (w, (h, ())) <- allocPrimUnsafe @CInt $ \ w ->+ allocPrimUnsafe @CInt $ \ h -> throwUVIfMinus_ $ uv_tty_get_winsize hdl w h+ return (fromIntegral w, fromIntegral h) _ -> return (-1, -1) --------------------------------------------------------------------------------@@ -276,6 +278,10 @@ -- | Print a 'Print' and flush to stdout, with a linefeed. printStdLn :: (HasCallStack, T.Print a) => a -> IO () printStdLn s = putStdLn (T.toUTF8Builder s)++-- | Similar to 'printStdLn', 'P.Parser' debug tool.+printStdLnP :: (HasCallStack, T.Print a) => a -> P.Parser ()+printStdLnP s = unsafeIOToPrim $ putStdLn (T.toUTF8Builder s) -- | Print a 'Builder' and flush to stdout, with a linefeed. putStdLn :: HasCallStack => B.Builder a -> IO ()
Z/IO/StdStream/Ansi.hs view
@@ -6,14 +6,11 @@ Maintainer : winterland1989@gmail.com Stability : experimental Portability : non-portable- Provides utilities to build <ANSI code sequences https://en.wikipedia.org/wiki/ANSI_escape_code>.- @ > putStd . bold . italicize . color Red $ "hello" hello -- bold, italicize and red @- -} module Z.IO.StdStream.Ansi@@ -46,6 +43,7 @@ csi, sgr, colorToCode ) where +import Control.Monad import qualified Z.Data.Builder as B import qualified Z.Data.Parser as P import qualified Z.Data.Text as T@@ -58,6 +56,7 @@ csi :: [Int] -- ^ List of parameters for the control sequence -> B.Builder () -- ^ Character(s) that identify the control function -> B.Builder ()+{-# INLINABLE csi #-} csi args code = do B.char8 '\ESC' B.char8 '['@@ -69,31 +68,38 @@ -> B.Builder () cursorDownLine, cursorUpLine :: Int -- ^ Number of lines to move -> B.Builder ()-cursorUp n = csi [n] (B.char8 'A')-cursorDown n = csi [n] (B.char8 'B')-cursorForward n = csi [n] (B.char8 'C')-cursorBackward n = csi [n] (B.char8 'D')-cursorDownLine n = csi [n] (B.char8 'E')-cursorUpLine n = csi [n] (B.char8 'F')+{-# INLINABLE cursorUp #-}+cursorUp n = when (n > 0) $ csi [n] (B.char8 'A')+{-# INLINABLE cursorDown #-}+cursorDown n = when (n > 0) $ csi [n] (B.char8 'B')+{-# INLINABLE cursorForward #-}+cursorForward n = when (n > 0) $ csi [n] (B.char8 'C')+{-# INLINABLE cursorBackward #-}+cursorBackward n = when (n > 0) $ csi [n] (B.char8 'D')+{-# INLINABLE cursorDownLine #-}+cursorDownLine n = when (n > 0) $ csi [n] (B.char8 'E')+{-# INLINABLE cursorUpLine #-}+cursorUpLine n = when (n > 0) $ csi [n] (B.char8 'F') -getCursorPosition :: IO (Int, Int)-getCursorPosition = do- withRawStdin . withMVar stdinBuf $ \ i -> do- clearInputBuffer i- putStd (csi [] "6n")- readParser (do- P.word8 ESC- P.word8 BRACKET_LEFT- !n <- P.int- P.word8 SEMICOLON- !m <- P.int- P.word8 LETTER_R- return (m, n)) i+getCursorPosition :: BufferedInput -> IO (Int, Int)+{-# INLINABLE getCursorPosition #-}+getCursorPosition i = do+ clearInputBuffer i+ putStd (csi [] "6n")+ readParser (do+ P.word8 ESC+ P.word8 BRACKET_LEFT+ !n <- P.int+ P.word8 SEMICOLON+ !m <- P.int+ P.word8 LETTER_R+ return (m, n)) i -- | Code to move the cursor to the specified column. The column numbering is -- 1-based (that is, the left-most column is numbered 1). setCursorColumn :: Int -- ^ 1-based column to move to -> B.Builder ()+{-# INLINABLE setCursorColumn #-} setCursorColumn n = csi [n] (B.char8 'G') -- | Code to move the cursor to the specified position (row and column). The@@ -101,34 +107,48 @@ setCursorPosition :: Int -- ^ 1-based row to move to -> Int -- ^ 1-based column to move to -> B.Builder ()+{-# INLINABLE setCursorPosition #-} setCursorPosition n m = csi [n, m] (B.char8 'G') saveCursor, restoreCursor :: B.Builder ()+{-# INLINABLE saveCursor #-} saveCursor = B.char8 '\ESC' >> B.char8 '7'+{-# INLINABLE restoreCursor #-} restoreCursor = B.char8 '\ESC' >> B.char8 '8' clearFromCursorToScreenEnd, clearFromCursorToScreenBeginning, clearScreen :: B.Builder () clearFromCursorToLineEnd, clearFromCursorToLineBeginning, clearLine :: B.Builder () +{-# INLINABLE clearFromCursorToScreenEnd #-} clearFromCursorToScreenEnd = csi [0] (B.char8 'J')+{-# INLINABLE clearFromCursorToScreenBeginning #-} clearFromCursorToScreenBeginning = csi [1] (B.char8 'J')+{-# INLINABLE clearScreen #-} clearScreen = csi [2] (B.char8 'J')+{-# INLINABLE clearFromCursorToLineEnd #-} clearFromCursorToLineEnd = csi [0] (B.char8 'K')+{-# INLINABLE clearFromCursorToLineBeginning #-} clearFromCursorToLineBeginning = csi [1] (B.char8 'K')+{-# INLINABLE clearLine #-} clearLine = csi [2] (B.char8 'K') scrollPageUp, scrollPageDown :: Int -- ^ Number of lines to scroll by -> B.Builder()-scrollPageUp n = csi [n] (B.char8 'S')-scrollPageDown n = csi [n] (B.char8 'T')+{-# INLINABLE scrollPageUp #-}+scrollPageUp n = when (n > 0) $ csi [n] (B.char8 'S')+{-# INLINABLE scrollPageDown #-}+scrollPageDown n = when (n > 0) $ csi [n] (B.char8 'T') hideCursor, showCursor :: B.Builder ()+{-# INLINABLE hideCursor #-} hideCursor = csi [] "?25l"+{-# INLINABLE showCursor #-} showCursor = csi [] "?25h" -- | XTerm control sequence to set the Icon Name and Window Title. setTitle :: T.Text -- ^ New Icon Name and Window Title -> B.Builder ()+{-# INLINABLE setTitle #-} setTitle title = do "\ESC]0;" B.text (T.filter (/= '\007') title)@@ -136,6 +156,7 @@ sgr :: [Word8] -- ^ List of sgr code for the control sequence -> B.Builder ()+{-# INLINABLE sgr #-} sgr args = do B.char8 '\ESC' B.char8 '['@@ -143,52 +164,72 @@ B.char8 'm' reset :: B.Builder ()+{-# INLINABLE reset #-} reset = sgr [0] boldIntensity, faintIntensity, resetIntensity :: B.Builder ()+{-# INLINABLE boldIntensity #-} boldIntensity = sgr [1]+{-# INLINABLE faintIntensity #-} faintIntensity = sgr [2]+{-# INLINABLE resetIntensity #-} resetIntensity = sgr [22] bold :: B.Builder () -> B.Builder ()+{-# INLINABLE bold #-} bold t = boldIntensity >> t >> resetIntensity italicized, noItalicized :: B.Builder ()+{-# INLINABLE italicized #-} italicized = sgr [3]+{-# INLINABLE noItalicized #-} noItalicized = sgr [23] -- | Italicize some text italicize :: B.Builder () -> B.Builder ()+{-# INLINABLE italicize #-} italicize t = italicized >> t >> noItalicized singleUnderline, doubleUnderline, noUnderline :: B.Builder ()+{-# INLINABLE singleUnderline #-} singleUnderline = sgr [4]+{-# INLINABLE doubleUnderline #-} doubleUnderline = sgr [21]+{-# INLINABLE noUnderline #-} noUnderline = sgr [24] -- | Add single underline to some text underline :: B.Builder () -> B.Builder ()+{-# INLINABLE underline #-} underline t = singleUnderline >> t >> singleUnderline slowBlink, rapidBlink, blinkOff :: B.Builder () -- | less than 150 per minute+{-# INLINABLE slowBlink #-} slowBlink = sgr [5] -- | MS-DOS ANSI.SYS, 150+ per minute; not widely supported+{-# INLINABLE rapidBlink #-} rapidBlink = sgr [6]+{-# INLINABLE blinkOff #-} blinkOff = sgr [25] conceal, reveal :: B.Builder () -- | Aka Hide, not widely supported.+{-# INLINABLE conceal #-} conceal = sgr [8]+{-# INLINABLE reveal #-} reveal = sgr [28] invert, invertOff :: B.Builder () -- | Swap foreground and background colors, inconsistent emulation+{-# INLINABLE invert #-} invert = sgr [7]+{-# INLINABLE invertOff #-} invertOff = sgr [27] -- | Colorized some text color :: AnsiColor -> B.Builder () -> B.Builder ()+{-# INLINABLE color #-} color c t = do setForeground c t@@ -196,6 +237,7 @@ -- | Colorized some text with background color color' :: AnsiColor -> AnsiColor -> B.Builder () -> B.Builder ()+{-# INLINABLE color' #-} color' c1 c2 t = do setForeground c1 setBackground c2@@ -205,6 +247,7 @@ -- | Colorized some text palette :: PaletteColor -> B.Builder () -> B.Builder ()+{-# INLINABLE palette #-} palette c t = do setPaletteForeground c t@@ -212,6 +255,7 @@ -- | Colorized some text with background color palette' :: PaletteColor -> PaletteColor -> B.Builder () -> B.Builder ()+{-# INLINABLE palette' #-} palette' c1 c2 t = do setPaletteForeground c1 setPaletteBackground c2@@ -221,6 +265,7 @@ -- | Colorized some text rgb :: RGBColor -> B.Builder () -> B.Builder ()+{-# INLINABLE rgb #-} rgb c t = do setRGBForeground c t@@ -228,6 +273,7 @@ -- | Colorized some text with background color rgb' :: RGBColor -> RGBColor -> B.Builder () -> B.Builder ()+{-# INLINABLE rgb' #-} rgb' c1 c2 t = do setRGBForeground c1 setRGBBackground c2@@ -236,21 +282,31 @@ setDefaultBackground setForeground, setBrightForeground, setBackground, setBrightBackground :: AnsiColor -> B.Builder ()+{-# INLINABLE setForeground #-} setForeground c = sgr [30 + colorToCode c]+{-# INLINABLE setBrightForeground #-} setBrightForeground c = sgr [90 + colorToCode c]+{-# INLINABLE setBackground #-} setBackground c = sgr [40 + colorToCode c]+{-# INLINABLE setBrightBackground #-} setBrightBackground c = sgr [100 + colorToCode c] setPaletteForeground, setPaletteBackground :: PaletteColor -> B.Builder ()+{-# INLINABLE setPaletteForeground #-} setPaletteForeground index = sgr [38, 5, index]+{-# INLINABLE setPaletteBackground #-} setPaletteBackground index = sgr [48, 5, index] setRGBForeground, setRGBBackground :: RGBColor -> B.Builder ()+{-# INLINABLE setRGBForeground #-} setRGBForeground (r,g,b) = sgr [38, 2, r, g, b]+{-# INLINABLE setRGBBackground #-} setRGBBackground (r,g,b) = sgr [48, 2, r, g, b] setDefaultForeground, setDefaultBackground :: B.Builder ()+{-# INLINABLE setDefaultForeground #-} setDefaultForeground = sgr [39]+{-# INLINABLE setDefaultBackground #-} setDefaultBackground = sgr [49] -- | ANSI's eight standard colors@@ -266,6 +322,7 @@ deriving anyclass T.Print colorToCode :: AnsiColor -> Word8+{-# INLINABLE colorToCode #-} colorToCode c = case c of Black -> 0 Red -> 1
Z/IO/Time.hs view
@@ -36,6 +36,7 @@ -- | A alternative version of 'getSystemTime'' based on libuv's @uv_gettimeofday@, which also doesn't use pinned allocation. getSystemTime' :: HasCallStack => IO SystemTime+{-# INLINABLE getSystemTime' #-} getSystemTime' = do (TimeVal64 s us) <- getTimeOfDay return (MkSystemTime s (fromIntegral us * 1000))@@ -48,6 +49,7 @@ -- The value is \"%Y-%m-%d %H:%M:%S\". -- This should be used with 'formatSystemTime' and 'parseSystemTime'. simpleDateFormat :: TimeFormat+{-# INLINABLE simpleDateFormat #-} simpleDateFormat = "%Y-%m-%d %H:%M:%S" -- | Simple format @2020-10-16T03:15:29@.@@ -55,6 +57,7 @@ -- The value is \"%Y-%m-%dT%H:%M:%S%z\". -- This should be used with 'formatSystemTime' and 'parseSystemTime'. iso8061DateFormat :: TimeFormat+{-# INLINABLE iso8061DateFormat #-} iso8061DateFormat = "%Y-%m-%dT%H:%M:%S%z" -- | Format for web (RFC 2616).@@ -62,6 +65,7 @@ -- The value is \"%a, %d %b %Y %H:%M:%S GMT\". -- This should be used with 'formatSystemTimeGMT' and 'parseSystemTimeGMT'. webDateFormat :: TimeFormat+{-# INLINABLE webDateFormat #-} webDateFormat = "%a, %d %b %Y %H:%M:%S GMT" -- | Format for e-mail (RFC 5322).@@ -69,6 +73,7 @@ -- The value is \"%a, %d %b %Y %H:%M:%S %z\". -- This should be used with 'formatSystemTime' and 'parseSystemTime'. mailDateFormat :: TimeFormat+{-# INLINABLE mailDateFormat #-} mailDateFormat = "%a, %d %b %Y %H:%M:%S %z" ----------------------------------------------------------------@@ -79,7 +84,7 @@ -- formatSystemTime :: TimeFormat -> SystemTime -> IO CBytes formatSystemTime fmt t = formatSystemTimeHelper c_format_unix_time fmt t-{-# INLINE formatSystemTime #-}+{-# INLINABLE formatSystemTime #-} -- | Formatting 'SystemTime' to 'CBytes' in GMT. --@@ -98,7 +103,7 @@ formatSystemTimeGMT :: TimeFormat -> SystemTime -> CBytes formatSystemTimeGMT fmt t = unsafePerformIO $ formatSystemTimeHelper c_format_unix_time_gmt fmt t-{-# INLINE formatSystemTimeGMT #-}+{-# INLINABLE formatSystemTimeGMT #-} ---------------------------------------------------------------- -- | Parsing 'CBytes' to 'SystemTime' interpreting as localtime.@@ -120,6 +125,7 @@ -- @ -- parseSystemTime :: TimeFormat -> CBytes -> IO SystemTime+{-# INLINABLE parseSystemTime #-} parseSystemTime fmt str = withCBytesUnsafe fmt $ \cfmt -> withCBytesUnsafe str $ \cstr -> do@@ -134,6 +140,7 @@ -- MkSystemTime {systemSeconds = 0, systemNanoseconds = 0} parseSystemTimeGMT :: TimeFormat -> CBytes -> SystemTime+{-# INLINABLE parseSystemTimeGMT #-} parseSystemTimeGMT fmt str = unsafePerformIO $ withCBytesUnsafe fmt $ \cfmt -> withCBytesUnsafe str $ \cstr -> do@@ -160,6 +167,7 @@ -> TimeFormat -> SystemTime -> IO CBytes+{-# INLINABLE formatSystemTimeHelper #-} formatSystemTimeHelper formatFun fmt t = go 80 where MkSystemTime sec _ = t
Z/IO/UV/Errno.hsc view
@@ -22,12 +22,14 @@ -- | Returns the error message for the given error code. Leaks a few bytes of memory when you call it with an unknown error code. uvStdError :: CInt -> IO Text+{-# INLINABLE uvStdError #-} uvStdError errno = toText <$> (fromCString =<< uv_strerror errno) foreign import ccall unsafe uv_strerror :: CInt -> IO CString -- | Returns the error name for the given error code. Leaks a few bytes of memory when you call it with an unknown error code. uvErrName :: CInt -> IO Text+{-# INLINABLE uvErrName #-} uvErrName errno = toText <$> (fromCString =<< uv_err_name errno) foreign import ccall unsafe uv_err_name :: CInt -> IO CString
Z/IO/UV/FFI.hsc view
@@ -62,15 +62,18 @@ data UVLoopData peekUVEventQueue :: Ptr UVLoopData -> IO (Int, Ptr Int)+{-# INLINABLE peekUVEventQueue #-} peekUVEventQueue p = (,) <$> (#{peek hs_loop_data, event_counter } p) <*> (#{peek hs_loop_data, event_queue } p) clearUVEventCounter :: Ptr UVLoopData -> IO ()+{-# INLINABLE clearUVEventCounter #-} clearUVEventCounter p = do #{poke hs_loop_data, event_counter } p $ (0 :: Int) peekUVBufferTable :: Ptr UVLoopData -> IO (Ptr (Ptr Word8), Ptr CSsize)+{-# INLINABLE peekUVBufferTable #-} peekUVBufferTable p = (,) <$> (#{peek hs_loop_data, buffer_table } p) <*> (#{peek hs_loop_data, buffer_size_table } p)@@ -86,6 +89,7 @@ -- | Peek loop data pointer from uv loop pointer. peekUVLoopData :: Ptr UVLoop -> IO (Ptr UVLoopData)+{-# INLINABLE peekUVLoopData #-} peekUVLoopData p = #{peek uv_loop_t, data} p foreign import ccall unsafe hs_uv_loop_init :: Int -> IO (Ptr UVLoop)@@ -110,6 +114,7 @@ data UVHandle peekUVHandleData :: Ptr UVHandle -> IO UVSlotUnsafe+{-# INLINABLE peekUVHandleData #-} peekUVHandleData p = UVSlotUnsafe <$> (#{peek uv_handle_t, data} p :: IO Int) foreign import ccall unsafe hs_uv_fileno :: Ptr UVHandle -> IO FD@@ -155,6 +160,7 @@ foreign import ccall unsafe uv_tcp_getpeername :: Ptr UVHandle -> MBA## SocketAddr -> MBA## CInt -> IO CInt uV_TCP_IPV6ONLY :: CUInt+{-# INLINABLE uV_TCP_IPV6ONLY #-} uV_TCP_IPV6ONLY = #const UV_TCP_IPV6ONLY foreign import ccall unsafe uv_tcp_bind :: Ptr UVHandle -> MBA## SocketAddr -> CUInt -> IO CInt@@ -515,6 +521,7 @@ | otherwise = DirEntUnknown peekUVDirEnt :: Ptr DirEntType -> IO (CString, UVDirEntType)+{-# INLINABLE peekUVDirEnt #-} #ifdef HAVE_DIRENT_TYPES peekUVDirEnt p = (,) (#{ptr hs_uv__dirent_t, d_name } p) <$> (#{peek hs_uv__dirent_t, d_type } p) #else@@ -537,10 +544,14 @@ deriving anyclass (Print, JSON) instance Storable UVTimeSpec where+ {-# INLINABLE sizeOf #-} sizeOf _ = #{size uv_timespec_t}+ {-# INLINABLE alignment #-} alignment _ = #{alignment uv_timespec_t}+ {-# INLINABLE peek #-} peek p = UVTimeSpec <$> (#{peek uv_timespec_t, tv_sec } p) <*> (#{peek uv_timespec_t, tv_nsec } p)+ {-# INLINABLE poke #-} poke p (UVTimeSpec sec nsec) = do (#{poke uv_timespec_t, tv_sec } p sec) (#{poke uv_timespec_t, tv_nsec } p nsec)@@ -566,9 +577,11 @@ deriving anyclass (Print, JSON) uvStatSize :: Int+{-# INLINABLE uvStatSize #-} uvStatSize = #{size uv_stat_t} peekUVStat :: Ptr FStat -> IO FStat+{-# INLINABLE peekUVStat #-} peekUVStat p = FStat <$> (#{peek uv_stat_t, st_dev } p) <*> (fromIntegral <$> (#{peek uv_stat_t, st_mode } p :: IO Word64))@@ -821,6 +834,7 @@ deriving anyclass (Print, JSON) processStdStreamFlag :: ProcessStdStream -> CInt+{-# INLINABLE processStdStreamFlag #-} processStdStreamFlag ProcessIgnore = #const UV_IGNORE processStdStreamFlag ProcessCreate = (#const UV_CREATE_PIPE) .|. (#const UV_READABLE_PIPE)
Z/IO/UV/FFI_Env.hsc view
@@ -75,9 +75,11 @@ deriving anyclass (Print, JSON) sizeOfResUsage :: Int+{-# INLINABLE sizeOfResUsage #-} sizeOfResUsage = #size uv_rusage_t peekResUsage :: MBA## a -> IO ResUsage+{-# INLINABLE peekResUsage #-} peekResUsage mba = do utime_sec :: CLong <- peekMBA mba (#offset uv_rusage_t, ru_utime ) utime_usec :: CLong <- peekMBA mba ((#offset uv_rusage_t, ru_utime) + sizeOf (undefined :: CLong))@@ -147,6 +149,7 @@ deriving anyclass (Print, JSON) getOSName :: HasCallStack => IO OSName+{-# INLINABLE getOSName #-} getOSName = do (MutableByteArray mba##) <- newByteArray (#size uv_utsname_t) throwUVIfMinus_ (uv_os_uname mba##)@@ -180,6 +183,7 @@ -- On non-Windows systems, all data comes from getpwuid_r(3). -- On Windows, uid and gid are set to -1 and have no meaning, and shell is empty. getPassWD :: HasCallStack => IO PassWD+{-# INLINABLE getPassWD #-} getPassWD = bracket (do mpa@(MutableByteArray mba##) <- newByteArray (#size uv_passwd_t) throwUVIfMinus_ (uv_os_get_passwd mba##)@@ -215,6 +219,7 @@ -- | Gets information about the CPUs on the system. getCPUInfo :: HasCallStack => IO [CPUInfo]+{-# INLINABLE getCPUInfo #-} getCPUInfo = bracket (do (p, (len, _)) <- allocPrimUnsafe $ \ pp -> allocPrimUnsafe $ \ plen ->@@ -224,6 +229,7 @@ (\ (p, len) -> forM [0..fromIntegral len-1] (peekCPUInfoOff p)) peekCPUInfoOff :: Ptr CPUInfo -> Int -> IO CPUInfo+{-# INLINABLE peekCPUInfoOff #-} peekCPUInfoOff p off = do let p' = p `plusPtr` (off * (#size uv_cpu_info_t)) model <- fromCString =<< (#peek uv_cpu_info_t, model) p'@@ -239,6 +245,7 @@ -- | Gets the load average. See: <https://en.wikipedia.org/wiki/Load_(computing)> getLoadAvg :: IO (Double, Double, Double)+{-# INLINABLE getLoadAvg #-} getLoadAvg = do (arr, _) <- allocPrimArrayUnsafe 3 uv_loadavg return ( indexPrimArray arr 0@@ -258,6 +265,7 @@ -- | Cross-platform implementation of <https://man7.org/linux/man-pages/man2/gettimeofday.2.html gettimeofday(2)>. -- The timezone argument to gettimeofday() is not supported, as it is considered obsolete. getTimeOfDay :: HasCallStack => IO TimeVal64+{-# INLINABLE getTimeOfDay #-} getTimeOfDay = do (MutableByteArray mba##) <- newByteArray (#size uv_timeval64_t) throwUVIfMinus_ (uv_gettimeofday mba##)
Z/IO/UV/Manager.hs view
@@ -55,7 +55,7 @@ import GHC.Conc.Sync (labelThread) import System.IO.Unsafe import Z.Data.Array-import Z.Data.PrimRef.PrimIORef+import Z.Data.PrimRef import qualified Z.Data.Text.Print as T import Z.IO.Exception import Z.IO.Resource@@ -82,6 +82,7 @@ instance Show UVManager where show = T.toString instance T.Print UVManager where+ {-# INLINABLE toUTF8BuilderP #-} toUTF8BuilderP p uvm = T.parenWhen (p > 10) $ "UVManager on capability " >> T.int (uvmCap uvm) @@ -162,6 +163,7 @@ pokeElemOff bufSizTable slot (fromIntegral bufSiz) initUVManager :: HasCallStack => Int -> Int -> Resource UVManager+{-# INLINABLE initUVManager #-} initUVManager siz cap = do loop <- initResource (throwOOMIfNull $ hs_uv_loop_init (fromIntegral siz))@@ -180,6 +182,7 @@ -- libuv is not thread safe, use this function to perform any action which will mutate uv_loop's state. -- withUVManager :: HasCallStack => UVManager -> (Ptr UVLoop -> IO a) -> IO a+{-# INLINABLE withUVManager #-} withUVManager (UVManager _ loop loopData runningLock _) f = go where go = do@@ -205,11 +208,13 @@ -- In fact most of the libuv's functions are not thread safe, so watch out! -- withUVManager' :: HasCallStack => UVManager -> IO a -> IO a+{-# INLINABLE withUVManager' #-} withUVManager' uvm f = withUVManager uvm (\ _ -> f) -- | Start the uv loop -- startUVManager :: HasCallStack => UVManager -> IO ()+{-# INLINABLE startUVManager #-} startUVManager uvm@(UVManager _ _ _ runningLock _) = poll -- use a closure capture uvm in case of stack memory leaking where -- we borrow mio's non-blocking/blocking poll strategy here@@ -267,7 +272,7 @@ -- Always use this function to turn an 'UVSlotUnsafe' into 'UVSlot', so that the block -- table size synchronize with libuv side's slot table. getUVSlot :: HasCallStack => UVManager -> IO UVSlotUnsafe -> IO UVSlot-{-# INLINE getUVSlot #-}+{-# INLINABLE getUVSlot #-} getUVSlot (UVManager blockTableRef _ _ _ _) f = do slot <- throwUVIfMinus (unsafeGetSlot <$> f) blockTable <- readIORef blockTableRef@@ -287,6 +292,7 @@ -- | Cancel uv async function (actions which can be cancelled with 'uv_cancel') with -- best effort, if the action is already performed, run an extra clean up action. cancelUVReq :: UVManager -> UVSlot -> (Int -> IO ()) -> IO ()+{-# INLINABLE cancelUVReq #-} cancelUVReq uvm slot extra_cleanup = withUVManager uvm $ \ loop -> do m <- getBlockMVar uvm slot r <- tryTakeMVar m@@ -306,6 +312,7 @@ -- async function with best efforts, withUVRequest :: HasCallStack => UVManager -> (Ptr UVLoop -> IO UVSlotUnsafe) -> IO Int+{-# INLINABLE withUVRequest #-} withUVRequest uvm f = do (slot, m) <- withUVManager uvm $ \ loop -> mask_ $ do slot <- getUVSlot uvm (f loop)@@ -318,6 +325,7 @@ -- | Same with 'withUVRequest' but disgard the result. withUVRequest_ :: HasCallStack => UVManager -> (Ptr UVLoop -> IO UVSlotUnsafe) -> IO ()+{-# INLINABLE withUVRequest_ #-} withUVRequest_ uvm f = void (withUVRequest uvm f) -- | Same with 'withUVRequest' but apply an convert function to result.@@ -330,6 +338,7 @@ -> (Ptr UVLoop -> IO UVSlotUnsafe) -> (Int -> IO b) -- ^ convert function -> IO b+{-# INLINABLE withUVRequest' #-} withUVRequest' uvm f g = do (slot, m) <- withUVManager uvm $ \ loop -> mask_ $ do slot <- getUVSlot uvm (f loop)@@ -345,6 +354,7 @@ -- e.g. release result memory. withUVRequestEx :: HasCallStack => UVManager -> (Ptr UVLoop -> IO UVSlotUnsafe) -> (Int -> IO ()) -> IO Int+{-# INLINABLE withUVRequestEx #-} withUVRequestEx uvm f extra_cleanup = do (slot, m) <- withUVManager uvm $ \ loop -> mask_ $ do slot <- getUVSlot uvm (f loop)@@ -364,6 +374,7 @@ -- we solve this problem with simple round-robin load-balancing: forkBa will automatically -- distribute new threads to all capabilities in round-robin manner. Thus its name forkBa(lance). forkBa :: IO () -> IO ThreadId+{-# INLINABLE forkBa #-} forkBa io = do i <- atomicAddCounter counter 1 forkOn i io
Z/IO/UV/UVStream.hs view
@@ -51,6 +51,7 @@ instance Show UVStream where show = T.toString instance T.Print UVStream where+ {-# INLINABLE toUTF8BuilderP #-} toUTF8BuilderP _ (UVStream hdl slot uvm _) = do "UVStream{uvsHandle=" >> T.toUTF8Builder hdl ",uvsSlot=" >> T.toUTF8Builder slot@@ -72,6 +73,7 @@ => (Ptr UVLoop -> Ptr UVHandle -> IO ()) -> UVManager -> Resource UVStream+{-# INLINABLE initUVStream #-} initUVStream f uvm = initResource (withUVManager uvm $ \ loop -> do hdl <- hs_uv_handle_alloc loop@@ -85,6 +87,7 @@ -- | Manually close a uv stream. closeUVStream :: UVStream -> IO ()+{-# INLINABLE closeUVStream #-} closeUVStream (UVStream hdl _ uvm closed) = withUVManager' uvm $ do c <- readIORef closed -- hs_uv_handle_close won't return error@@ -94,6 +97,7 @@ -- -- Futher writing will throw 'ResourceVanished'(EPIPE). shutdownUVStream :: HasCallStack => UVStream -> IO ()+{-# INLINABLE shutdownUVStream #-} shutdownUVStream (UVStream hdl _ uvm closed) = do c <- readIORef closed when c throwECLOSED@@ -106,6 +110,7 @@ -- | Get stream fd getUVStreamFD :: HasCallStack => UVStream -> IO FD+{-# INLINABLE getUVStreamFD #-} getUVStreamFD (UVStream hdl _ _ closed) = do c <- readIORef closed when c throwECLOSED@@ -187,10 +192,12 @@ -- | Write "hello world" to a 'UVStream'. helloWorld :: UVStream -> IO ()+{-# INLINABLE helloWorld #-} helloWorld uvs = writeOutput uvs (Ptr "hello world"#) 11 -- | Echo whatever received bytes. echo :: UVStream -> IO ()+{-# INLINABLE echo #-} echo uvs = do i <- newBufferedInput uvs o <- newBufferedOutput uvs
Z/IO/UV/Win.hs view
@@ -20,7 +20,7 @@ -- This functions will run 'uv__once_init' once if not run before, -- withUVInitDo :: IO a -> IO a-{-# INLINE withUVInitDo #-}+{-# INLINABLE withUVInitDo #-} #if defined(mingw32_HOST_OS)
@@ -7,63 +7,74 @@ -- -- The notes on linux 'writeFileP' applied to 'FilePtr' too. data FilePtr = FilePtr {-# UNPACK #-} !File- {-# UNPACK #-} !(PrimIORef Int64)+ {-# UNPACK #-} !(PrimRef RealWorld Int64) -- | Create a file offset bundle from an 'File'. -- newFilePtr :: File -- ^ the file we're reading -> Int64 -- ^ initial offset -> IO FilePtr-newFilePtr uvf off = FilePtr uvf <$> newPrimIORef off+{-# INLINABLE newFilePtr #-}+newFilePtr uvf off = FilePtr uvf <$> newPrimRef off -- | Get current offset. getFilePtrOffset :: FilePtr -> IO Int64-getFilePtrOffset (FilePtr _ offsetRef) = readPrimIORef offsetRef+{-# INLINABLE getFilePtrOffset #-}+getFilePtrOffset (FilePtr _ offsetRef) = readPrimRef offsetRef -- | Change current offset. setFilePtrOffset :: FilePtr -> Int64 -> IO ()-setFilePtrOffset (FilePtr _ offsetRef) = writePrimIORef offsetRef+{-# INLINABLE setFilePtrOffset #-}+setFilePtrOffset (FilePtr _ offsetRef) = writePrimRef offsetRef instance Input FilePtr where+ {-# INLINABLE readInput #-} readInput (FilePtr file offsetRef) buf bufSiz =- readPrimIORef offsetRef >>= \ off -> do+ readPrimRef offsetRef >>= \ off -> do l <- readFileP file buf bufSiz off- writePrimIORef offsetRef (off + fromIntegral l)+ writePrimRef offsetRef (off + fromIntegral l) return l instance Output FilePtr where+ {-# INLINABLE writeOutput #-} writeOutput (FilePtr file offsetRef) buf bufSiz =- readPrimIORef offsetRef >>= \ off -> do+ readPrimRef offsetRef >>= \ off -> do writeFileP file buf bufSiz off- writePrimIORef offsetRef (off + fromIntegral bufSiz)+ writePrimRef offsetRef (off + fromIntegral bufSiz) -- | Quickly open a file and read its content. readFile :: HasCallStack => CBytes -> IO V.Bytes+{-# INLINABLE readFile #-} readFile filename = do withResource (initFile filename O_RDONLY DEFAULT_FILE_MODE) $ \ file -> do readAll' =<< newBufferedInput file -- | Quickly open a file and read its content as UTF8 text. readTextFile :: HasCallStack => CBytes -> IO T.Text+{-# INLINABLE readTextFile #-} readTextFile filename = T.validate <$> readFile filename -- | Quickly open a file and write some content. writeFile :: HasCallStack => CBytes -> V.Bytes -> IO ()+{-# INLINABLE writeFile #-} writeFile filename content = do withResource (initFile filename (O_WRONLY .|. O_CREAT) DEFAULT_FILE_MODE) $ \ file -> do withPrimVectorSafe content (writeOutput file) -- | Quickly open a file and write some content as UTF8 text. writeTextFile :: HasCallStack => CBytes -> T.Text -> IO ()+{-# INLINABLE writeTextFile #-} writeTextFile filename content = writeFile filename (T.getUTF8Bytes content) -- | Quickly open a file and read its content as a JSON value. -- Throw 'OtherError' with name @EPARSE@ if JSON value is not parsed. readJSONFile :: (HasCallStack, JSON.JSON a) => CBytes -> IO a+{-# INLINABLE readJSONFile #-} readJSONFile filename = unwrap "EPARSE" . JSON.decode' =<< readFile filename -- | Quickly open a file and write a JSON Value. writeJSONFile :: (HasCallStack, JSON.JSON a) => CBytes -> a -> IO ()+{-# INLINABLE writeJSONFile #-} writeJSONFile filename x = writeFile filename (JSON.encode x) --------------------------------------------------------------------------------@@ -76,6 +87,7 @@ -- scandirRecursively "." (\\ p _ -> (== ".hs") . snd \<$\> splitExtension p) -- @ scandirRecursively :: HasCallStack => CBytes -> (CBytes -> DirEntType -> IO Bool) -> IO [CBytes]+{-# INLINABLE scandirRecursively #-} scandirRecursively dir p = loop [] =<< P.normalize dir where loop acc0 pdir =@@ -93,44 +105,53 @@ -- | Does given path exist? -- doesPathExist :: CBytes -> IO Bool+{-# INLINABLE doesPathExist #-} doesPathExist path = maybe False (const True) <$> stat' path -- | Returns 'True' if the argument file exists and is either a file or a -- symbolic link to a file, and 'False' otherwise. doesFileExist :: CBytes -> IO Bool+{-# INLINABLE doesFileExist #-} doesFileExist path = maybe False isFileSt <$> stat' path -- | Returns 'True' if the argument directory exists and is either a directory or a -- symbolic link to a directory, and 'False' otherwise. doesDirExist :: CBytes -> IO Bool+{-# INLINABLE doesDirExist #-} doesDirExist path = maybe False isDirSt <$> stat' path -------------------------------------------------------------------------------- -- | If given path is a symbolic link? isLink :: HasCallStack => CBytes -> IO Bool+{-# INLINABLE isLink #-} isLink = fmap isLinkSt . lstat -- | If given path is a directory or a symbolic link to a directory? isDir :: HasCallStack => CBytes -> IO Bool+{-# INLINABLE isDir #-} isDir = fmap isDirSt . stat -- | If given path is a file or a symbolic link to a file? isFile :: HasCallStack => CBytes -> IO Bool+{-# INLINABLE isFile #-} isFile = fmap isFileSt . stat -- | Shortcut to @\\ st -> stMode st .&. S_IFMT == S_IFLNK@ -- -- Note you should use 'lstat' to get the link's stat. isLinkSt :: FStat -> Bool+{-# INLINABLE isLinkSt #-} isLinkSt st = stMode st .&. S_IFMT == S_IFLNK -- | Shortcut to @\\ st -> stMode st .&. S_IFMT == S_IFDIR@ isDirSt :: FStat -> Bool+{-# INLINABLE isDirSt #-} isDirSt st = stMode st .&. S_IFMT == S_IFDIR -- | Shortcut to @\\ st -> stMode st .&. S_IFMT == S_IFREG@ isFileSt :: FStat -> Bool+{-# INLINABLE isFileSt #-} isFileSt st = stMode st .&. S_IFMT == S_IFREG -------------------------------------------------------------------------------@@ -143,6 +164,7 @@ -- ("/var/folders/3l/cfdy03vd1gvd1x75gg_js7280000gn/T/Z-IO-bYgZDX",File 13) -- initTempFile :: HasCallStack => Resource (CBytes, File)+{-# INLINABLE initTempFile #-} initTempFile = do d <- liftIO $ Env.getTempDir mkstemp d "Z-IO-" False@@ -155,4 +177,5 @@ -- "/tmp/Z-IO-xfWR0L" -- initTempDir :: HasCallStack => Resource CBytes+{-# INLINABLE initTempDir #-} initTempDir = initResource (Env.getTempDir >>= (`P.join` "Z-IO-") >>= mkdtemp) rmrf
+ test/Z/IO/BIO/BaseSpec.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE OverloadedStrings #-}++module Z.IO.BIO.BaseSpec where++import Control.Concurrent+import Control.Monad+import qualified Codec.Compression.Zlib as TheZlib+import Data.IORef+import qualified Z.Data.Vector as V+import Z.IO.BIO.Zlib+import Z.IO.BIO.Base+import Z.IO+import Test.QuickCheck+import Test.QuickCheck.Function+import Test.QuickCheck.Property+import Test.Hspec+import Test.Hspec.QuickCheck+import Test.HUnit+import System.IO.Unsafe+++spec :: Spec+spec = describe "BIO" . modifyMaxSize (*10) $ do++ describe "decode . encode === id(Base64)" $+ prop "Base64" $ \ xs ->+ let r = unsafePerformIO $ do+ let src = sourceFromList xs+ (rRef, sink) <- sinkToList+ enc <- newBase64Encoder+ dec <- newBase64Decoder+ run_ $ src . enc . dec . sink+ takeMVar rRef+ in V.concat r === V.concat xs++ describe "decode . encode === id(Hex)" $ do+ prop "Hex" $ \ xs upper ->+ let r = unsafePerformIO $ do+ let src = sourceFromList xs+ (rRef, sink) <- sinkToList+ dec <- newHexDecoder+ run_ $ src . hexEncode upper . dec . sink+ takeMVar rRef+ in V.concat r === V.concat xs+
test/Z/IO/BIO/ConcurrentSpec.hs view
@@ -7,8 +7,7 @@ import Control.Monad import Control.Monad.IO.Class import Data.IORef-import Z.IO.BIO.Concurrent-import Z.IO.BIO+import qualified Z.IO.BIO as BIO import Test.QuickCheck import Test.QuickCheck.Function import Test.QuickCheck.Property@@ -22,23 +21,23 @@ spec :: Spec spec = describe "BIO.Concurrent" $ do - prop "zipBIO works like zip on Sources" $ \ xs ys -> monadicIO $ do- (zs :: [(Int, Int)]) <- liftIO . runBIO $ zipBIO (sourceFromList xs) (sourceFromList ys)+ prop "zip works like zip on Sources" $ \ xs ys -> monadicIO $ do+ (zs :: [(Int, Int)]) <- liftIO . BIO.run $ BIO.zip (BIO.sourceFromList xs) (BIO.sourceFromList ys) QM.assert (zs == zip xs ys) it "TQueueNode works as expected" $ do let content = [1..1000] - (sink, src) <- newTQueueNode 2+ (sink, src) <- BIO.newTQueuePair 2 sumRef <- newIORef 0 let producter = do src' <- sourceListWithDelay content- runBIO_ (src' . sink)+ BIO.run_ (src' . sink) let consumer = do- (rRef, sink') <- sinkToList- runBIO_ (src . sink')+ (rRef, sink') <- BIO.sinkToList+ BIO.run_ (src . sink') r <- takeMVar rRef atomicModifyIORef' sumRef $ \ x -> (x + sum r, ()) @@ -56,16 +55,16 @@ it "TBQueueNode works as expected" $ do let content = [1..1000] - (sink, src) <- newTBQueueNode 2 10+ (sink, src) <- BIO.newTBQueuePair 2 10 sumRef <- newIORef 0 let producter = do src' <- sourceListWithDelay content- runBIO_ (src' . sink)+ BIO.run_ (src' . sink) let consumer = do- (rRef, sink') <- sinkToList- runBIO_ (src . sink')+ (rRef, sink') <- BIO.sinkToList+ BIO.run_ (src . sink') r <- takeMVar rRef atomicModifyIORef' sumRef $ \ x -> (x + sum r, ()) @@ -83,17 +82,17 @@ it "TChanNode works as expected" $ do let content = [1..1000] - (sink, srcf) <- newBroadcastTChanNode 2+ (sink, srcf) <- BIO.newBroadcastTChanPair 2 sumRef <- newIORef 0 let producter = do src' <- sourceListWithDelay content- runBIO_ (src' . sink)+ BIO.run_ (src' . sink) let consumer = do- (rRef, sink') <- sinkToList+ (rRef, sink') <- BIO.sinkToList src <- srcf- runBIO_ (src . sink')+ BIO.run_ (src . sink') r <- takeMVar rRef atomicModifyIORef' sumRef $ \ x -> (x + sum r, ()) @@ -109,7 +108,7 @@ s @?= (sum content * 2 * 3) -sourceListWithDelay :: [Int] -> IO (Source Int)+sourceListWithDelay :: [Int] -> IO (BIO.Source Int) sourceListWithDelay xs = do return $ \ k _ -> do forM_ xs $ \ x -> do
test/Z/IO/BIO/ZlibSpec.hs view
@@ -9,6 +9,7 @@ import Data.ByteString.Lazy as BL import Z.Data.Vector as V import Z.IO.BIO.Zlib+import Z.IO.BIO import Z.IO import Test.QuickCheck import Test.QuickCheck.Function
− test/Z/IO/BIOSpec.hs
@@ -1,45 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Z.IO.BIOSpec where--import Control.Concurrent-import Control.Monad-import qualified Codec.Compression.Zlib as TheZlib-import Data.IORef-import qualified Z.Data.Vector as V-import Z.IO.BIO.Zlib-import Z.IO-import Test.QuickCheck-import Test.QuickCheck.Function-import Test.QuickCheck.Property-import Test.Hspec-import Test.Hspec.QuickCheck-import Test.HUnit-import System.IO.Unsafe---spec :: Spec-spec = describe "BIO" . modifyMaxSize (*10) $ do-- describe "decode . encode === id(Base64)" $- prop "Base64" $ \ xs ->- let r = unsafePerformIO $ do- let src = sourceFromList xs- (rRef, sink) <- sinkToList- enc <- newBase64Encoder- dec <- newBase64Decoder- runBIO $ src . enc . dec . sink- takeMVar rRef- in V.concat r === V.concat xs-- describe "decode . encode === id(Hex)" $ do- prop "Hex" $ \ xs upper ->- let r = unsafePerformIO $ do- let src = sourceFromList xs- (rRef, sink) <- sinkToList- let enc = hexEncoder upper- dec <- newHexDecoder- runBIO $ src . enc . dec . sink- takeMVar rRef- in V.concat r === V.concat xs-
test/Z/IO/LowResTimerSpec.hs view
@@ -3,7 +3,7 @@ import Control.Concurrent import Control.Monad import Control.Monad.IO.Class-import Z.Data.PrimRef.PrimIORef+import Z.Data.PrimRef import Z.IO.LowResTimer import Test.Hspec import Test.HUnit@@ -22,7 +22,7 @@ assertEqual "timer manager should start" True running threadDelay 2000000 -- make sure all timers are fired- c' <- readPrimIORef c+ c' <- readPrimRef c assertEqual "timers registration counter" 100000 c' threadDelay 100000 -- another 0.1s@@ -38,7 +38,7 @@ throttledAdd threadDelay 100000 threadDelay 10000000 -- wait 10s here- c' <- readPrimIORef c+ c' <- readPrimRef c assertBool ("throttled add " ++ show c') (5 <= c' && c' <= 8) -- on osx CI threadDelay drift too much it "throttleTrailing" $ do@@ -48,5 +48,5 @@ throttledAdd threadDelay 100000 threadDelay 10000000 -- wait 10s here- c' <- readPrimIORef c+ c' <- readPrimRef c assertBool ("throttled add " ++ show c') (5 <= c' && c' <= 8) -- on osx CI threadDelay drift too muc
test/Z/IO/Network/UDPSpec.hs view
@@ -6,8 +6,8 @@ import Control.Monad import Data.Bits import Data.IORef-import Z.Data.Vector as V-import Z.Data.Vector.Base as V+import qualified Z.Data.Vector as V+import qualified Z.Data.Vector.Base as V import Data.List as List import Foreign.Marshal.Array import Foreign.Ptr
test/Z/IO/ResourceSpec.hs view
@@ -5,7 +5,7 @@ import Control.Concurrent import Control.Exception import Control.Monad-import Z.Data.PrimRef.PrimIORef+import Z.Data.PrimRef import Z.IO.Resource as R import Test.Hspec import Test.HUnit@@ -30,20 +30,20 @@ threadDelay 1000000 - r <- readPrimIORef resCounter+ r <- readPrimRef resCounter assertEqual "pool should keep returned resources alive" 100 r threadDelay 5000000 -- after 5s, 200 thread should release all resources - w <- readPrimIORef workerCounter+ w <- readPrimRef workerCounter assertEqual "worker should be able to get resource" 200 w - r <- readPrimIORef resCounter+ r <- readPrimRef resCounter assertEqual "pool should reap unused resources" 0 r -- Let's test again - writePrimIORef workerCounter 0+ writePrimRef workerCounter 0 forM_ [1..200] $ \ k -> forkIO. R.withSimplePool pool $ \ i -> do atomicAddCounter_ workerCounter 1@@ -51,15 +51,15 @@ threadDelay 1000000 - r <- readPrimIORef resCounter+ r <- readPrimRef resCounter assertEqual "pool should keep returned resources alive" 100 r threadDelay 5000000 - w <- readPrimIORef workerCounter+ w <- readPrimRef workerCounter assertEqual "worker should be able to get resource" 200 w - r <- readPrimIORef resCounter+ r <- readPrimRef resCounter assertEqual "pool should reap unused resources" 0 r it "resource pool under exceptions" $ do@@ -77,5 +77,5 @@ threadDelay 5000000 - r <- readPrimIORef resCounter+ r <- readPrimRef resCounter assertEqual "pool should reap unused resources" 0 r