blockio-uring 0.1.0.3 → 0.2.0.0
raw patch · 7 files changed
+106/−24 lines, 7 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ System.IO.BlockIO: [ioctxIOWaitMetrics] :: IOCtxParams -> !Bool
- System.IO.BlockIO: IOCtxParams :: !Int -> !Int -> IOCtxParams
+ System.IO.BlockIO: IOCtxParams :: !Int -> !Int -> !Bool -> IOCtxParams
Files
- CHANGELOG.md +26/−0
- benchmark/Bench.hs +2/−2
- blockio-uring.cabal +6/−4
- src/System/IO/BlockIO.hs +34/−14
- src/System/IO/BlockIO/URing.hs +18/−1
- src/System/IO/BlockIO/URingFFI.hsc +17/−0
- test/test.hs +3/−3
CHANGELOG.md view
@@ -1,5 +1,31 @@ # Revision history for blockio-uring +## 0.2.0.0 -- 2026-04-30++### Breaking changes++* `IOCtxParams` has a new record field for configuring the use of IOWAIT metrics+ (see below).++### New features++* Support enabling/disabling IOWAIT metrics. See [issue+ #55](https://github.com/well-typed/blockio-uring/issues/55) and [PR+ #57](https://github.com/well-typed/blockio-uring/pull/57).++### Minor changes++None++### Bug fixes++* On systems with limited CPU and memory resources, `submitIO` could reliably+ fail with `EFAULT` error numbers because of frequent reaping and rescheduling+ of Haskell's lightweight threads. The bug is fixed by always running+ `submitIO` in a fresh bound thread. See [issue+ #58](https://github.com/well-typed/blockio-uring/issues/58) and [PR+ #60](https://github.com/well-typed/blockio-uring/pull/60).+ ## 0.1.0.3 -- 2026-03-12 * PATCH: support `ghc-9.14`. See [PR
benchmark/Bench.hs view
@@ -127,7 +127,8 @@ lastBlock = fromIntegral (size `div` 4096 - 1) params = IOCtxParams { ioctxBatchSizeLimit = 64,- ioctxConcurrencyLimit = 64 * 4+ ioctxConcurrencyLimit = 64 * 4,+ ioctxIOWaitMetrics = True } ntasks = 4 * ncaps batchsz = 32@@ -243,4 +244,3 @@ (i, rng') = uniformR (0, Set.size xs - 1) rng !x = Set.elemAt i xs !xs' = Set.deleteAt i xs-
blockio-uring.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.4 name: blockio-uring-version: 0.1.0.3+version: 0.2.0.0 synopsis: Perform batches of asynchronous disk IO operations. description: This library supports disk I/O operations using the Linux io_uring API. The@@ -24,7 +24,9 @@ copyright: (c) Well-Typed LLP 2022 - 2025 category: System build-type: Simple-tested-with: GHC ==9.2 || ==9.4 || ==9.6 || ==9.8 || ==9.10 || ==9.12 || ==9.14+tested-with:+ GHC ==9.2 || ==9.4 || ==9.6 || ==9.8 || ==9.10 || ==9.12 || ==9.14+ extra-doc-files: CHANGELOG.md README.md@@ -36,7 +38,7 @@ source-repository this type: git location: https://github.com/well-typed/blockio-uring- tag: blockio-uring-0.1.0.3+ tag: blockio-uring-0.2.0.0 common warnings ghc-options:@@ -88,7 +90,7 @@ , base <5 , containers , primitive- , random ^>= 1.3+ , random ^>=1.3 , time , unix ^>=2.8.7.0 , vector
src/System/IO/BlockIO.hs view
@@ -29,7 +29,7 @@ import Control.Monad import Control.Monad.Primitive import Control.Concurrent (forkOn, myThreadId, threadCapability,- getNumCapabilities)+ getNumCapabilities, runInBoundThread) import Control.Concurrent.MVar import Control.Concurrent.QSemN import Control.Concurrent.Chan@@ -122,7 +122,16 @@ -- If a use of 'submitIO' would lead to this limit being exceeded, then the -- call to 'submitIO' will block until enough I/O batches have been -- processed.- ioctxConcurrencyLimit :: !Int+ ioctxConcurrencyLimit :: !Int,++ -- | Enable or disable IOWAIT metrics+ --+ -- If @io_uring_set_iowait(3)@ is available, then it will be called with+ -- this value to either enable ('True') or disable ('False') IOWAIT+ -- metrics. This is innocuous in terms of performance but changes how CPU+ -- idle time is reported. @io_uring_set_iowait(3)@ is available for+ -- liburing versions >= 2.10 and Linux kernels versions >= 6.15.+ ioctxIOWaitMetrics :: !Bool } deriving stock Show @@ -132,7 +141,8 @@ defaultIOCtxParams = IOCtxParams { ioctxBatchSizeLimit = 64,- ioctxConcurrencyLimit = 64 * 3+ ioctxConcurrencyLimit = 64 * 3,+ ioctxIOWaitMetrics = True } validateIOCtxParams :: IOCtxParams -> Maybe String@@ -179,11 +189,13 @@ initIOCapCtx :: IOCtxParams -> CapNo -> IO IOCapCtx initIOCapCtx IOCtxParams { ioctxBatchSizeLimit,- ioctxConcurrencyLimit+ ioctxConcurrencyLimit,+ ioctxIOWaitMetrics } capno = do mask_ $ do ioctxQSemN <- newQSemN ioctxConcurrencyLimit uring <- URing.setupURing (URing.URingParams ioctxBatchSizeLimit ioctxConcurrencyLimit)+ URing.setIOWait uring ioctxIOWaitMetrics ioctxURing <- newMVar (Just uring) ioctxChanIOBatch <- newChan ioctxChanIOBatchIx <- newChan@@ -284,16 +296,24 @@ -- at least the target number in flight at once. -- submitIO :: IOCtx -> V.Vector (IOOp RealWorld) -> IO (VU.Vector IOResult)-submitIO (IOCtx capctxs) !ioops = do- -- Find out which capability the thread is currently running on and use- -- that one. It does _not matter_ for correctness if the thread is migrated- -- while the I/O is submitted or when waiting for completion. Migration- -- happens sufficiently infrequently that it should not be a performance- -- problem.- tid <- myThreadId- (capno, _) <- threadCapability tid- let !capctx = capctxs V.! (capno `mod` V.length capctxs)- submitCapIO capctx ioops+submitIO (IOCtx capctxs) !ioops =+ -- Requests have to be submitted from within a bound thread, or we might get+ -- @EFAULT@ errors. See issue #58.+ --+ -- TODO <https://github.com/well-typed/blockio-uring/issues/61>: ideally,+ -- for performance, it would be better to run a smaller section of the+ -- 'submitIO' code inside 'runInBoundThread'. However, it is not 100% clear+ -- what the critical code section is that /has/ to run in a bound thread .+ -- So, for now we pick the safe option of running the entirety of 'submitIO'+ -- in a bound thread.+ runInBoundThread $ do+ -- Find out which capability the thread is currently running on and use+ -- that one. It does _not matter_ for correctness that the thread submits+ -- IO to that same thread's capability's ring, but it is more performant.+ tid <- myThreadId+ (capno, _) <- threadCapability tid+ let !capctx = capctxs V.! (capno `mod` V.length capctxs)+ submitCapIO capctx ioops submitCapIO :: IOCapCtx -> V.Vector (IOOp RealWorld) -> IO (VU.Vector IOResult) submitCapIO ioctx@IOCapCtx {ioctxBatchSizeLimit'} !ioops
src/System/IO/BlockIO/URing.hs view
@@ -7,6 +7,7 @@ setupURing, closeURing, withURing,+ setIOWait, IOOpId(..), prepareRead, prepareWrite,@@ -94,6 +95,10 @@ withURing params = bracket (setupURing params) closeURing +setIOWait :: URing -> Bool -> IO ()+setIOWait URing {uringptr} b =+ callIfSupported_ "setIOWAIT" $+ FFI.io_uring_set_iowait uringptr (if b then 1 else 0) -- -- Submitting I/O@@ -231,6 +236,19 @@ (Errno (-res)) Nothing Nothing +callIfSupported_ :: String -> IO CInt -> IO ()+callIfSupported_ label action = do+ res <- action+ if Errno (-res) == eOPNOTSUPP+ then pure ()+ else+ when (res < 0) $+ throwIO $+ errnoToIOError+ label+ (Errno (-res))+ Nothing Nothing+ throwErrResIfNull :: String -> IOErrorType -> String -> IO (Ptr a) -> IO (Ptr a) throwErrResIfNull location ioErrorType description action = do res <- action@@ -243,4 +261,3 @@ Nothing Nothing) description else return res-
src/System/IO/BlockIO/URingFFI.hsc view
@@ -125,3 +125,20 @@ foreign import capi unsafe "liburing.h io_uring_cqe_seen" io_uring_cqe_seen :: Ptr URing -> Ptr URingCQE -> IO () ++--+-- Calling @io_uring_set_iowait(3)@ if available+--++-- | Available only on liburing >= 2.10 and kernel >= 6.15.+--+-- See @io_uring_set_iowait(3)@ man page.+#ifdef IORING_FEAT_NO_IOWAIT+#if IORING_FEAT_NO_IOWAIT+foreign import capi unsafe "liburing.h io_uring_set_iowait"+ io_uring_set_iowait :: Ptr URing -> CBool -> IO CInt+#endif+#else+io_uring_set_iowait :: Ptr URing -> CBool -> IO CInt+io_uring_set_iowait _ _ = pure #{const EOPNOTSUPP}+#endif
test/test.hs view
@@ -98,9 +98,9 @@ concurrencyLimitBoundsExcl batchSizeLimit = (batchSizeLimit, 2^(16::Int)) instance Arbitrary IOCtxParams where- arbitrary = IOCtxParams <$> genLimit <*> genLimit- shrink (IOCtxParams a b) =- [ IOCtxParams a' b' | (a', b') <- liftShrink2 shrinkLimit shrinkLimit (a, b) ]+ arbitrary = IOCtxParams <$> genLimit <*> genLimit <*> arbitrary+ shrink (IOCtxParams a b i) =+ [ IOCtxParams a' b' i' | (a', (b', i')) <- liftShrink2 shrinkLimit (liftShrink2 shrinkLimit shrink) (a, (b, i)) ] genLimit :: Gen Int genLimit = frequency [