From 58e4f0eaa7659b6bc1155cc3937bf2b35bf010fe Mon Sep 17 00:00:00 2001 From: shum Date: Wed, 9 Sep 2026 07:30:04 +0000 Subject: [PATCH] tests: reproduce RSLV fan-out, proxy stuck session --- tests/RSLVTests.hs | 20 ++++++++++++++++++++ tests/SMPClient.hs | 11 +++++++++++ tests/SMPProxyTests.hs | 15 +++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/tests/RSLVTests.hs b/tests/RSLVTests.hs index 2416d851e..fa314daa7 100644 --- a/tests/RSLVTests.hs +++ b/tests/RSLVTests.hs @@ -11,7 +11,10 @@ module RSLVTests (rslvTests) where +import Control.Concurrent (threadDelay) +import Control.Monad (forM_) import Control.Monad.Trans.Except (ExceptT, runExceptT) +import Data.IORef (readIORef) import qualified Data.Aeson as J import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy as LB @@ -82,6 +85,23 @@ rslvTests = do it "PFWD-wrapped RSLV success returns RNAME (record JSON frames over the proxy)" testRslvForwardedSuccess describe "RSLV success path (RNAME response)" $ do it "returns RNAME with NameRecord" testRslvSuccess + describe "RSLV resource use" $ + xit "one connection must not fan out to many concurrent resolver requests" testRslvFanOut + +testRslvFanOut :: IO () +testRslvFanOut = + NRS.withResolverServerDelayed 3000 (NRS.resolveResp status200 "{}") $ \port reqs -> + withSmpServerConfigOn (transport @TLS) (withNames port memCfg) testPort $ const $ + testSMPClient @TLS $ \(h@THandle {params} :: THandleSMP TLS 'TClient) -> do + let k = 64 :: Int + globalCap = 8 :: Int + forM_ [1 .. k] $ \i -> do + let TransmissionForAuth {tToSend} = encodeTransmissionForAuth params (CorrId (B.pack $ "fan" <> show i), NoEntity, Cmd SResolver (RSLV (domain "alice.simplex"))) + [Right ()] <- tPut h (Right (Nothing, tToSend) :| []) + pure () + threadDelay 800000 + inFlight <- length . filter ((== ["resolve"]) . take 1) <$> readIORef reqs + inFlight `shouldSatisfy` (<= globalCap) testRslvBackendNotFound :: IO () testRslvBackendNotFound = diff --git a/tests/SMPClient.hs b/tests/SMPClient.hs index e5adaa749..317967e84 100644 --- a/tests/SMPClient.hs +++ b/tests/SMPClient.hs @@ -346,6 +346,17 @@ proxyCfgShortTimeout = nt = NetworkTimeout {backgroundTimeout = 4_000000, interactiveTimeout = 4_000000} in cfg' {smpAgentCfg = aCfg {smpCfg = cCfg {networkConfig = (networkConfig cCfg) {tcpConnectTimeout = nt}}}} +-- Proxy whose forward (RFWD) response deadline is 1us: every forward to the relay times out +-- while the initial relay connect (tcpConnectTimeout) is unaffected, so a session is established +-- and then every forward on it fails with a response timeout. +proxyCfgForwardTimeout :: AServerConfig +proxyCfgForwardTimeout = + updateCfg proxyCfg $ \cfg' -> + let aCfg = smpAgentCfg cfg' + cCfg = smpCfg aCfg + nt = NetworkTimeout {backgroundTimeout = 1, interactiveTimeout = 1} + in cfg' {smpAgentCfg = aCfg {smpCfg = cCfg {networkConfig = (networkConfig cCfg) {tcpTimeout = nt}}}} + withSmpServerStoreMsgLogOn :: HasCallStack => (ASrvTransport, AStoreType) -> ServiceName -> (HasCallStack => ThreadId -> IO a) -> IO a withSmpServerStoreMsgLogOn (t, msType) = withSmpServerConfigOn t $ updateCfg (cfgMS msType) $ \cfg' -> cfg' {storeNtfsFile = Just testStoreNtfsFile, serverStatsBackupFile = Just testServerStatsBackupFile} diff --git a/tests/SMPProxyTests.hs b/tests/SMPProxyTests.hs index bb3932232..0a17f9e28 100644 --- a/tests/SMPProxyTests.hs +++ b/tests/SMPProxyTests.hs @@ -63,6 +63,8 @@ smpProxyTests = do testProxyRecoversWithoutDisconnect it "reconnects to relay after sender disconnects mid-connection" $ \_ -> testProxyReconnectAfterRelayRestart + xit "must drop a stuck relay session after forward timeouts" $ \_ -> + testProxyForwardTimeoutStuckSession describe "agent client reconnection" $ do it "reconnects after a connect is cancelled mid-flight" $ \_ -> testAgentClientReconnectAfterCancel @@ -494,6 +496,19 @@ testProxyReconnectAfterRelayRestart = race_ (threadDelay 1000000) requestRelaySession requireProxyReconnect +testProxyForwardTimeoutStuckSession :: IO () +testProxyForwardTimeoutStuckSession = + withSmpServerConfigOn (transport @TLS) proxyCfgForwardTimeout testPort $ \_ -> do + g <- C.newRandom + ts <- getCurrentTime + let srv = SMPServer testHost testPort testKeyHash + vr = mkVersionRange minServerSMPRelayVersion currentClientSMPRelayVersion + pc <- either (fail . show) pure =<< getProtocolClient g NRMInteractive (1, srv, Nothing) defaultSMPClientConfig {serverVRange = vr} [] Nothing ts (\_ -> pure ()) + sess <- runExceptT' $ connectSMPProxiedRelay pc NRMInteractive srv (Just "correct") + sId <- atomically $ SMP.EntityId <$> C.randomBytes 24 g + rs <- forM ([1 .. 10] :: [Int]) $ \_ -> runExceptT' (proxySMPMessage pc NRMInteractive sess Nothing sId noMsgFlags "hi") + rs `shouldSatisfy` elem (Left (ProxyProtocolError (SMP.PROXY SMP.NO_SESSION))) + -- Bug B (same root cause as the proxy, in the messaging agent): getSMPServerClient inserts an -- empty SessionVar into smpClients, then connects inside newProtocolClient's tryAllErrors, which -- rethrows async exceptions. If the connecting thread is cancelled mid-connect, putTMVar is