Skip to content

Commit efdf425

Browse files
committed
Ensure uncommitted forkers do not leak Ledger tables handles
1 parent d66ce26 commit efdf425

File tree

4 files changed

+57
-14
lines changed

4 files changed

+57
-14
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: 18 additions & 12 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)
@@ -573,13 +572,14 @@ getEnvSTM (LDBHandle varState) f =
573572
getStateRef ::
574573
(IOLike m, Traversable t) =>
575574
LedgerDBEnv m l blk ->
575+
ResourceRegistry m ->
576576
(LedgerSeq m l -> t (StateRef m l)) ->
577577
m (t (StateRef m l))
578-
getStateRef ldbEnv project =
578+
getStateRef ldbEnv reg project =
579579
RAWLock.withReadAccess (ldbOpenHandlesLock ldbEnv) $ \() -> do
580580
tst <- project <$> readTVarIO (ldbSeq ldbEnv)
581581
for tst $ \st -> do
582-
tables' <- duplicate $ tables st
582+
(_, tables') <- allocate reg (\_ -> duplicate $ tables st) close
583583
pure st{tables = tables'}
584584

585585
-- | Like 'StateRef', but takes care of closing the handle when the given action
@@ -590,8 +590,8 @@ withStateRef ::
590590
(LedgerSeq m l -> t (StateRef m l)) ->
591591
(t (StateRef m l) -> m a) ->
592592
m a
593-
withStateRef ldbEnv project =
594-
bracket (getStateRef ldbEnv project) (traverse_ (close . tables))
593+
withStateRef ldbEnv project f =
594+
withRegistry $ \reg -> getStateRef ldbEnv reg project >>= f
595595

596596
acquireAtTarget ::
597597
( HeaderHash l ~ HeaderHash blk
@@ -602,9 +602,10 @@ acquireAtTarget ::
602602
) =>
603603
LedgerDBEnv m l blk ->
604604
Either Word64 (Target (Point blk)) ->
605+
ResourceRegistry m ->
605606
m (Either GetForkerError (StateRef m l))
606-
acquireAtTarget ldbEnv target =
607-
getStateRef ldbEnv $ \l -> case target of
607+
acquireAtTarget ldbEnv target reg =
608+
getStateRef ldbEnv reg $ \l -> case target of
608609
Right VolatileTip -> pure $ currentHandle l
609610
Right ImmutableTip -> pure $ anchorHandle l
610611
Right (SpecificPoint pt) -> do
@@ -638,7 +639,7 @@ newForkerAtTarget ::
638639
Target (Point blk) ->
639640
m (Either GetForkerError (Forker m l blk))
640641
newForkerAtTarget h rr pt = getEnv h $ \ldbEnv ->
641-
acquireAtTarget ldbEnv (Right pt) >>= traverse (newForker h ldbEnv rr)
642+
acquireAtTarget ldbEnv (Right pt) rr >>= traverse (newForker h ldbEnv rr)
642643

643644
newForkerByRollback ::
644645
( HeaderHash l ~ HeaderHash blk
@@ -653,14 +654,14 @@ newForkerByRollback ::
653654
Word64 ->
654655
m (Either GetForkerError (Forker m l blk))
655656
newForkerByRollback h rr n = getEnv h $ \ldbEnv ->
656-
acquireAtTarget ldbEnv (Left n) >>= traverse (newForker h ldbEnv rr)
657+
acquireAtTarget ldbEnv (Left n) rr >>= traverse (newForker h ldbEnv rr)
657658

658659
closeForkerEnv ::
659660
IOLike m => ForkerEnv m l blk -> m ()
660661
closeForkerEnv ForkerEnv{foeResourcesToRelease = (lock, key, toRelease)} =
661662
RAWLock.withWriteAccess lock $
662663
const $ do
663-
id =<< atomically (swapTVar toRelease (pure ()))
664+
Monad.join $ atomically (swapTVar toRelease (pure ()))
664665
_ <- release key
665666
pure ((), ())
666667

@@ -757,7 +758,12 @@ newForker h ldbEnv rr st = do
757758
let tr = LedgerDBForkerEvent . TraceForkerEventWithKey forkerKey >$< ldbTracer ldbEnv
758759
traceWith tr ForkerOpen
759760
lseqVar <- newTVarIO . LedgerSeq . AS.Empty $ st
760-
(k, toRelease) <- allocate rr (\_ -> newTVarIO (pure ())) (readTVarIO Monad.>=> id)
761+
-- The closing action that we allocate in the TVar from the start is not
762+
-- strictly necessary if the caller uses a short-lived registry like the ones
763+
-- in Chain selection or the forging loop. Just in case the user passes a
764+
-- long-lived registry, we store such closing action to make sure the handle
765+
-- is closed even under @forkerClose@ if the registry outlives the forker.
766+
(k, toRelease) <- allocate rr (\_ -> newTVarIO (close (tables st))) (Monad.join . readTVarIO)
761767
let forkerEnv =
762768
ForkerEnv
763769
{ 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)