Skip to content

Commit d5446cd

Browse files
committed
Ensure uncommitted forkers do not leak Ledger tables handles
1 parent ab17724 commit d5446cd

File tree

4 files changed

+67
-23
lines changed

4 files changed

+67
-23
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!--
2+
A new scriv changelog fragment.
3+
4+
Uncomment the section that is right (remove the HTML comment wrapper).
5+
For top level release notes, leave all the headers commented out.
6+
-->
7+
8+
<!--
9+
### Patch
10+
11+
- A bullet item for the Patch category.
12+
13+
-->
14+
15+
### Non-Breaking
16+
17+
- Ensure uncommitted forkers do not leak Ledger tables handles.
18+
19+
<!--
20+
### Breaking
21+
22+
- A bullet item for the Breaking category.
23+
24+
-->

ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/LedgerDB/V2.hs

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,12 @@
1717
module Ouroboros.Consensus.Storage.LedgerDB.V2 (mkInitDb) where
1818

1919
import Control.Arrow ((>>>))
20-
import qualified Control.Monad as Monad (void, (>=>))
20+
import qualified Control.Monad as Monad (join, void)
2121
import Control.Monad.Except
2222
import Control.RAWLock
2323
import qualified Control.RAWLock as RAWLock
2424
import Control.ResourceRegistry
2525
import Control.Tracer
26-
import Data.Foldable (traverse_)
2726
import qualified Data.Foldable as Foldable
2827
import Data.Functor.Contravariant ((>$<))
2928
import Data.Kind (Type)
@@ -197,7 +196,7 @@ mkInternals bss h =
197196
let selectWhereTo = case whereTo of
198197
TakeAtImmutableTip -> anchorHandle
199198
TakeAtVolatileTip -> currentHandle
200-
withStateRef env (MkSolo . selectWhereTo) $ \(MkSolo st) ->
199+
withStateRef env (MkSolo . selectWhereTo) $ \(MkSolo (st, _)) ->
201200
Monad.void $
202201
takeSnapshot
203202
(configCodec . getExtLedgerCfg . ledgerDbCfg $ ldbCfg env)
@@ -367,7 +366,7 @@ implTryTakeSnapshot ::
367366
implTryTakeSnapshot bss env mTime nrBlocks =
368367
if onDiskShouldTakeSnapshot (ldbSnapshotPolicy env) (uncurry (flip diffTime) <$> mTime) nrBlocks
369368
then do
370-
withStateRef env (MkSolo . anchorHandle) $ \(MkSolo st) ->
369+
withStateRef env (MkSolo . anchorHandle) $ \(MkSolo (st, _)) ->
371370
Monad.void $
372371
takeSnapshot
373372
(configCodec . getExtLedgerCfg . ledgerDbCfg $ ldbCfg env)
@@ -565,33 +564,35 @@ getEnvSTM (LDBHandle varState) f =
565564

