Skip to content
4 changes: 2 additions & 2 deletions .github/workflows/applications.yml
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ jobs:
ghc: ${{ matrix.ghc }}
use-freeze-file: ${{ matrix.use-freeze-file }}
- name: Run compaction-tests
timeout-minutes: 7
timeout-minutes: 10
run: |
ulimit -n 10000
compaction-tests
Expand Down Expand Up @@ -505,7 +505,7 @@ jobs:
ghc: ${{ matrix.ghc }}
use-freeze-file: ${{ matrix.use-freeze-file }}
- name: Run multi-node-network-tests
timeout-minutes: 10
timeout-minutes: 15
run: |
ulimit -n 10000
multi-node-network-tests
Expand Down
2 changes: 1 addition & 1 deletion src/Chainweb/BlockWeight.hs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ newtype BlockWeight = BlockWeight HashDifficulty
( Hashable
, ToJSON, FromJSON, ToJSONKey, FromJSONKey
, AdditiveSemigroup, AdditiveAbelianSemigroup
, Num
, Num, Bounded
)

instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag BlockWeight where
Expand Down
35 changes: 17 additions & 18 deletions src/Chainweb/Chainweb/MinerResources.hs
Original file line number Diff line number Diff line change
Expand Up @@ -105,24 +105,23 @@ withMiningCoordination logger conf cdb inner
c503 <- newIORef 0
c403 <- newIORef 0
l <- newIORef (_coordinationUpdateStreamLimit coordConf)
fmap thd . runConcurrently $ (,,)
<$> Concurrently (prune t m c503 c403)
<*> Concurrently (mapConcurrently_ (primeWork m) cids)
<*> Concurrently (inner . Just $ MiningCoordination
{ _coordLogger = logger
, _coordCutDb = cdb
, _coordState = t
, _coordLimit = _coordinationReqLimit coordConf
, _coord503s = c503
, _coord403s = c403
, _coordConf = coordConf
, _coordUpdateStreamCount = l
, _coordPrimedWork = m
, _coordTargetFork =
if _coordinationTargetForkOverride coordConf
then pred $ max 1 (_versionForkNumber v)
else _versionForkNumber v
})
withAsync (prune t m c503 c403) (\_ -> do
withAsync (mapConcurrently_ (primeWork m) cids) (\_ -> do
inner (Just MiningCoordination
{ _coordLogger = logger
, _coordCutDb = cdb
, _coordState = t
, _coordLimit = _coordinationReqLimit coordConf
, _coord503s = c503
, _coord403s = c403
, _coordConf = coordConf
, _coordUpdateStreamCount = l
, _coordPrimedWork = m
, _coordTargetFork =
if _coordinationTargetForkOverride coordConf
then pred $ max 1 (_versionForkNumber v)
else _versionForkNumber v
})))
Comment on lines -108 to +124

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually the whole fix according to my experiments, if you amend this PR to just have this I'll stamp.

where
coordConf = _miningCoordination conf
inNodeConf = _miningInNode conf
Expand Down
13 changes: 3 additions & 10 deletions src/Chainweb/CutDB.hs
Original file line number Diff line number Diff line change
Expand Up @@ -241,15 +241,6 @@ cutHashesTable rdb = Casify $ newTable rdb valueCodec keyCodec ["CutHashes"]
(runGetS $ (,,) <$> decodeCutHeightBe <*> decodeBlockWeightBe <*> decodeCutId)
valueCodec = Codec encodeToByteString decodeStrictOrThrow'

-- -------------------------------------------------------------------------- --
-- Exceptions

data CutDbStopped = CutDbStopped
deriving (Eq, Show, Generic)

instance Exception CutDbStopped where
fromException = asyncExceptionFromException
toException = asyncExceptionToException

-- -------------------------------------------------------------------------- --
-- Cut DB
Expand Down Expand Up @@ -518,10 +509,12 @@ fastForwardCutDb cutDb = do
--
stopCutDb :: CutDb tbl -> IO ()
stopCutDb db = do
pQueueEnd (_cutDbQueue db)
void $ waitCatch (_cutDbAsync db)

currentCut <- readTVarIO (_cutDbCut db)
unless (_cutDbReadOnly db) $
casInsert (_cutDbCutStore db) (cutToCutHashes Nothing currentCut)
cancelWith (_cutDbAsync db) CutDbStopped

-- | Lookup the BlockHeaders for a CutHashes structure. Throws an exception if
-- the lookup for some BlockHash in the input CutHashes.
Expand Down
30 changes: 25 additions & 5 deletions src/Data/PQueue.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE LambdaCase #-}


-- |
-- Module: Data.PQueue
Expand All @@ -18,10 +20,14 @@ module Data.PQueue
, pQueueRemove
, pQueueIsEmpty
, pQueueSize
, pQueueEnd
, pQueueEndNice
) where

import Control.Concurrent.STM
import Control.Concurrent.Async (AsyncCancelled(..))
import Control.Monad
import Control.Exception

