Skip to content

Commit 0050528

Browse files
committed
Refactor getEpochStateDetails to return immediately on foldEpochState failure.
Change getEpochStateDetails to return a tuple instead of accepting a function.
1 parent 6480fb7 commit 0050528

1 file changed

Lines changed: 59 additions & 25 deletions

File tree

  • cardano-testnet/src/Testnet/Components

cardano-testnet/src/Testnet/Components/Query.hs

Lines changed: 59 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ import Data.Maybe
7171
import Data.Ord (Down (..))
7272
import qualified Data.Set as Set
7373
import qualified Data.Text as T
74+
import qualified Data.Time.Clock as DTC
7475
import Data.Type.Equality
7576
import Data.Word (Word64)
7677
import GHC.Exts (IsList (..))
@@ -194,19 +195,27 @@ retryUntilJustM esv timeout act = withFrozenCallStack $ do
194195
WaitForSlots n -> n
195196
WaitForBlocks n -> n
196197

198+
-- | Status of the 'EpochStateView' background thread when epoch state is not yet available
199+
data EpochStateStatus
200+
= EpochStateNotInitialised
201+
-- ^ The background thread has not yet received any epoch state from the node
202+
| EpochStateFoldError !FoldBlocksError
203+
-- ^ The background thread encountered an error while folding blocks
204+
197205
-- | A read-only mutable pointer to an epoch state, updated automatically
198206
data EpochStateView = EpochStateView
199207
{ nodeConfigPath :: !(NodeConfigFile In)
200208
-- ^ node configuration file path
201209
, socketPath :: !SocketPath
202210
-- ^ node socket path, to which foldEpochState is connected to
203-
, epochStateView :: !(IORef (Maybe (AnyNewEpochState, SlotNo, BlockNo)))
204-
-- ^ Automatically updated current NewEpochState. Use 'getEpochState', 'getBlockNumber', 'getSlotNumber'
205-
-- to access the values.
211+
, epochStateView :: !(IORef (Either EpochStateStatus (AnyNewEpochState, SlotNo, BlockNo)))
212+
-- ^ Automatically updated current NewEpochState. 'Left' indicates the state is not yet available
213+
-- (either not initialised or an error occurred). 'Right' contains the latest epoch state.
214+
-- Use 'getEpochState', 'getBlockNumber', 'getSlotNumber' to access the values.
206215
}
207216

208-
-- | Get epoch state from the view. If the state isn't available, retry waiting up to 15 seconds. Fails when
209-
-- the state is not available after 15 seconds.
217+
-- | Get epoch state from the view. If the state isn't available, retry waiting up to 25 seconds. Fails
218+
-- immediately if the background thread encountered an error, or after 25 seconds if not yet initialised.
210219
getEpochState
211220
:: HasCallStack
212221
=> MonadTest m
@@ -215,7 +224,7 @@ getEpochState
215224
=> EpochStateView
216225
-> m AnyNewEpochState
217226
getEpochState epochStateView =
218-
withFrozenCallStack $ getEpochStateDetails epochStateView $ \(nes, _, _) -> pure nes
227+
withFrozenCallStack $ (\(nes, _, _) -> nes) <$> getEpochStateDetails epochStateView
219228

220229
getBlockNumber
221230
:: HasCallStack
@@ -225,7 +234,7 @@ getBlockNumber
225234
=> EpochStateView
226235
-> m BlockNo -- ^ The number of last produced block
227236
getBlockNumber epochStateView =
228-
withFrozenCallStack $ getEpochStateDetails epochStateView $ \(_, _, blockNumber) -> pure blockNumber
237+
withFrozenCallStack $ (\(_, _, blockNumber) -> blockNumber) <$> getEpochStateDetails epochStateView
229238

230239
getSlotNumber
231240
:: HasCallStack
@@ -235,24 +244,45 @@ getSlotNumber
235244
=> EpochStateView
236245
-> m SlotNo -- ^ The current slot number
237246
getSlotNumber epochStateView =
238-
withFrozenCallStack $ getEpochStateDetails epochStateView $ \(_, slotNumber, _) -> pure slotNumber
247+
withFrozenCallStack $ (\(_, slotNumber, _) -> slotNumber) <$> getEpochStateDetails epochStateView
239248

