writerAction: drain, dedupe, and rate-limit outbound calls

This commit is contained in:
2026-08-25 19:37:57 +03:00
parent 3f5aa4735c
commit 9fadbae777
+32 -5
View File
@@ -9,10 +9,12 @@ module HomeAssistant.Runtime.Connection
) where ) where
import Control.Concurrent.STM import Control.Concurrent.STM
( atomically ( TChan
, atomically
, readTChan , readTChan
, readTVar , readTVar
, retry , retry
, tryReadTChan
, writeTChan , writeTChan
, writeTVar , writeTVar
) )
@@ -103,16 +105,41 @@ receiveJSON conn = do
Left err -> throw (Fatal $ "Invalid JSON from Home Assistant: " <> T.pack err) Left err -> throw (Fatal $ "Invalid JSON from Home Assistant: " <> T.pack err)
Right x -> pure x Right x -> pure x
writerAction :: Bus -> IO Void -- | Floor between sends within a batch: 100ms, so a many-distinct-target
writerAction bus = forever $ do -- flood still caps at ~10 sends/sec even after dedupe.
(request, svc) <- atomically $ readTChan (busOutbound bus) minInterval :: Int
conn <- atomically $ readTVar (busConn bus) >>= maybe retry pure minInterval = 100000
-- | Non-blocking drain of everything queued on a channel. Returns items
-- oldest-first (FIFO from the channel), so prepending the blocking
-- `readTChan` item keeps the whole batch oldest-first for `dedupeBatch`.
drainTry :: TChan a -> IO [a]
drainTry chan = go []
where
go acc = do
m <- atomically $ tryReadTChan chan
case m of
Nothing -> pure (reverse acc)
Just x -> go (x : acc)
sendWithId :: Bus -> WS.Connection -> Request -> Service -> IO ()
sendWithId bus conn request svc = do
callId <- generateCallId (busGen bus) callId <- generateCallId (busGen bus)
let textData = encode $ encodeService callId svc let textData = encode $ encodeService callId svc
runKatipContextT (busLogEnv bus) (sl "traceId" (toText (requestTraceId request))) "connection" $ runKatipContextT (busLogEnv bus) (sl "traceId" (toText (requestTraceId request))) "connection" $
logFM DebugS (ls textData) logFM DebugS (ls textData)
WS.sendTextData conn textData WS.sendTextData conn textData
writerAction :: Bus -> IO Void
writerAction bus = forever $ do
first <- atomically $ readTChan (busOutbound bus)
rest <- drainTry (busOutbound bus)
let deduped = dedupeBatch (first : rest)
conn <- atomically $ readTVar (busConn bus) >>= maybe retry pure
forM_ deduped $ \(request, svc) -> do
sendWithId bus conn request svc
threadDelay minInterval
encodeService :: Int -> Service -> Value encodeService :: Int -> Service -> Value
encodeService callId Service{..} = object $ encodeService callId Service{..} = object $
[ "id" .= callId [ "id" .= callId