import Data.Ord
import qualified Data.Map as M
Expand All @@ -38,11 +44,14 @@ import Numeric.Natural
-- items in the queue. An item of low priority my starve in the queue if higher
-- priority items are added at a rate at least as high as items are removed.
--

data PQueueElement a = PQueueEOF | PQueueData a

data PQueue a =
forall p k. (Ord p, Ord k) =>
PQueue (TVar (M.Map (Down p, k) a)) (TVar (S.Set k)) (a -> p) (a -> k) (Maybe Natural)
forall p k. (Ord p, Bounded p, Ord k, Bounded k) =>
PQueue (TVar (M.Map (Down p, k) (PQueueElement a))) (TVar (S.Set k)) (a -> p) (a -> k) (Maybe Natural)

newEmptyPQueue :: (Ord p, Ord k) => (a -> p) -> (a -> k) -> Maybe Natural -> IO (PQueue a)
newEmptyPQueue :: (Ord p, Bounded p, Ord k, Bounded k) => (a -> p) -> (a -> k) -> Maybe Natural -> IO (PQueue a)
newEmptyPQueue getPrio getKey maybeMaxLen = PQueue
<$> newTVarIO mempty
<*> newTVarIO mempty
Expand All @@ -60,7 +69,7 @@ pQueueInsert (PQueue mv sv getPrio getKey maybeMaxLen) a =
then return ()
else do
let s' = S.insert k s
let m' = M.insert (Down $ getPrio a, k) a m
let m' = M.insert (Down $ getPrio a, k) (PQueueData a) m
let fixup (maxlen :: Natural) = if M.size m' > fromIntegral (2 * maxlen)
then let (keep, dontkeep) = M.splitAt (fromIntegral maxlen) m'
in (foldl' (flip (S.delete . snd)) s' (M.keys dontkeep), keep)
Expand All @@ -75,10 +84,21 @@ pQueueIsEmpty (PQueue mv _ _ _ _) = M.null <$!> readTVarIO mv
pQueueSize :: PQueue a -> IO Natural
pQueueSize (PQueue mv _ _ _ _) = fromIntegral . M.size <$!> readTVarIO mv

-- End the queue by pushing a top priority EOF
pQueueEnd :: PQueue a -> IO ()
pQueueEnd (PQueue mv _ _ _ _) = atomically $ modifyTVar mv $ M.insert (Down maxBound, minBound) PQueueEOF

-- End the queue by pushing a Lowest priority EOF
pQueueEndNice :: PQueue a -> IO ()
pQueueEndNice (PQueue mv _ _ _ _) = atomically $ modifyTVar mv $ M.insert (Down minBound, maxBound) PQueueEOF

-- | If the queue is empty it blocks and races for new items
--
pQueueRemove :: PQueue a -> IO a
pQueueRemove (PQueue mv sv _getPrio _getKey _) = atomically run
pQueueRemove (PQueue mv sv _getPrio _getKey _) =
atomically run >>= \case
PQueueEOF -> throwIO AsyncCancelled
PQueueData a -> return a
where
run = do
m <- readTVar mv
Expand Down
5 changes: 5 additions & 0 deletions src/P2P/TaskQueue.hs
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,14 @@ newtype TaskId = TaskId T.Text
deriving (Show, Eq, Ord, Generic)
deriving newtype (IsString)

instance Bounded TaskId where
minBound = TaskId T.empty
maxBound = TaskId $ T.singleton maxBound

newtype Priority = Priority Int
deriving (Show, Eq, Ord, Generic)
deriving anyclass (NFData, Hashable)
deriving newtype (Bounded)

newtype AttemptsCount = AttemptsCount Natural
deriving (Show, Eq, Ord, Generic)
Expand Down
45 changes: 29 additions & 16 deletions test/lib/Chainweb/Test/MultiNode.hs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import Data.ByteString.Base16 qualified as Base16
import Chainweb.Pact.Backend.PactState.EmbeddedSnapshot (Snapshot(..))
import Data.Aeson (ToJSON)
import Data.Foldable
import Data.Maybe
import Data.Hashable
import qualified Data.HashMap.Strict as HM
import qualified Data.HashSet as HS
Expand All @@ -87,7 +88,6 @@ import System.Directory (createDirectoryIfMissing)
import System.FilePath
import System.IO.Temp
import System.LogLevel
import System.Timeout

import Test.Tasty.HUnit

Expand Down Expand Up @@ -245,18 +245,30 @@ harvestConsensusState _ _ _ (Replayed _ _) =
error "harvestConsensusState: doesn't work when replaying, replays don't do consensus"
harvestConsensusState logger stateVar nid (StartedChainweb cw) = do
runChainweb cw (\_ -> return ()) `finally` do
logFunctionText logger Info "write sample data"
logFunctionText logger' Info "Node main threads ended"

-- At this point, Warp/Servant server is supposed to be closed.
-- But Warp doesn't kill existing connections. As such, other nodes
-- can continue to push new Cuts, despite we would like to freeze a final Cut.

-- A workaround is to early stop the CutDB, and not wait for the node to do it
-- naturally during unwinding.
stopCutDb (cw ^. chainwebCutResources . cutsCutDb)

