Skip to content

Commit bd366da

Browse files
authored
Merge pull request #572 from IntersectMBO/dcoutts/pr-554-review
Follow-up from review on PR #554
2 parents e000f86 + b22f64c commit bd366da

File tree

4 files changed

+44
-26
lines changed

4 files changed

+44
-26
lines changed

src/Database/LSMTree/Internal/MergeSchedule.hs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,8 @@ addRunToLevels tr conf@TableConfig{..} resolve hfs hbio root uc r0 reg levels ul
746746
OneShot -> do
747747
let !required = MR.Credits (unNumEntries (V.foldMap' Run.size rs))
748748
let !thresh = creditThresholdForLevel conf ln
749-
_leftoverCredits <- MR.supplyCredits mr thresh required
749+
leftoverCredits <- MR.supplyCredits mr thresh required
750+
assert (leftoverCredits == 0) $ return ()
750751
-- This ensures the merge is really completed. However, we don't
751752
-- release the merge yet and only briefly inspect the resulting run.
752753
bracket (MR.expectCompleted mr) releaseRef $ \r ->
@@ -878,6 +879,9 @@ supplyCredits conf c levels =
878879
let !c' = scaleCreditsForMerge mp mr c
879880
let !thresh = creditThresholdForLevel conf ln
880881
_leftoverCredits <- MR.supplyCredits mr thresh c'
882+
--TODO: assert leftoverCredits == 0
883+
-- to assert that we did not finished the merge too early,
884+
-- and thus have spread the work out evenly.
881885
return ()
882886

883887
-- | Scale a number of credits to a number of merge steps to be performed, based
@@ -915,4 +919,4 @@ scaleCreditsForMerge LevelTiering mr (Credits c) =
915919
creditThresholdForLevel :: TableConfig -> LevelNo -> MR.CreditThreshold
916920
creditThresholdForLevel conf (LevelNo _i) =
917921
let AllocNumEntries (NumEntries x) = confWriteBufferAlloc conf
918-
in MR.CreditThreshold (MR.Credits x)
922+
in MR.CreditThreshold (MR.UnspentCredits (MR.Credits x))

src/Database/LSMTree/Internal/MergingRun.hs

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,14 @@ duplicateRuns (DeRef mr) =
231231
V.mapM (\r -> withRollback reg (dupRef r) releaseRef) rs
232232

233233
-- | Take a snapshot of the state of a merging run.
234+
--
235+
-- TODO: this is not concurrency safe! The inputs runs to the merging run could
236+
-- be released concurrently by another thread that completes the merge, while
237+
-- the snapshot is taking place. The solution is for snapshot here to duplicate
238+
-- the runs it returns _while_ holding the mergeState MVar (to exclude threads
239+
-- that might concurrently complete the merge). And then the caller of course
240+
-- must be updated to release the extra references.
241+
--
234242
snapshot ::
235243
(PrimMonad m, MonadMVar m)
236244
=> Ref (MergingRun m h)
@@ -267,7 +275,7 @@ work to do).
267275
The implementation is similar but somewhat more complex. We also accumulate
268276
unspent credits until they reach a threshold at which point we do a batch of
269277
merging work. Unlike the prototype, the implementation tracks both credits
270-
spent credits as yet unspent. We will elaborate on why and how below.
278+
spent and credits as yet unspent. We will elaborate on why and how below.
271279
272280
In the prototype, the credits spent equals the merge steps performed. The
273281
same holds in the real implementation, but making it so is more complicated.
@@ -296,7 +304,8 @@ Thus we track two things:
296304
* credits unspent ('UnspentCredits'): credits supplied that are not yet spent
297305
and are thus available to spend.
298306
299-
The credits supplied is the sum of the credits spent and unspent.
307+
The credits supplied is the sum of the credits spent and unspent. We guarantee
308+
that the supplied credits never exceeds the total debt.
300309
301310
The credits spent and the steps performed (or in the process of being
302311
performed) will typically be equal. They are not guaranteed to be equal in the
@@ -330,7 +339,7 @@ numEntriesToTotalDebt (NumEntries n) = Credits n
330339
-- Note that ideally the batch size for different LSM levels should be
331340
-- co-prime so that merge work at different levels is not synchronised.
332341
--
333-
newtype CreditThreshold = CreditThreshold Credits
342+
newtype CreditThreshold = CreditThreshold UnspentCredits
334343

335344
-- | The supplied credits is simply the sum of all the credits that have been
336345
-- (successfully) supplied to a merging run via 'supplyCredits'.
@@ -559,8 +568,8 @@ atomicDepositAndSpendCredits (CreditsVar !var) !totalDebt
559568

560569
-- 2. not case 1, but enough unspent credits have accumulated to do
561570
-- a batch of merge work;
562-
| (\(UnspentCredits x)->x) unspent' >= batchThreshold
563-
= spendBatchCredits spent unspent'
571+
| unspent' >= batchThreshold
572+
= spendBatchCredits spent unspent' batchThreshold
564573

565574
-- 3. not case 1 or 2, not enough credits to do any merge work.
566575
| otherwise
@@ -587,14 +596,15 @@ atomicDepositAndSpendCredits (CreditsVar !var) !totalDebt
587596
assert (leftover >= 0) $
588597
(supplied', UnspentCredits unspent', leftover)
589598

590-
spendBatchCredits (SpentCredits !spent) (UnspentCredits !unspent) =
599+
spendBatchCredits (SpentCredits !spent) (UnspentCredits !unspent)
600+
(UnspentCredits unspentBatchThreshold) =
591601
-- numBatches may be zero, in which case the result will be zero
592-
let !nBatches = unspent `div` batchThreshold
593-
!spend = nBatches * batchThreshold
602+
let !nBatches = unspent `div` unspentBatchThreshold
603+
!spend = nBatches * unspentBatchThreshold
594604
!spent' = spent + spend
595605
!unspent' = unspent - spend
596606
in assert (spend >= 0) $
597-
assert (unspent' < batchThreshold) $
607+
assert (unspent' < unspentBatchThreshold) $
598608
assert (spent' + unspent' == spent + unspent) $
599609
(spend, SpentCredits spent', UnspentCredits unspent')
600610

@@ -702,11 +712,10 @@ performMergeSteps ::
702712
-> Credits
703713
-> m Bool
704714
performMergeSteps mergeVar creditsVar (Credits credits) =
715+
assert (credits >= 0) $
705716
withMVar mergeVar $ \case
706717
CompletedMerge{} -> pure False
707718
OngoingMerge _rs m -> do
708-
-- We have dealt with the case of credits <= 0 above,
709-
-- so here we know credits is positive
710719
let stepsToDo = credits
711720
(stepsDone, stepResult) <- Merge.steps m stepsToDo
712721
assert (stepResult == MergeDone || stepsDone >= stepsToDo) (pure ())
@@ -743,8 +752,9 @@ completeMerge mergeVar mergeKnownCompletedVar = do
743752
(OngoingMerge rs m) -> do
744753
-- first try to complete the merge before performing other side effects,
745754
-- in case the completion fails
746-
--TODO: Run.fromMutable claims not to be exception safe
747-
-- may need to use uninteruptible mask
755+
--TODO: Run.fromMutable (used in Merge.complete) claims not to be
756+
-- exception safe so we should probably be using the resource registry
757+
-- and test for exception safety.
748758
r <- Merge.complete m
749759
V.forM_ rs releaseRef
750760
-- Cache the knowledge that we completed the merge
@@ -768,16 +778,14 @@ expectCompleted (DeRef MergingRun {..}) = do
768778
let totalDebt = numEntriesToTotalDebt mergeNumEntries
769779
suppliedCredits = spentCredits + unspentCredits
770780
!credits = assert (suppliedCredits == totalDebt) $
781+
assert (unspentCredits >= 0) $
771782
unspentCredits
772783

773-
--TODO: what about exception safety: check if it is ok to be interrupted
774-
-- between performMergeSteps and completeMerge here, and above.
775784
weFinishedMerge <- performMergeSteps mergeState mergeCreditsVar credits
785+
-- If an async exception happens before we get to perform the
786+
-- completion, then that is fine. The next 'expectCompleted' will
787+
-- complete the merge.
776788
when weFinishedMerge $ completeMerge mergeState mergeKnownCompleted
777-
-- TODO: can we think of a check to see if we did not do too much work
778-
-- here? <-- assert (suppliedCredits == totalDebt) ought to do it!
779-
-- A related question is if we finished the merge too early, could have
780-
-- spread out the work better.
781789
withMVar mergeState $ \case
782790
CompletedMerge r -> dupRef r -- return a fresh reference to the run
783791
OngoingMerge{} -> do

src/Database/LSMTree/Internal/Snapshot.hs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,12 @@ instance NFData r => NFData (SnapMergingRunState r) where
171171
Conversion to levels snapshot format
172172
-------------------------------------------------------------------------------}
173173

174+
--TODO: probably generally all the Ref (Run _) here ought to be fresh
175+
-- references, created as we snapshot the levels, so that the runs don't
176+
-- disappear under our feet during the process of making the snapshot durable.
177+
-- At minimum the volatile runs are the inputs to merging runs, but it may be
178+
-- simpler to duplicate them all, and release them all at the end.
179+
174180
{-# SPECIALISE toSnapLevels :: Levels IO h -> IO (SnapLevels (Ref (Run IO h))) #-}
175181
toSnapLevels ::
176182
(PrimMonad m, MonadMVar m)
@@ -194,14 +200,14 @@ toSnapIncomingRun ::
194200
-> m (SnapIncomingRun (Ref (Run m h)))
195201
toSnapIncomingRun (Single r) = pure (SnapSingleRun r)
196202
toSnapIncomingRun (Merging mergePolicy mergingRun) = do
197-
-- We need to know how many credits were spend and yet unspent so we can
198-
-- restore merge work on snapshot load. No need to snapshot the contents
199-
-- of totalStepsVar here, since we still start counting from 0 again when
200-
-- loading the snapshot.
203+
-- We need to know how many credits were supplied so we can restore merge
204+
-- work on snapshot load.
201205
(mergingRunState,
202206
MR.SuppliedCredits (MR.Credits suppliedCredits),
203207
mergeNumRuns,
204208
mergeNumEntries) <- MR.snapshot mergingRun
209+
-- TODO: MR.snapshot needs to return duplicated run references, and we
210+
-- need to arrange to release them when the snapshoting is done.
205211
let smrs = toSnapMergingRunState mergingRunState
206212
pure $
207213
SnapMergingRun

test/Test/Database/LSMTree/Internal/MergingRun.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ tests = testGroup "Test.Database.LSMTree.Internal.MergingRun"
1313
]
1414

1515
-- | The representation of CreditsPair should round trip properly. This is
16-
-- non-trivial because it uses a packed bit the representation.
16+
-- non-trivial because it uses a packed bitfield representation.
1717
--
1818
prop_CreditsPair :: SpentCredits -> UnspentCredits -> Property
1919
prop_CreditsPair spentCredits unspentCredits =

0 commit comments

Comments
 (0)