566565
-- | Get a 'StateRef' from the 'LedgerSeq' in the 'LedgerDBEnv', with the
567566
-- 'LedgerTablesHandle' having been duplicated (such that the original can be
568-
-- closed). The caller is responsible for closing the handle.
567+
-- closed). The caller should close the handle using the returned @ResourceKey@,
568+
-- although closing the registry will also release the handle.
569569
--
570570
-- For more flexibility, an arbitrary 'Traversable' of the 'StateRef' can be
571571
-- returned; for the simple use case of getting a single 'StateRef', use @t ~
572572
-- 'Solo'@.
573573
getStateRef ::
574574
(IOLike m, Traversable t) =>
575575
LedgerDBEnv m l blk ->
576+
ResourceRegistry m ->
576577
(LedgerSeq m l -> t (StateRef m l)) ->
577-
m (t (StateRef m l))
578-
getStateRef ldbEnv project =
578+
m (t (StateRef m l, ResourceKey m))
579+
getStateRef ldbEnv reg project =
579580
RAWLock.withReadAccess (ldbOpenHandlesLock ldbEnv) $ \() -> do
580581
tst <- project <$> readTVarIO (ldbSeq ldbEnv)
581582
for tst $ \st -> do
582-
tables' <- duplicate $ tables st
583-
pure st{tables = tables'}
583+
(resKey, tables') <- allocate reg (\_ -> duplicate $ tables st) close
584+
pure (st{tables = tables'}, resKey)
584585

585586
-- | Like 'StateRef', but takes care of closing the handle when the given action
586587
-- returns or errors.
587588
withStateRef ::
588589
(IOLike m, Traversable t) =>
589590
LedgerDBEnv m l blk ->
590591
(LedgerSeq m l -> t (StateRef m l)) ->
591-
(t (StateRef m l) -> m a) ->
592+
(t (StateRef m l, ResourceKey m) -> m a) ->
592593
m a
593-
withStateRef ldbEnv project =
594-
bracket (getStateRef ldbEnv project) (traverse_ (close . tables))
594+
withStateRef ldbEnv project f =
595+
withRegistry $ \reg -> getStateRef ldbEnv reg project >>= f
595596

596597
acquireAtTarget ::
597598
( HeaderHash l ~ HeaderHash blk
@@ -602,9 +603,10 @@ acquireAtTarget ::
602603
) =>
603604
LedgerDBEnv m l blk ->
604605
Either Word64 (Target (Point blk)) ->
605-
m (Either GetForkerError (StateRef m l))
606-
acquireAtTarget ldbEnv target =
607-
getStateRef ldbEnv $ \l -> case target of
606+
ResourceRegistry m ->
607+
m (Either GetForkerError (StateRef m l, ResourceKey m))
608+
acquireAtTarget ldbEnv target reg =
609+
getStateRef ldbEnv reg $ \l -> case target of
608610
Right VolatileTip -> pure $ currentHandle l
609611
Right ImmutableTip -> pure $ anchorHandle l
610612
Right (SpecificPoint pt) -> do
@@ -638,7 +640,7 @@ newForkerAtTarget ::
638640
Target (Point blk) ->
639641
m (Either GetForkerError (Forker m l blk))
640642
newForkerAtTarget h rr pt = getEnv h $ \ldbEnv ->
641-
acquireAtTarget ldbEnv (Right pt) >>= traverse (newForker h ldbEnv rr)
643+
acquireAtTarget ldbEnv (Right pt) rr >>= traverse (newForker h ldbEnv rr)
642644

643645
newForkerByRollback ::
644646
( HeaderHash l ~ HeaderHash blk
@@ -653,14 +655,14 @@ newForkerByRollback ::
653655
Word64 ->
654656
m (Either GetForkerError (Forker m l blk))
655657
newForkerByRollback h rr n = getEnv h $ \ldbEnv ->
656-
acquireAtTarget ldbEnv (Left n) >>= traverse (newForker h ldbEnv rr)
658+
acquireAtTarget ldbEnv (Left n) rr >>= traverse (newForker h ldbEnv rr)
657659

658660
closeForkerEnv ::
659661
IOLike m => ForkerEnv m l blk -> m ()
660662
closeForkerEnv ForkerEnv{foeResourcesToRelease = (lock, key, toRelease)} =
661663
RAWLock.withWriteAccess lock $
662664
const $ do
663-
id =<< atomically (swapTVar toRelease (pure ()))
665+
Monad.join $ atomically (swapTVar toRelease (pure ()))
664666
_ <- release key
665667
pure ((), ())
666668

@@ -750,14 +752,19 @@ newForker ::
750752
LedgerDBHandle m l blk ->
751753
LedgerDBEnv m l blk ->
752754
ResourceRegistry m ->
753-
StateRef m l ->
755+
(StateRef m l, ResourceKey m) ->
754756
m (Forker m l blk)
755-
newForker h ldbEnv rr st = do
757+
newForker h ldbEnv rr (st, rk) = do
756758
forkerKey <- atomically $ stateTVar (ldbNextForkerKey ldbEnv) $ \r -> (r, r + 1)
757759
let tr = LedgerDBForkerEvent . TraceForkerEventWithKey forkerKey >$< ldbTracer ldbEnv
758760
traceWith tr ForkerOpen
759761
lseqVar <- newTVarIO . LedgerSeq . AS.Empty $ st
760-
(k, toRelease) <- allocate rr (\_ -> newTVarIO (pure ())) (readTVarIO Monad.>=> id)
762+
-- The closing action that we allocate in the TVar from the start is not
763+
-- strictly necessary if the caller uses a short-lived registry like the ones
764+
-- in Chain selection or the forging loop. Just in case the user passes a
765+
-- long-lived registry, we store such closing action to make sure the handle
766+
-- is closed even under @forkerClose@ if the registry outlives the forker.
767+
(k, toRelease) <- allocate rr (\_ -> newTVarIO (Monad.void (release rk))) (Monad.join . readTVarIO)
761768
let forkerEnv =
762769
ForkerEnv
763770
{ foeLedgerSeq = lseqVar

ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/LedgerDB/V2/Forker.hs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,17 @@ implForkerCommit env = do
165165
_ AS.:< closeOld' -> closeLedgerSeq (LedgerSeq closeOld')
166166
-- Finally, close the anchor of @lseq@ (which is a duplicate of
167167
-- the head of @olddb'@).
168+
--
169+
-- Note if the resource registry used to create the Forker is
170+
-- ephemeral as the one created on each Chain selection or each
171+
-- Forging loop iteration, this first duplicated state will be
172+
-- closed by the resource registry closing down, so this will be
173+
-- a double release, which is fine. We prefer keeping this
174+
-- action just in case some client passes a registry that
175+
-- outlives the forker.
176+
--
177+
-- The rest of the states in the forker will be closed via
178+
-- @foeResourcesToRelease@ instead of via the registry.
168179
close $ tables $ AS.anchor lseq
169180
pure (closeDiscarded, s)
170181
)

ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/LedgerDB/V2/InMemory.hs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,10 @@ newInMemoryLedgerTablesHandle tracer someFS@(SomeHasFS hasFS) l = do
9696
pure
9797
LedgerTablesHandle
9898
{ close = do
99-
atomically $ writeTVar tv LedgerTablesHandleClosed
100-
traceWith tracer V2.TraceLedgerTablesHandleClose
99+
p <- atomically $ swapTVar tv LedgerTablesHandleClosed
100+
case p of
101+
LedgerTablesHandleOpen{} -> traceWith tracer V2.TraceLedgerTablesHandleClose
102+
_ -> pure ()
101103
, duplicate = do
102104
hs <- readTVarIO tv
103105
!x <- guardClosed hs $ newInMemoryLedgerTablesHandle tracer someFS

0 commit comments

Comments
 (0)