logFunctionText logger' Info "write sample data"
modifyMVar_ stateVar $
sampleConsensusState
nid
(view (chainwebCutResources . cutsCutDb . cutDbWebBlockHeaderDb) cw)
(view (chainwebCutResources . cutsCutDb) cw)
logFunctionText logger Info "shutdown node"
logFunctionText logger' Info "shutdown node"
where
logger' = addLabel ("node", toText nid) logger

multiNode
:: LogLevel
-> (T.Text -> IO ())
-> MVar PeerInfo
-> MVar (Maybe PeerInfo)
-> ChainwebConfiguration
-> RocksDb
-> FilePath
Expand All @@ -269,9 +281,9 @@ multiNode loglevel write bootstrapPeerInfoVar conf rdb pactDbDir nid inner = do
withChainweb conf logger namespacedNodeRocksDb (pactDbDir </> show nid) backupTmpDir False $ \cw -> do
case cw of
StartedChainweb cw' ->
when (nid == bootstrapNodeId) $ putMVar bootstrapPeerInfoVar
when (nid == bootstrapNodeId) $ putMVar bootstrapPeerInfoVar $ Just
$ view (chainwebPeer . peerResPeer . peerInfo) cw'
Replayed _ _ -> return ()
Replayed _ _ -> when (nid == bootstrapNodeId) $ putMVar bootstrapPeerInfoVar Nothing
inner nid cw
where
logger :: GenericLogger
Expand Down Expand Up @@ -313,7 +325,7 @@ runNodes loglevel write v confBuilders rdb pactDbDir inner = do
| i == 0 ->
return $ multiBootstrapConfig baseConf
| otherwise ->
setBootstrapPeerInfo <$> readMVar bootstrapPortVar <*> pure baseConf
maybe baseConf (`setBootstrapPeerInfo` baseConf) <$> readMVar bootstrapPortVar

multiNode loglevel write bootstrapPortVar (confBuilder conf) rdb pactDbDir (NodeId i) inner

Expand All @@ -330,9 +342,11 @@ runNodesForSeconds
-> FilePath
-> (forall logger. NodeId -> StartedChainweb logger -> IO ())
-> IO ()
runNodesForSeconds loglevel write v confBuilders (Seconds seconds) rdb pactDbDir inner = do
void $ timeout (int seconds * 1_000_000)
$ runNodes loglevel write v confBuilders rdb pactDbDir inner
runNodesForSeconds loglevel write v confBuilders (Seconds seconds) rdb pactDbDir inner =
runNodes loglevel write v confBuilders rdb pactDbDir innerWithTimeout
where
innerWithTimeout:: NodeId -> StartedChainweb a -> IO()
innerWithTimeout nid cw = void $ race (inner nid cw) $ threadDelay (int seconds * 1_000_000)

-- | Ensure that we can compact a live node(s).
--
Expand Down Expand Up @@ -617,19 +631,18 @@ replayTest loglevel v n rdb pactDbDir step = do
tastylog $ "phase 3... replaying"
let replayInitialHeight = 5
firstReplayCompleteRef <- newIORef False
runNodesForSeconds loglevel logFun v
runNodes loglevel logFun v
(replicate n
$ multiConfig n
& mapped . configCuts . cutInitialBlockHeightLimit
.~ Just replayInitialHeight
& mapped . configOnlySyncPact .~ True)
(Seconds 20) rdb pactDbDir $ \nid cw -> case cw of
rdb pactDbDir $ \nid cw -> case cw of
Replayed l (Just u) -> do
writeIORef firstReplayCompleteRef True
_ <- flip HM.traverseWithKey (_cutMap l) $ \cid bh ->
assertEqual ("lower chain " <> sshow cid) replayInitialHeight (view blockHeight bh)
-- TODO: this is flaky, presumably because a node's cutdb
-- is not being cancelled synchronously enough

assertEqual "upper cut" (_stateCutMap state2 HM.! nid) u
_ <- flip HM.traverseWithKey (_cutMap u) $ \cid bh ->
assertGe ("upper chain " <> sshow cid) (Actual $ view blockHeight bh) (Expected replayInitialHeight)
Expand All @@ -640,7 +653,7 @@ replayTest loglevel v n rdb pactDbDir step = do
let fastForwardHeight = 10
tastylog $ "phase 4... replaying with fast-forward limit"
secondReplayCompleteRef <- newIORef False
runNodesForSeconds loglevel logFun v
runNodes loglevel logFun v
(replicate n
$ multiConfig n
& mapped . configCuts . cutInitialBlockHeightLimit
Expand All @@ -649,7 +662,7 @@ replayTest loglevel v n rdb pactDbDir step = do
.~ Just fastForwardHeight
& mapped . configOnlySyncPact .~ True
)
(Seconds 20) rdb pactDbDir $ \_ cw -> case cw of
rdb pactDbDir $ \_ cw -> case cw of
Replayed l (Just u) -> do
writeIORef secondReplayCompleteRef True
_ <- flip HM.traverseWithKey (_cutMap l) $ \cid bh ->
Expand Down
Loading