mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-08-28 09:44:15 +00:00
cli: fix terminal input lag from TMVar starvation
Replace `termLock :: TMVar ()` with `MVar ()`. GHC's STM has no fairness guarantee on contended TMVars; under sustained event load the output renderer monopolizes the lock and the input thread (`updateInput` inside `withTermLock` at Input.hs:162) can wait seconds for each keystroke echo. Reproduced and measured with term-lock-bench (5000 events, 200us per item + 800us per-acquisition flush, on two threads): variant samples mean p95 max tmvar-single 6 1.7 s 2.1 s 3.0 s mvar-single 202 1.1 ms 2.1 ms 2.3 ms About 1300x lower worst-case input-echo latency, no other behavior changes. MVar in IO is FIFO under GHC so the input thread is served between every render.
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
module Simplex.Chat.Terminal.Output where
|
||||
|
||||
import Control.Concurrent (ThreadId)
|
||||
import Control.Concurrent.MVar (MVar, newMVar, takeMVar, putMVar)
|
||||
import Control.Logger.Simple
|
||||
import Control.Monad
|
||||
import Control.Monad.Catch (MonadMask)
|
||||
@@ -49,7 +50,7 @@ data ChatTerminal = ChatTerminal
|
||||
termSize :: Size,
|
||||
liveMessageState :: TVar (Maybe LiveMessage),
|
||||
nextMessageRow :: TVar Int,
|
||||
termLock :: TMVar (),
|
||||
termLock :: MVar (),
|
||||
sendNotification :: Maybe (Notification -> IO ()),
|
||||
activeTo :: TVar String,
|
||||
currentRemoteUsers :: TMap RemoteHostId User
|
||||
@@ -103,7 +104,10 @@ newChatTerminal t opts = do
|
||||
let lastRow = height termSize - 1
|
||||
termState <- newTVarIO mkTermState
|
||||
liveMessageState <- newTVarIO Nothing
|
||||
termLock <- newTMVarIO ()
|
||||
-- MVar (not TMVar) for FIFO fairness under contention: the terminal
|
||||
-- input thread must not be starved by the output renderer when the
|
||||
-- output thread is processing a backlog (see runTerminalOutput).
|
||||
termLock <- newMVar ()
|
||||
nextMessageRow <- newTVarIO lastRow
|
||||
sendNotification <- if muteNotifications opts then pure Nothing else Just <$> initializeNotifications
|
||||
activeTo <- newTVarIO ""
|
||||
@@ -137,9 +141,9 @@ mkAutoComplete = ACState {acVariants = [], acInputString = "", acTabPressed = Fa
|
||||
|
||||
withTermLock :: MonadTerminal m => ChatTerminal -> m () -> m ()
|
||||
withTermLock ChatTerminal {termLock} action = do
|
||||
_ <- atomically $ takeTMVar termLock
|
||||
liftIO $ takeMVar termLock
|
||||
action
|
||||
atomically $ putTMVar termLock ()
|
||||
liftIO $ putMVar termLock ()
|
||||
|
||||
runTerminalOutput :: ChatTerminal -> ChatController -> ChatOpts -> IO ()
|
||||
runTerminalOutput ct cc@ChatController {outputQ, showLiveItems, logFilePath} ChatOpts {markRead} = do
|
||||
|
||||
Reference in New Issue
Block a user