240-
-- | Utility function for accessing epoch state in `IORef`
249+
-- | Utility function for accessing epoch state in 'IORef'.
250+
-- Retries every 0.5s for up to 25 seconds while not initialised.
251+
-- Fails immediately if the background fold thread encountered an error.
241252
getEpochStateDetails
242253
:: HasCallStack
243254
=> MonadAssertion m
244255
=> MonadTest m
245256
=> MonadIO m
246257
=> EpochStateView
247-
-> ((AnyNewEpochState, SlotNo, BlockNo) -> m a)
248-
-> m a
249-
getEpochStateDetails EpochStateView{epochStateView} f =
250-
withFrozenCallStack $
251-
H.byDurationM 0.5 15 "EpochStateView has not been initialized within 15 seconds" $
252-
H.evalIO (readIORef epochStateView) >>= maybe H.failure f
258+
-> m (AnyNewEpochState, SlotNo, BlockNo)
259+
getEpochStateDetails EpochStateView{epochStateView} =
260+
withFrozenCallStack $ do
261+
deadline <- liftIO $ DTC.addUTCTime 25 <$> DTC.getCurrentTime
262+
go deadline
263+
where
264+
go deadline = do
265+
result <- H.evalIO $ readIORef epochStateView
266+
case result of
267+
Left (EpochStateFoldError err) -> do
268+
H.note_ $ "EpochStateView background thread failed: " <> docToString (prettyError err)
269+
H.failure
270+
Left EpochStateNotInitialised -> do
271+
currentTime <- liftIO DTC.getCurrentTime
272+
if currentTime < deadline
273+
then do
274+
H.threadDelay 500_000
275+
go deadline
276+
else do
277+
H.note_ "EpochStateView has not been initialised within 25 seconds"
278+
H.failure
279+
Right details -> pure details
253280

254281
-- | Create a background thread listening for new epoch states. New epoch states are available to access
255282
-- through 'EpochStateView', using query functions.
283+
-- The background thread captures any 'FoldBlocksError' into the shared state, so that consumers
284+
-- (e.g. 'getEpochStateDetails') can fail immediately with a meaningful error message instead of
285+
-- waiting for the full timeout.
256286
getEpochStateView
257287
:: HasCallStack
258288
=> MonadResource m
@@ -261,11 +291,15 @@ getEpochStateView
261291
-> SocketPath -- ^ node socket path
262292
-> m EpochStateView
263293
getEpochStateView nodeConfigFile socketPath = withFrozenCallStack $ do
264-
epochStateView <- H.evalIO $ newIORef Nothing
265-
void . asyncRegister_ . runExceptT . foldEpochState nodeConfigFile socketPath QuickValidation (EpochNo maxBound) Nothing
266-
$ \epochState slotNumber blockNumber -> do
267-
liftIOAnnotated . writeIORef epochStateView $ Just (epochState, slotNumber, blockNumber)
268-
pure ConditionNotMet
294+
epochStateView <- H.evalIO $ newIORef $ Left EpochStateNotInitialised
295+
void . asyncRegister_ $ do
296+
result <- runExceptT $ foldEpochState nodeConfigFile socketPath QuickValidation (EpochNo maxBound) ()
297+
$ \epochState slotNumber blockNumber -> do
298+
liftIOAnnotated . writeIORef epochStateView $ Right (epochState, slotNumber, blockNumber)
299+
pure ConditionNotMet
300+
case result of
301+
Left err -> writeIORef epochStateView $ Left $ EpochStateFoldError err
302+
Right _ -> pure ()
269303
pure $ EpochStateView nodeConfigFile socketPath epochStateView
270304

271305
-- | Watch the epoch state view until the guard function returns 'Just' or the timeout epoch is reached.
@@ -285,7 +319,7 @@ watchEpochStateUpdate epochStateView (EpochInterval maxWait) f = withFrozenCall
285319
where
286320
go :: Word64 -> m (Maybe a)
287321
go timeout = do
288-
newEpochStateDetails@(AnyNewEpochState _ newEpochState' _, _, _) <- getEpochStateDetails epochStateView pure
322+
newEpochStateDetails@(AnyNewEpochState _ newEpochState' _, _, _) <- getEpochStateDetails epochStateView
289323
let EpochNo currentEpoch = L.nesEL newEpochState'
290324
f newEpochStateDetails >>= \case
291325
Just result -> pure (Just result)
@@ -573,10 +607,10 @@ assertNewEpochState epochStateView sbe maxWait lens expected = withFrozenCallSta
573607
getFromEpochStateForEra
574608
:: HasCallStack
575609
=> m value
576-
getFromEpochStateForEra = withFrozenCallStack $ getEpochStateDetails epochStateView $
577-
\(AnyNewEpochState actualEra newEpochState _, _, _) -> do
578-
Refl <- H.leftFail $ assertErasEqual sbe actualEra
579-
pure $ newEpochState ^. lens
610+
getFromEpochStateForEra = withFrozenCallStack $ do
611+
(AnyNewEpochState actualEra newEpochState _, _, _) <- getEpochStateDetails epochStateView
612+
Refl <- H.leftFail $ assertErasEqual sbe actualEra
613+
pure $ newEpochState ^. lens
580614

581615
-- | Return current protocol parameters from the governance state
582616
getProtocolParams :: (H.MonadAssertion m, MonadTest m, MonadIO m)

0 commit comments

Comments
 (0)