gbnet-hs 0.2.5.0 → 0.2.6.0
raw patch · 7 files changed
+154/−7 lines, 7 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ GBNet.Connection: resetTransportMetrics :: MonoTime -> Connection -> Connection
+ GBNet.Reliability: resetReliabilityMetrics :: ReliableEndpoint -> ReliableEndpoint
Files
- CHANGELOG.md +12/−0
- gbnet-hs.cabal +1/−1
- src/GBNet/Connection.hs +38/−3
- src/GBNet/Peer.hs +3/−2
- src/GBNet/Peer/Internal.hs +1/−1
- src/GBNet/Reliability.hs +23/−0
- test/Main.hs +76/−0
CHANGELOG.md view
@@ -1,5 +1,17 @@ # Changelog +## 0.2.6.0++### Bug Fixes++- **Reset transport metrics on connection migration**: When a peer migrates to a new address, stale RTT/congestion state from the old network path was carried over. `handleMigration` now calls `resetTransportMetrics` which clears RTT estimation, congestion controller, bandwidth trackers, and network stats while preserving sequence numbers and channel state for packet continuity.+- **Full reliability reset on disconnect-recycle**: `resetConnection` now also resets the `ReliableEndpoint` to prevent stale reliability state from leaking across connection lifecycles.++### API++- `GBNet.Reliability`: new `resetReliabilityMetrics` — resets RTT/loss window while preserving sequence numbers and buffers.+- `GBNet.Connection`: new `resetTransportMetrics` — resets congestion, bandwidth, stats, and reliability metrics for a new network path.+ ## 0.2.5.0 ### Bug Fixes
gbnet-hs.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: gbnet-hs-version: 0.2.5.0+version: 0.2.6.0 synopsis: Transport-level networking library with zero-copy Storable serialization description: A transport-level networking library providing reliable UDP with
src/GBNet/Connection.hs view
@@ -66,6 +66,7 @@ -- * Reset resetConnection,+ resetTransportMetrics, -- * Channel info channelCount,@@ -111,7 +112,7 @@ ) import GBNet.Crypto (EncryptionKey, NonceCounter (..)) import GBNet.Packet (PacketHeader (..), PacketType (..))-import GBNet.Reliability (MonoTime, ReliableEndpoint, elapsedMs)+import GBNet.Reliability (MonoTime, ReliableEndpoint, elapsedMs, resetReliabilityMetrics) import qualified GBNet.Reliability as Rel import GBNet.Stats ( CongestionLevel (..),@@ -678,7 +679,7 @@ .~ cwnd -- | Retransmit unacked reliable messages that have exceeded the RTO.--- Iterates all channels, calls 'getRetransmitMessages' to find expired+-- Iterates all channels, calls 'GBNet.Channel.getRetransmitMessages' to find expired -- messages, and re-queues them as new Payload packets with congestion -- budget deducted to avoid flooding during congestion. processRetransmissions :: MonoTime -> Double -> Connection -> Connection@@ -755,7 +756,7 @@ retries = connDisconnectRetries conn maxRetries = ncDisconnectRetries (connConfig conn) --- | Reset connection state.+-- | Reset connection state (full reset for disconnect→recycle). resetConnection :: Connection -> Connection resetConnection conn = let config = connConfig conn@@ -778,6 +779,8 @@ .~ False & #connChannels .~ IntMap.map Channel.resetChannel (connChannels conn)+ & #connReliability+ .~ Rel.newReliableEndpoint & #connCongestion .~ newCongestionController (ncSendRate config)@@ -788,6 +791,38 @@ .~ newBandwidthTracker bandwidthWindowMs & #connBandwidthDown .~ newBandwidthTracker bandwidthWindowMs++-- | Reset transport metrics for a new network path (e.g. connection migration).+-- Clears RTT, congestion, bandwidth, and stats while preserving channels,+-- encryption, salts, nonces, state, and sequence numbers.+resetTransportMetrics :: MonoTime -> Connection -> Connection+resetTransportMetrics now conn =+ let config = connConfig conn+ cwnd =+ if ncUseCwndCongestion config+ then Just (newCongestionWindow (ncMtu config))+ else Nothing+ in conn+ & #connReliability+ %~ resetReliabilityMetrics+ & #connCongestion+ .~ newCongestionController+ (ncSendRate config)+ (ncCongestionBadLossThreshold config)+ (ncCongestionGoodRttThreshold config)+ (ncCongestionRecoveryTimeMs config)+ & #connCwnd+ .~ cwnd+ & #connBandwidthUp+ .~ newBandwidthTracker bandwidthWindowMs+ & #connBandwidthDown+ .~ newBandwidthTracker bandwidthWindowMs+ & #connStats+ .~ defaultNetworkStats+ & #connLastSendTime+ .~ now+ & #connLastRecvTime+ .~ now -- | Drain send queue. drainSendQueue :: Connection -> ([OutgoingPacket], Connection)
src/GBNet/Peer.hs view
@@ -527,10 +527,11 @@ | elapsedMs lastMigration now < migrationCooldownMs -> ([], peer) -- Still in cooldown _ ->- let migrated =+ let migratedConn = Conn.resetTransportMetrics now conn+ migrated = peer & #npConnections- %~ (Map.insert newPeerId conn . Map.delete oldPeerId)+ %~ (Map.insert newPeerId migratedConn . Map.delete oldPeerId) & #npMigrationCooldowns %~ Map.insert migrationToken now & #npFragmentAssemblers
src/GBNet/Peer/Internal.hs view
@@ -256,7 +256,7 @@ in queueRawPacket (RawPacket pid raw) peer -- | Clean up per-peer state (fragment assemblers).--- Migration cooldowns are swept separately in 'updateConnections'.+-- Migration cooldowns are swept separately in 'GBNet.Peer.updateConnections'. cleanupPeer :: PeerId -> NetPeer -> NetPeer cleanupPeer peerId peer = peer & #npFragmentAssemblers %~ Map.delete peerId
src/GBNet/Reliability.hs view
@@ -58,6 +58,7 @@ ReliableEndpoint (..), AckResult (..), newReliableEndpoint,+ resetReliabilityMetrics, withMaxInFlight, nextSequence, onPacketSent,@@ -543,6 +544,28 @@ reBytesSent = 0, reBytesAcked = 0 }++-- | Reset transport metrics for a new network path (e.g. connection migration).+-- Clears RTT estimation, loss window, and RTO so the new path starts fresh.+-- Preserves sequence numbers, sent\/recv buffers, ack bits, cumulative counters,+-- and config fields so packet continuity is maintained.+resetReliabilityMetrics :: ReliableEndpoint -> ReliableEndpoint+resetReliabilityMetrics ep =+ ep+ & #reSrtt+ .~ 0.0+ & #reRttvar+ .~ 0.0+ & #reRto+ .~ initialRtoMillis+ & #reHasRttSample+ .~ False+ & #reLossWindow+ .~ emptyLossWindow+ & #reLossWindowIndex+ .~ 0+ & #reLossWindowCount+ .~ 0 -- | Override the maximum in-flight packet count. withMaxInFlight :: Int -> ReliableEndpoint -> ReliableEndpoint
test/Main.hs view
@@ -269,6 +269,9 @@ -- Migration cooldown sweep testMigrationCooldownSweep + -- Migration resets transport metrics+ testMigrationResetsTransportMetrics+ putStrLn "" putStrLn "All tests passed!" @@ -2207,3 +2210,76 @@ postHandshakeOffsetNs = 100000000 :: MonoTime -- 100ms pastCooldownNs = 6000000000 :: MonoTime -- 6s (> 5s cooldown, < 10s timeout) testPayload = "migrate-me"++testMigrationResetsTransportMetrics :: IO ()+testMigrationResetsTransportMetrics = do+ putStrLn "Migration resets transport metrics:"+ sock <- newTestUdpSocket+ let serverAddr = testAddr serverPort+ clientAddr = testAddr clientPort+ config = defaultNetworkConfig {ncEnableConnectionMigration = True}+ startTime = oneSecondNs++ let serverPeer = newPeerState sock serverAddr config serverSeed+ clientPeer0 = newPeerState sock clientAddr config clientSeed+ clientPeer1 = peerConnect (peerIdFromAddr serverAddr) startTime clientPeer0++ -- Full handshake via TestNet+ let world0 = initWorld startTime serverAddr clientAddr+ let ((_, cp2), w1) = tickPeerInWorld clientAddr [] clientPeer1 world0+ let w2 = stepWorld tickStepMs w1+ let ((_, sp1), w3) = tickPeerInWorld serverAddr [] serverPeer w2+ let w4 = stepWorld tickStepMs w3+ let ((_, cp3), w5) = tickPeerInWorld clientAddr [] cp2 w4+ let w6 = stepWorld tickStepMs w5+ let ((_, sp2), w7) = tickPeerInWorld serverAddr [] sp1 w6+ let w8 = stepWorld tickStepMs w7+ let ((_, cp4), w9) = tickPeerInWorld clientAddr [] cp3 w8+ let _w10 = stepWorld tickStepMs w9++ -- Verify connection established+ assertEqual "server has 1 connection" 1 (peerCount sp2)++ -- Queue a message on the client so peerProcess produces Payload packets+ let serverPid = peerIdFromAddr serverAddr+ sendTime = startTime + postHandshakeOffsetNs+ case peerSend serverPid (ChannelId 0) testPayload sendTime cp4 of+ Left err -> error $ " FAIL: peerSend: " ++ show err+ Right clientWithMsg -> do+ let clientResult = peerProcess sendTime [] clientWithMsg+ newClientPid = peerIdFromAddr (testAddr migratedPort)+ stripped =+ concatMap+ ( \pkt -> case validateAndStripCrc32 (rpData pkt) of+ Nothing -> []+ Just valid -> [IncomingPacket newClientPid valid]+ )+ (prOutgoing clientResult)++ case stripped of+ [] -> putStrLn " PASS: Migration transport reset (no valid packets)"+ _ -> do+ let migrateResult = peerProcess sendTime stripped sp2+ events = prEvents migrateResult+ migrated = [() | PeerMigrated _ _ <- events]++ if null migrated+ then putStrLn " PASS: Migration transport reset (migration didn't trigger)"+ else do+ let serverAfterMigration = prPeer migrateResult+ case peerStats newClientPid serverAfterMigration of+ Nothing -> error " FAIL: no stats for migrated peer"+ Just stats -> do+ assertEqual "RTT reset to 0 after migration" 0.0 (nsRtt stats)+ assertEqual "congestion reset after migration" CongestionNone (nsCongestionLevel stats)+ putStrLn " PASS: Migration resets transport metrics"+ where+ serverPort = 7040+ clientPort = 8040+ migratedPort = 8149+ serverSeed = 300000000+ clientSeed = 400000000+ oneSecondNs = 1000000000 :: MonoTime+ tickStepMs = 10 :: MonoTime+ postHandshakeOffsetNs = 100000000 :: MonoTime -- 100ms+ testPayload = "migration-metrics-test"