Copyright | (c) Dong Han 2017-2018 |
---|---|
License | BSD |
Maintainer | winterland1989@gmail.com |
Stability | experimental |
Portability | non-portable |
Safe Haskell | None |
Language | Haskell2010 |
This module provide IO manager which bridge libuv's async interface with ghc's light weight thread.
The main procedures for doing event IO is:
- Allocate a slot number using
allocSlot
. - Prepare you IO buffer and write them to uv loop with
pokeBufferTable
(both read and write). - Block your thread with a
MVar
, usinggetBlockMVar
to get it. - Read the result with
getResult
, for read it's the read bytes number, for write it will be zero. UsethrowIfError
to guard error situations. - Return the slot back uv manager with
freeSlot
.
Usually slots are cache in the IO device so that you don't have to allocate new one before each IO operation. Check System.IO.Socket.TCP as an example.
Synopsis
- data UVManager
- getUVManager :: IO UVManager
- getBlockMVar :: UVManager -> UVSlot -> IO (MVar Int)
- peekBufferTable :: UVManager -> UVSlot -> IO Int
- pokeBufferTable :: UVManager -> UVSlot -> Ptr Word8 -> Int -> IO ()
- withUVManager :: HasCallStack => UVManager -> (Ptr UVLoop -> IO a) -> IO a
- withUVManager_ :: HasCallStack => UVManager -> IO a -> IO a
- getUVSlot :: HasCallStack => UVManager -> IO UVSlotUnSafe -> IO UVSlot
- withUVRequest :: HasCallStack => UVManager -> (Ptr UVLoop -> IO UVSlotUnSafe) -> IO Int
- withUVRequest_ :: HasCallStack => UVManager -> (Ptr UVLoop -> IO UVSlotUnSafe) -> IO ()
- withUVRequest' :: HasCallStack => UVManager -> (Ptr UVLoop -> IO UVSlotUnSafe) -> (Int -> IO b) -> IO b
- withUVRequestEx :: HasCallStack => UVManager -> (Ptr UVLoop -> IO UVSlotUnSafe) -> (Int -> IO ()) -> IO Int
- initUVStream :: HasCallStack => (Ptr UVLoop -> Ptr UVHandle -> IO ()) -> UVManager -> Resource UVStream
- data UVStream = UVStream {}
- forkBa :: IO () -> IO ThreadId
Documentation
getBlockMVar :: UVManager -> UVSlot -> IO (MVar Int) Source #
Get MVar
from blocking table with given slot.
pokeBufferTable :: UVManager -> UVSlot -> Ptr Word8 -> Int -> IO () Source #
Poke a prepared buffer and size into loop data under given slot.
NOTE, this action is not protected with 'withUVManager_ for effcient reason, you should merge this action with other uv action and put them together inside a 'withUVManager_ or 'withUVManager\''. for example:
... withUVManager_ uvm $ do pokeBufferTable uvm slot buf len uvReadStart handle ...
withUVManager :: HasCallStack => UVManager -> (Ptr UVLoop -> IO a) -> IO a Source #
Lock an uv mananger, so that we can safely mutate its uv_loop's state.
libuv is not thread safe, use this function to perform any action which will mutate uv_loop's state.
withUVManager_ :: HasCallStack => UVManager -> IO a -> IO a Source #
Lock an uv mananger, so that we can safely mutate its uv_loop's state.
Some action did not request uv_loop pointer explicitly, but will mutate uv_loop underhood, for example:
uv_read_start
. These actions have to be protected by locking the uv_loop.
In fact most of the libuv's functions are not thread safe, so watch out!
getUVSlot :: HasCallStack => UVManager -> IO UVSlotUnSafe -> IO UVSlot Source #
Run a libuv FFI to get a UVSlotUnSafe
(which may exceed block table size),
resize the block table in that case, so that the returned slot always has an
accompanying MVar
in block table.
Always use this function to turn an UVSlotUnsafe
into UVSlot
, so that the block
table size synchronize with libuv side's slot table.
request based async function helper
withUVRequest :: HasCallStack => UVManager -> (Ptr UVLoop -> IO UVSlotUnSafe) -> IO Int Source #
Exception safe uv request helper
This helper will run a libuv's async function, which will return a
libuv side's slot, then we will accommodate a MVar
in block table and
wait on that MVar
, until the async function finished or an exception
is received, in later case we will call cancelUVReq
to cancel the on-going
async function with best efforts,
withUVRequest_ :: HasCallStack => UVManager -> (Ptr UVLoop -> IO UVSlotUnSafe) -> IO () Source #
Same with withUVRequest
but disgard the result.
:: HasCallStack | |
=> UVManager | |
-> (Ptr UVLoop -> IO UVSlotUnSafe) | |
-> (Int -> IO b) | convert function |
-> IO b |
Same with withUVRequest
but apply an convert function to result.
The convert function have all access to the returned value including negative ones, it's convert funtions's responsiblity to throw an exception if appropriate.
withUVRequestEx :: HasCallStack => UVManager -> (Ptr UVLoop -> IO UVSlotUnSafe) -> (Int -> IO ()) -> IO Int Source #
Same with withUVRequest
, but will also run an extra cleanup function
if async exception hit this thread but the async action is already successfully performed,
e.g. release result memory.
uv_stream abstraction
initUVStream :: HasCallStack => (Ptr UVLoop -> Ptr UVHandle -> IO ()) -> UVManager -> Resource UVStream Source #
Safely lock an uv manager and perform uv_handle initialization.
Initialization an UV stream usually take two step:
- allocate an uv_stream struct with proper size
- lock a particular uv_loop from a uv manager, and perform custom initialization, such as
uv_tcp_init
.
And this is what initUVStream
do, all you need to do is to provide the manager you want to hook the handle
onto(usually the one on the same capability, i.e. the one obtained by getUVManager
),
and provide a custom initialization function.
A haskell data type wrap an uv_stream_t
inside
UVStream
DO NOT provide thread safety! Use UVStream
concurrently in multiple
threads will lead to undefined behavior.
concurrent helpers
forkBa :: IO () -> IO ThreadId Source #
Fork a new GHC thread with active load-balancing.
Using libuv based IO solution has a disadvantage that file handlers are bound to certain uv_loop, thus certain uv mananger/capability. Worker threads that migrate to other capability will lead contention since various APIs here is protected by manager's lock, this makes GHC's work-stealing strategy unsuitable for certain workload, such as a webserver. 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).