Skip to content

Commit 6276783

Browse files
authored
Merge pull request #742 from IntersectMBO/wenkokke/bloomfilter-salt
feat(bloomfilter): add salt
2 parents 2e7635c + 0824caf commit 6276783

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+1408
-520
lines changed

bench-unions/Bench/Unions.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ doSetup gopts = do
292292
createDirectoryIfMissing True $ rootDir gopts
293293

294294
-- Populate the specified number of tables
295-
LSM.withSession (rootDir gopts) $ \session -> do
295+
LSM.withOpenSession (rootDir gopts) $ \session -> do
296296
-- Create a "baseline" table
297297
--
298298
-- We create a single table that *already has* all the same key value pairs
@@ -337,7 +337,7 @@ tableRange gopts =
337337
-- | Count duplicate keys in all tables that will be unioned together
338338
doCollisionAnalysis :: GlobalOpts -> IO ()
339339
doCollisionAnalysis gopts = do
340-
LSM.withSession (rootDir gopts) $ \session -> do
340+
LSM.withOpenSession (rootDir gopts) $ \session -> do
341341
seenRef <- newIORef Set.empty
342342
dupRef <- newIORef Set.empty
343343

@@ -381,7 +381,7 @@ doRun gopts opts = do
381381
withFile dataPath WriteMode $ \h -> do
382382
hPutStrLn h "# iteration \t baseline (ops/sec) \t union (ops/sec) \t union debt"
383383

384-
LSM.withSession (rootDir gopts) $ \session -> do
384+
LSM.withOpenSession (rootDir gopts) $ \session -> do
385385
-- Load the baseline table
386386
LSM.withTableFromSnapshot session baselineTableName label
387387
$ \baselineTable -> do

bench/macro/lsm-tree-bench-bloomfilter.hs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ benchmarks = do
108108
benchmark "bloomQueries"
109109
"(this is the batch lookup, less the cost of computing and hashing the keys)"
110110
(benchInBatches benchmarkBatchSize rng0
111-
(\ks -> Bloom.bloomQueries vbs ks `seq` ()))
111+
(\ks -> Bloom.bloomQueries benchSalt vbs ks `seq` ()))
112112
(fromIntegralChecked benchmarkNumLookups)
113113
hashcost
114114
0
@@ -200,6 +200,8 @@ totalNumEntriesSanityCheck l1 filterSizes =
200200
==
201201
sum [ 2^l1 * sizeFactor | (_, sizeFactor, _) <- filterSizes ]
202202

203+
benchSalt :: Bloom.Salt
204+
benchSalt = 4
203205

204206
-- | Input environment for benchmarking 'Bloom.elemMany'.
205207
--
@@ -223,7 +225,10 @@ elemManyEnv :: [BloomFilterSizeInfo]
223225
elemManyEnv filterSizes rng0 =
224226
stToIO $ do
225227
-- create the filters
226-
mbs <- sequence [ Bloom.new bsize | (_, _, bsize) <- filterSizes ]
228+
mbs <- sequence
229+
[ Bloom.new bsize benchSalt
230+
| (_, _, bsize) <- filterSizes
231+
]
227232
-- add elements
228233
foldM_
229234
(\rng (i, mb) -> do
@@ -264,7 +269,7 @@ benchInBatches !b !rng0 !action =
264269
benchMakeHashes :: Vector (Bloom SerialisedKey) -> BatchBench
265270
benchMakeHashes !_bs !ks =
266271
let khs :: VP.Vector (Bloom.Hashes SerialisedKey)
267-
!khs = V.convert (V.map Bloom.hashes ks)
272+
!khs = V.convert (V.map (Bloom.hashesWithSalt benchSalt) ks)
268273
in khs `seq` ()
269274

270275
-- | This gives us a combined cost of calculating the series of keys, their
@@ -273,7 +278,7 @@ benchMakeHashes !_bs !ks =
273278
benchElemHashes :: Vector (Bloom SerialisedKey) -> BatchBench
274279
benchElemHashes !bs !ks =
275280
let khs :: VP.Vector (Bloom.Hashes SerialisedKey)
276-
!khs = V.convert (V.map Bloom.hashes ks)
281+
!khs = V.convert (V.map (Bloom.hashesWithSalt benchSalt) ks)
277282
in V.foldl'
278283
(\_ b -> VP.foldl'
279284
(\_ kh -> Bloom.elemHashes b kh `seq` ())

bench/macro/lsm-tree-bench-lookups.hs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ entryBitsWithOverhead = entryBits -- key and value size
129129
numEntriesFitInPage :: Fractional a => a
130130
numEntriesFitInPage = fromIntegral unusedPageBits / fromIntegral entryBitsWithOverhead
131131

132+
benchSalt :: Bloom.Salt
133+
benchSalt = 4
134+
132135
benchmarks :: Run.RunDataCaching -> IO ()
133136
benchmarks !caching = withFS $ \hfs hbio -> do
134137
#ifdef NO_IGNORE_ASSERTS
@@ -351,7 +354,7 @@ lookupsEnv runSizes keyRng0 hfs hbio caching = do
351354

352355
-- create the runs
353356
rbs <- sequence
354-
[ RunBuilder.new hfs hbio
357+
[ RunBuilder.new hfs hbio benchSalt
355358
RunParams {
356359
runParamCaching = caching,
357360
runParamAlloc = RunAllocFixed benchmarkNumBitsPerEntry,
@@ -428,7 +431,7 @@ benchBloomQueries !bs !keyRng !n
428431
| n <= 0 = ()
429432
| otherwise =
430433
let (!ks, !keyRng') = genLookupBatch keyRng benchmarkGenBatchSize
431-
in bloomQueries bs ks `seq`
434+
in bloomQueries benchSalt bs ks `seq`
432435
benchBloomQueries bs keyRng' (n-benchmarkGenBatchSize)
433436

434437
-- | This gives us the combined cost of calculating batches of keys, performing
@@ -445,7 +448,7 @@ benchIndexSearches !arenaManager !bs !ics !hs !keyRng !n
445448
| n <= 0 = pure ()
446449
| otherwise = do
447450
let (!ks, !keyRng') = genLookupBatch keyRng benchmarkGenBatchSize
448-
!rkixs = bloomQueries bs ks
451+
!rkixs = bloomQueries benchSalt bs ks
449452
!_ioops <- withArena arenaManager $ \arena -> stToIO $ indexSearches arena ics hs ks rkixs
450453
benchIndexSearches arenaManager bs ics hs keyRng' (n-benchmarkGenBatchSize)
451454

@@ -463,7 +466,7 @@ benchPrepLookups !arenaManager !bs !ics !hs !keyRng !n
463466
| n <= 0 = pure ()
464467
| otherwise = do
465468
let (!ks, !keyRng') = genLookupBatch keyRng benchmarkGenBatchSize
466-
(!_rkixs, !_ioops) <- withArena arenaManager $ \arena -> stToIO $ prepLookups arena bs ics hs ks
469+
(!_rkixs, !_ioops) <- withArena arenaManager $ \arena -> stToIO $ prepLookups arena benchSalt bs ics hs ks
467470
benchPrepLookups arenaManager bs ics hs keyRng' (n-benchmarkGenBatchSize)
468471

469472
-- | This gives us the combined cost of calculating batches of keys, and
@@ -489,7 +492,7 @@ benchLookupsIO !hbio !arenaManager !resolve !wb !wbblobs !rs !bs !ics !hs =
489492
| otherwise = do
490493
let (!ks, !keyRng') = genLookupBatch keyRng benchmarkGenBatchSize
491494
!_ <- lookupsIOWithWriteBuffer
492-
hbio arenaManager resolve wb wbblobs rs bs ics hs ks
495+
hbio arenaManager resolve benchSalt wb wbblobs rs bs ics hs ks
493496
go keyRng' (n-benchmarkGenBatchSize)
494497

495498
{-------------------------------------------------------------------------------
@@ -524,7 +527,7 @@ classifyLookups !bs !keyRng0 !n0 =
524527
| otherwise =
525528
unsafePerformIO (putStr ".") `seq`
526529
let (!ks, !keyRng') = genLookupBatch keyRng benchmarkGenBatchSize
527-
!rkixs = bloomQueries bs ks
530+
!rkixs = bloomQueries benchSalt bs ks
528531
in loop (positives + VP.length rkixs) keyRng' (n-benchmarkGenBatchSize)
529532

530533
-- | Fill a mutable vector with uniformly random values.

bench/macro/lsm-tree-bench-wp8.hs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ benchTableConfig :: LSM.TableConfig
8787
benchTableConfig =
8888
LSM.defaultTableConfig {LSM.confFencePointerIndex = LSM.CompactIndex}
8989

90+
benchSalt :: LSM.Salt
91+
benchSalt = 4
92+
9093
-------------------------------------------------------------------------------
9194
-- Keys and values
9295
-------------------------------------------------------------------------------
@@ -413,7 +416,7 @@ doSetup' gopts opts = do
413416

414417
let name = LSM.toSnapshotName "bench"
415418

416-
LSM.withSession (mkTracer gopts) hasFS hasBlockIO (FS.mkFsPath []) $ \session -> do
419+
LSM.withOpenSession (mkTracer gopts) hasFS hasBlockIO benchSalt (FS.mkFsPath []) $ \session -> do
417420
tbl <- LSM.newTableWith @IO @K @V @B (mkTableConfigSetup gopts opts benchTableConfig) session
418421

419422
forM_ (groupsOfN 256 [ 0 .. initialSize gopts ]) $ \batch -> do
@@ -575,7 +578,7 @@ doRun gopts opts = do
575578

576579
let name = LSM.toSnapshotName "bench"
577580

578-
LSM.withSession (mkTracer gopts) hasFS hasBlockIO (FS.mkFsPath []) $ \session ->
581+
LSM.withOpenSession (mkTracer gopts) hasFS hasBlockIO benchSalt (FS.mkFsPath []) $ \session ->
579582
withLatencyHandle $ \h -> do
580583
-- open snapshot
581584
-- In checking mode we start with an empty table, since our pure

bench/micro/Bench/Database/LSMTree.hs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Control.DeepSeq
66
import Control.Exception
77
import Control.Tracer
88
import Criterion.Main
9+
import qualified Data.BloomFilter.Hash as Bloom
910
import Data.ByteString.Short (ShortByteString)
1011
import qualified Data.ByteString.Short as SBS
1112
import Data.Foldable
@@ -82,6 +83,9 @@ benchConfig = defaultTableConfig
8283
, confFencePointerIndex = CompactIndex
8384
}
8485

86+
benchSalt :: Bloom.Salt
87+
benchSalt = 4
88+
8589
{-------------------------------------------------------------------------------
8690
Large Value vs. Small Value Blob
8791
-------------------------------------------------------------------------------}
@@ -135,7 +139,7 @@ benchLargeValueVsSmallValueBlob =
135139

136140
initialise inss = do
137141
(tmpDir, hfs, hbio) <- mkFiles
138-
s <- openSession nullTracer hfs hbio (FS.mkFsPath [])
142+
s <- openSession nullTracer hfs hbio benchSalt (FS.mkFsPath [])
139143
t <- newTableWith benchConfig s
140144
V.mapM_ (inserts t) inss
141145
pure (tmpDir, hfs, hbio, s, t)
@@ -220,7 +224,7 @@ benchCursorScanVsRangeLookupScan =
220224

221225
initialise inss = do
222226
(tmpDir, hfs, hbio) <- mkFiles
223-
s <- openSession nullTracer hfs hbio (FS.mkFsPath [])
227+
s <- openSession nullTracer hfs hbio benchSalt (FS.mkFsPath [])
224228
t <- newTableWith benchConfig s
225229
V.mapM_ (inserts t) inss
226230
pure (tmpDir, hfs, hbio, s, t)
@@ -265,7 +269,7 @@ benchInsertBatches =
265269

266270
initialise = do
267271
(tmpDir, hfs, hbio) <- mkFiles
268-
s <- openSession nullTracer hfs hbio (FS.mkFsPath [])
272+
s <- openSession nullTracer hfs hbio benchSalt (FS.mkFsPath [])
269273
t <- newTableWith _benchConfig s
270274
pure (tmpDir, hfs, hbio, s, t)
271275

@@ -451,7 +455,7 @@ mkTable ::
451455
, Table IO K V3 B3
452456
)
453457
mkTable hfs hbio conf = do
454-
sesh <- openSession nullTracer hfs hbio (FS.mkFsPath [])
458+
sesh <- openSession nullTracer hfs hbio benchSalt (FS.mkFsPath [])
455459
t <- newTableWith conf sesh
456460
pure (sesh, t)
457461

bench/micro/Bench/Database/LSMTree/Internal/BloomFilter.hs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ benchmarks = bgroup "Bench.Database.LSMTree.Internal.BloomFilter" [
4545
]
4646
]
4747

48+
benchSalt :: Bloom.Salt
49+
benchSalt = 4
50+
4851
-- | Input environment for benchmarking 'Bloom.elem'.
4952
elemEnv ::
5053
Double -- ^ False positive rate
@@ -61,7 +64,7 @@ elemEnv fpr nbloom nelemsPositive nelemsNegative = do
6164
$ uniformWithoutReplacement @UTxOKey g1 (nbloom + nelemsNegative)
6265
ys2 = sampleUniformWithReplacement @UTxOKey g2 nelemsPositive xs
6366
zs = shuffle (ys1 ++ ys2) g3
64-
pure ( Bloom.fromList (Bloom.policyForFPR fpr) (fmap serialiseKey xs)
67+
pure ( Bloom.fromList (Bloom.policyForFPR fpr) benchSalt (fmap serialiseKey xs)
6568
, fmap serialiseKey zs
6669
)
6770

@@ -86,5 +89,5 @@ constructBloom ::
8689
constructBloom fpr m =
8790
-- For faster construction, avoid going via lists and use Bloom.create,
8891
-- traversing the map inserting the keys
89-
Bloom.create (Bloom.sizeForFPR fpr (Map.size m)) $ \b ->
92+
Bloom.create (Bloom.sizeForFPR fpr (Map.size m)) benchSalt $ \b ->
9093
BiFold.bifoldMap (\k -> Bloom.insert b k) (\_v -> pure ()) m

bench/micro/Bench/Database/LSMTree/Internal/Lookup.hs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import Database.LSMTree.Extras.Random (frequency, randomByteStringR,
2121
import Database.LSMTree.Extras.UTxO
2222
import Database.LSMTree.Internal.Arena (ArenaManager, closeArena,
2323
newArena, newArenaManager, withArena)
24+
import qualified Database.LSMTree.Internal.BloomFilter as Bloom
2425
import Database.LSMTree.Internal.Entry (Entry (..), NumEntries (..))
2526
import Database.LSMTree.Internal.Index as Index
2627
import Database.LSMTree.Internal.Lookup (bloomQueries, indexSearches,
@@ -84,6 +85,9 @@ benchmarks = bgroup "Bench.Database.LSMTree.Internal.Lookup" [
8485
}
8586
]
8687

88+
benchSalt :: Bloom.Salt
89+
benchSalt = 4
90+
8791
benchLookups :: Config -> Benchmark
8892
benchLookups conf@Config{name} =
8993
withEnv $ \ ~(_dir, arenaManager, _hasFS, hasBlockIO, wbblobs, rs, ks) ->
@@ -96,23 +100,23 @@ benchLookups conf@Config{name} =
96100
-- The bloomfilter is queried for all lookup keys. The result is an
97101
-- unboxed vector, so only use @whnf@.
98102
bench "Bloomfilter query" $
99-
whnf (\ks' -> bloomQueries blooms ks') ks
103+
whnf (\ks' -> bloomQueries benchSalt blooms ks') ks
100104
-- The compact index is only searched for (true and false) positive
101105
-- lookup keys. We use whnf here because the result is
102-
, env (pure $ bloomQueries blooms ks) $ \rkixs ->
106+
, env (pure $ bloomQueries benchSalt blooms ks) $ \rkixs ->
103107
bench "Compact index search" $
104108
whnfAppIO (\ks' -> withArena arenaManager $ \arena -> stToIO $ indexSearches arena indexes kopsFiles ks' rkixs) ks
105109
-- prepLookups combines bloom filter querying and index searching.
106110
-- The implementation forces the results to WHNF, so we use
107111
-- whnfAppIO here instead of nfAppIO.
108112
, bench "Lookup preparation in memory" $
109-
whnfAppIO (\ks' -> withArena arenaManager $ \arena -> stToIO $ prepLookups arena blooms indexes kopsFiles ks') ks
113+
whnfAppIO (\ks' -> withArena arenaManager $ \arena -> stToIO $ prepLookups arena benchSalt blooms indexes kopsFiles ks') ks
110114
-- Submit the IOOps we get from prepLookups to HasBlockIO. We use
111115
-- perRunEnv because IOOps contain mutable buffers, so we want fresh
112116
-- ones for each run of the benchmark. We manually evaluate the
113117
-- result to WHNF since it is unboxed vector.
114118
, bench "Submit IOOps" $
115-
perRunEnv (withArena arenaManager $ \arena -> stToIO $ prepLookups arena blooms indexes kopsFiles ks) $ \ ~(_rkixs, ioops) -> do
119+
perRunEnv (withArena arenaManager $ \arena -> stToIO $ prepLookups arena benchSalt blooms indexes kopsFiles ks) $ \ ~(_rkixs, ioops) -> do
116120
!_ioress <- FS.submitIO hasBlockIO ioops
117121
pure ()
118122
-- When IO result have been collected, intra-page lookups searches
@@ -125,7 +129,7 @@ benchLookups conf@Config{name} =
125129
, bench "Perform intra-page lookups" $
126130
perRunEnvWithCleanup
127131
( do arena <- newArena arenaManager
128-
(rkixs, ioops) <- stToIO (prepLookups arena blooms indexes kopsFiles ks)
132+
(rkixs, ioops) <- stToIO (prepLookups arena benchSalt blooms indexes kopsFiles ks)
129133
ioress <- FS.submitIO hasBlockIO ioops
130134
pure (rkixs, ioops, ioress, arena)
131135
)
@@ -141,7 +145,7 @@ benchLookups conf@Config{name} =
141145
, bench "Lookups in IO" $
142146
whnfAppIO (\ks' -> lookupsIOWithWriteBuffer
143147
hasBlockIO arenaManager resolveV
144-
WB.empty wbblobs
148+
benchSalt WB.empty wbblobs
145149
rs blooms indexes kopsFiles ks') ks
146150
]
147151
-- TODO: consider adding benchmarks that also use the write buffer
@@ -192,7 +196,7 @@ lookupsInBatchesEnv Config {..} = do
192196
wbblobs <- WBB.new hasFS (FS.mkFsPath ["0.wbblobs"])
193197
wb <- WB.fromMap <$> traverse (traverse (WBB.addBlob hasFS wbblobs)) storedKeys
194198
let fsps = RunFsPaths (FS.mkFsPath []) (RunNumber 0)
195-
r <- Run.fromWriteBuffer hasFS hasBlockIO runParams fsps wb wbblobs
199+
r <- Run.fromWriteBuffer hasFS hasBlockIO benchSalt runParams fsps wb wbblobs
196200
let NumEntries nentriesReal = Run.size r
197201
assertEqual nentriesReal nentries $ pure ()
198202
-- 42 to 43 entries per page

bench/micro/Bench/Database/LSMTree/Internal/Merge.hs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import Database.LSMTree.Extras.Orphans ()
1616
import qualified Database.LSMTree.Extras.Random as R
1717
import Database.LSMTree.Extras.RunData
1818
import Database.LSMTree.Extras.UTxO
19+
import qualified Database.LSMTree.Internal.BloomFilter as Bloom
1920
import Database.LSMTree.Internal.Entry
2021
import qualified Database.LSMTree.Internal.Index as Index (IndexType (Compact))
2122
import Database.LSMTree.Internal.Merge (MergeType (..))
@@ -220,6 +221,9 @@ benchmarks = bgroup "Bench.Database.LSMTree.Internal.Merge" [
220221
| w <- weights
221222
]
222223

224+
benchSalt :: Bloom.Salt
225+
benchSalt = 4
226+
223227
runParams :: RunBuilder.RunParams
224228
runParams =
225229
RunBuilder.RunParams {
@@ -273,7 +277,7 @@ merge ::
273277
merge fs hbio Config {..} targetPaths runs = do
274278
let f = fromMaybe const mergeResolve
275279
m <- fromMaybe (error "empty inputs, no merge created") <$>
276-
Merge.new fs hbio runParams mergeType f targetPaths runs
280+
Merge.new fs hbio benchSalt runParams mergeType f targetPaths runs
277281
Merge.stepsToCompletion m stepSize
278282

279283
fsPath :: FS.FsPath
@@ -397,7 +401,7 @@ randomRuns ::
397401
randomRuns hasFS hasBlockIO config@Config {..} rng0 = do
398402
counter <- inputRunPathsCounter
399403
fmap V.fromList $
400-
mapM (unsafeCreateRun hasFS hasBlockIO runParams fsPath counter) $
404+
mapM (unsafeCreateRun hasFS hasBlockIO benchSalt runParams fsPath counter) $
401405
zipWith
402406
(randomRunData config)
403407
nentries
@@ -446,5 +450,5 @@ randomRunData Config {..} runentries g0 =
446450
-- Each run entry needs a distinct key.
447451
randomWord64OutOf :: Int -> Rnd SerialisedKey
448452
randomWord64OutOf possibleKeys =
449-
first (serialiseKey . Hash.hash64)
453+
first (serialiseKey . Hash.hashSalt64 benchSalt)
450454
. uniformR (0, fromIntegral possibleKeys :: Word64)

blockio/src-linux/System/FS/BlockIO/Async.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ ctxParamsConv API.IOCtxParams{API.ioctxBatchSizeLimit, API.ioctxConcurrencyLimit
5656
}
5757

5858
submitIO ::
59-
HasFS IO HandleIO
59+
HasCallStack
60+
=> HasFS IO HandleIO
6061
-> I.IOCtx
6162
-> V.Vector (IOOp RealWorld HandleIO)
6263
-> IO (VU.Vector IOResult)

blockio/src/System/FS/BlockIO/Serial.hs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Control.Monad.Primitive (PrimMonad, PrimState, RealWorld)
99
import qualified Data.Vector as V
1010
import qualified Data.Vector.Unboxed as VU
1111
import qualified Data.Vector.Unboxed.Mutable as VUM
12+
import GHC.Stack (HasCallStack)
1213
import System.FS.API
1314
import qualified System.FS.BlockIO.API as API
1415
import System.FS.BlockIO.API (IOOp (..), IOResult (..), LockMode (..))
@@ -55,7 +56,7 @@ serialHasBlockIO hSetNoCache hAdvise hAllocate tryLockFile hSynchronise synchron
5556
data IOCtx m = IOCtx { ctxFS :: SomeHasFS m, openVar :: MVar m Bool }
5657

5758
{-# SPECIALISE guardIsOpen :: IOCtx IO -> IO () #-}
58-
guardIsOpen :: (MonadMVar m, MonadThrow m) => IOCtx m -> m ()
59+
guardIsOpen :: (HasCallStack, MonadMVar m, MonadThrow m) => IOCtx m -> m ()
5960
guardIsOpen ctx = readMVar (openVar ctx) >>= \b ->
6061
unless b $ throwIO (API.mkClosedError (ctxFS ctx) "submitIO")
6162

@@ -72,7 +73,7 @@ close ctx = modifyMVar_ (openVar ctx) $ const (pure False)
7273
-> IOCtx IO -> V.Vector (IOOp RealWorld h)
7374
-> IO (VU.Vector IOResult) #-}
7475
submitIO ::
75-
(MonadMVar m, MonadThrow m, PrimMonad m)
76+
(HasCallStack, MonadMVar m, MonadThrow m, PrimMonad m)
7677
=> HasFS m h
7778
-> IOCtx m
7879
-> V.Vector (IOOp (PrimState m) h)

0 commit comments

Comments
 (0)