natskell-1.1.0.0: internal/Policy/State/Store.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
module State.Store
( ClientState
, newClientState
, runClient
, config
, queue
, connection
, enqueue
, tryEnqueue
, openQueue
, closeQueue
, discardConnectionScoped
, PingResult (..)
, registerPingWaiter
, registerPingWaiterAndEnqueue
, resolveNextPing
, clearPendingPings
, nextSid
, nextInbox
, readServerInfo
, setServerInfo
, updateLogContextFromInfo
, setConnectName
, setEndpoint
, ServerErrorEnqueueResult (..)
, notifyServerError
, CallbackWorker
, startCallbackWorker
, callbackWorkerThreadId
, waitForCallbackWorker
, stopCallbackWorker
, readStatus
, waitForConnected
, beginConnectionAttempt
, activateConnectionAttempt
, setReconnecting
, setClosing
, markClosed
, waitForClosed
, waitForNotRunning
, waitForServerInfo
, connectedOnce
, markConnectionReady
, markConnectionReadyWithRejectedPublishes
, waitForInitialConnection
, failInitialConnection
, readConnectionGeneration
, waitForConnectionGenerationAfter
, waitForConnectionGenerationLoss
, enqueueOnGenerationTracked
, PublishEnqueueResult (..)
, enqueuePublishOnConnected
, enqueuePublishOnConnectedGenerationTracked
, tryEnqueueOnGeneration
, readAttemptIndex
, incrementAttemptIndex
, registerManagedThread
, unregisterManagedThread
, isManagedThread
, withSubscriptionGate
) where
import Control.Concurrent
( ThreadId
, forkIOWithUnmask
, killThread
, myThreadId
)
import Control.Concurrent.STM
import Control.Exception (bracket_, finally)
import Control.Monad (unless, void, when)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BC
import Data.List (delete)
import Lib.Exception (trySync)
import Lib.Logger
( LogLevel (..)
, MonadLogger (..)
, loggerApi
)
import Lib.Logger.Types (AppM, LogContext (..))
import Lib.LoggerAPI (runWithLogger, updateLogContext)
import Network.ConnectionAPI (Conn)
import Nuid (Nuid, newNuidIO, nextNuid)
import qualified Queue.API as Queue
import Queue.API (Queue, QueueItem, TryEnqueueResult)
import Sid (SIDCounter, initialSIDCounter, nextSID)
import State.Types
import qualified Types.Info as Info
import Types.Info (Info, client_id)
import Types.Msg (SID, Subject)
data ClientState = ClientState
{ clientConfig :: ClientConfig
, clientQueue :: Queue
, clientPings :: TQueue (Int, TMVar PingResult)
, clientCallbacks :: TQueue ClientCallback
, clientPendingServerCallbacks :: TVar Int
, clientPendingServerCallbackBytes :: TVar Int
, clientServerCallbackOverflowed :: TVar Bool
, clientConnectedOnce :: TVar Bool
, clientConnectionGeneration :: TVar Int
, clientInitialConnection :: TMVar (Either ConnectError ())
, clientSidCounter :: TVar SIDCounter
, clientInboxNuid :: TVar Nuid
, clientServerInfo :: TVar (Maybe Info)
, clientAttemptIndex :: TVar Int
, clientAttemptEpoch :: TVar Int
, clientStatus :: TVar ConnectionState
, clientConnection :: Conn
, clientLogContext :: TVar LogContext
, clientManagedThreads :: TVar [ThreadId]
, clientSubscriptionGate :: TMVar ()
}
data PingResult = PingReceived | PingConnectionLost
deriving (Eq, Show)
data PublishEnqueueResult = PublishEnqueued Int
| PublishTooLarge Int
| PublishConnectionClosed ClientExitReason
deriving (Eq, Show)
data ClientCallback = CallbackServerError ServerError
| CallbackConnectionEvent ConnectionEvent
data ServerErrorEnqueueResult = ServerErrorQueued | ServerErrorDropped | ServerErrorDroppedReport
deriving (Eq, Show)
data CallbackWorker = CallbackWorker
{ callbackWorkerThreadId :: ThreadId
, callbackWorkerDone :: TMVar ()
}
newClientState :: ClientConfig -> Queue -> Conn -> TVar LogContext -> IO ClientState
newClientState cfg queue conn logContext = do
Queue.close queue
pings <- newTQueueIO
callbacks <- newTQueueIO
pendingServerCallbacks <- newTVarIO 0
pendingServerCallbackBytes <- newTVarIO 0
serverCallbackOverflowed <- newTVarIO False
connectedOnce <- newTVarIO False
connectionGeneration <- newTVarIO 0
initialConnection <- newEmptyTMVarIO
sidCounter <- newTVarIO initialSIDCounter
inboxNuid <- newTVarIO =<< newNuidIO
serverInfo <- newTVarIO Nothing
attemptIndex <- newTVarIO 0
attemptEpoch <- newTVarIO 0
status <- newTVarIO ConnectionConnecting
managedThreads <- newTVarIO []
subscriptionGate <- newTMVarIO ()
pure ClientState
{ clientConfig = cfg
, clientQueue = queue
, clientPings = pings
, clientCallbacks = callbacks
, clientPendingServerCallbacks = pendingServerCallbacks
, clientPendingServerCallbackBytes = pendingServerCallbackBytes
, clientServerCallbackOverflowed = serverCallbackOverflowed
, clientConnectedOnce = connectedOnce
, clientConnectionGeneration = connectionGeneration
, clientInitialConnection = initialConnection
, clientSidCounter = sidCounter
, clientInboxNuid = inboxNuid
, clientServerInfo = serverInfo
, clientAttemptIndex = attemptIndex
, clientAttemptEpoch = attemptEpoch
, clientStatus = status
, clientConnection = conn
, clientLogContext = logContext
, clientManagedThreads = managedThreads
, clientSubscriptionGate = subscriptionGate
}
runClient :: ClientState -> AppM a -> IO a
runClient client =
runWithLogger loggerApi (loggerConfig (clientConfig client)) (clientLogContext client)
config :: ClientState -> ClientConfig
config = clientConfig
queue :: ClientState -> Queue
queue = clientQueue
connection :: ClientState -> Conn
connection = clientConnection
enqueue :: ClientState -> QueueItem -> IO (Either String ())
enqueue client = Queue.enqueue (clientQueue client)
tryEnqueue :: ClientState -> QueueItem -> IO TryEnqueueResult
tryEnqueue client = Queue.tryEnqueue (clientQueue client)
openQueue :: ClientState -> IO ()
openQueue = Queue.open . clientQueue
closeQueue :: ClientState -> IO ()
closeQueue = Queue.close . clientQueue
discardConnectionScoped :: ClientState -> IO ()
discardConnectionScoped = Queue.discardConnectionScoped . clientQueue
registerPingWaiter :: ClientState -> TMVar PingResult -> IO (Either ClientExitReason ())
registerPingWaiter client waiter = atomically $ do
status <- readTVar (clientStatus client)
case status of
ConnectionConnecting -> retry
ConnectionReconnecting -> retry
ConnectionConnected -> do
generation <- readTVar (clientConnectionGeneration client)
writeTQueue (clientPings client) (generation, waiter)
pure (Right ())
ConnectionClosing reason -> pure (Left reason)
ConnectionClosed reason -> pure (Left reason)
registerPingWaiterAndEnqueue
:: ClientState
-> TMVar PingResult
-> QueueItem
-> IO (Either ClientExitReason Int)
registerPingWaiterAndEnqueue client waiter item = do
connected <- atomically (waitForConnectedGeneration client)
case connected of
Left reason -> pure (Left reason)
Right generation -> atomically $ do
result <- enqueueOnGenerationSTM client generation item
case result of
Left reason -> pure (Left reason)
Right () -> do
writeTQueue (clientPings client) (generation, waiter)
pure (Right generation)
enqueueOnGenerationTracked
:: ClientState
-> Int
-> TMVar Int
-> QueueItem
-> IO (Either ClientExitReason Int)
enqueueOnGenerationTracked client expectedGeneration committedGeneration item =
atomically $ do
result <- enqueueOnGenerationSTM client expectedGeneration item
case result of
Left reason -> pure (Left reason)
Right () -> do
putTMVar committedGeneration expectedGeneration
pure (Right expectedGeneration)
enqueuePublishOnConnected
:: ClientState
-> Int
-> QueueItem
-> IO PublishEnqueueResult
enqueuePublishOnConnected client actualSize item =
atomically (enqueuePublishOnConnectedSTM client Nothing actualSize item)
enqueuePublishOnConnectedGenerationTracked
:: ClientState
-> TMVar Int
-> Int
-> QueueItem
-> IO PublishEnqueueResult
enqueuePublishOnConnectedGenerationTracked client committedGeneration actualSize item =
atomically $
enqueuePublishOnConnectedSTM client (Just committedGeneration) actualSize item
enqueuePublishOnConnectedSTM
:: ClientState
-> Maybe (TMVar Int)
-> Int
-> QueueItem
-> STM PublishEnqueueResult
enqueuePublishOnConnectedSTM client committedGeneration actualSize item = do
status <- readTVar (clientStatus client)
case status of
ConnectionConnecting -> retry
ConnectionReconnecting -> retry
ConnectionConnected -> do
generation <- readTVar (clientConnectionGeneration client)
serverInfo <- readTVar (clientServerInfo client)
let maximumSize = effectivePayloadLimit client serverInfo
if actualSize > maximumSize
then pure (PublishTooLarge maximumSize)
else do
queued <- Queue.enqueueSTM (clientQueue client) item
case queued of
Left _ -> pure (PublishConnectionClosed ExitResetRequested)
Right () -> do
mapM_ (`putTMVar` generation) committedGeneration
pure (PublishEnqueued generation)
ConnectionClosing reason -> pure (PublishConnectionClosed reason)
ConnectionClosed reason -> pure (PublishConnectionClosed reason)
effectivePayloadLimit :: ClientState -> Maybe Info -> Int
effectivePayloadLimit client =
maybe
(messageLimit (clientConfig client))
(min (messageLimit (clientConfig client)) . Info.max_payload)
waitForConnectedGeneration :: ClientState -> STM (Either ClientExitReason Int)
waitForConnectedGeneration client = do
status <- readTVar (clientStatus client)
case status of
ConnectionConnecting -> retry
ConnectionReconnecting -> retry
ConnectionConnected ->
Right <$> readTVar (clientConnectionGeneration client)
ConnectionClosing reason -> pure (Left reason)
ConnectionClosed reason -> pure (Left reason)
enqueueOnGenerationSTM
:: ClientState
-> Int
-> QueueItem
-> STM (Either ClientExitReason ())
enqueueOnGenerationSTM client expectedGeneration item = do
status <- readTVar (clientStatus client)
generation <- readTVar (clientConnectionGeneration client)
case status of
ConnectionConnecting -> pure (Left ExitResetRequested)
ConnectionReconnecting -> pure (Left ExitResetRequested)
ConnectionConnected -> do
if generation /= expectedGeneration
then pure (Left ExitResetRequested)
else Queue.enqueueSTM (clientQueue client) item >>= \case
Right () -> pure (Right ())
Left _ -> pure (Left ExitResetRequested)
ConnectionClosing reason -> pure (Left reason)
ConnectionClosed reason -> pure (Left reason)
tryEnqueueOnGeneration
:: ClientState
-> Int
-> QueueItem
-> IO TryEnqueueResult
tryEnqueueOnGeneration client expectedGeneration item = atomically $ do
status <- readTVar (clientStatus client)
generation <- readTVar (clientConnectionGeneration client)
if status == ConnectionConnected && generation == expectedGeneration
then Queue.tryEnqueueSTM (clientQueue client) item
else pure Queue.TryQueueClosed
resolveNextPing :: ClientState -> IO ()
resolveNextPing client =
atomically $ do
generation <- readTVar (clientConnectionGeneration client)
resolveNextPingSTM client generation
resolveNextPingSTM :: ClientState -> Int -> STM ()
resolveNextPingSTM client expectedGeneration = do
currentGeneration <- readTVar (clientConnectionGeneration client)
when (expectedGeneration == currentGeneration) resolveCurrent
where
resolveCurrent = do
pending <- tryReadTQueue (clientPings client)
case pending of
Nothing -> pure ()
Just (generation, waiter) -> do
when (generation == expectedGeneration) $
void (tryPutTMVar waiter PingReceived)
clearPendingPings :: ClientState -> IO ()
clearPendingPings = atomically . clearPendingPingsSTM
nextSid :: ClientState -> IO SID
nextSid client =
atomically $ do
counter <- readTVar (clientSidCounter client)
let (sid, counter') = nextSID counter
writeTVar (clientSidCounter client) counter'
pure sid
nextInbox :: ClientState -> IO Subject
nextInbox client =
atomically $ do
nuid <- readTVar (clientInboxNuid client)
let (token, nextNuidValue) = nextNuid nuid
writeTVar (clientInboxNuid client) nextNuidValue
pure ("_INBOX." <> token)
readServerInfo :: ClientState -> IO (Maybe Info)
readServerInfo = readTVarIO . clientServerInfo
setServerInfo :: ClientState -> Info -> IO ()
setServerInfo client info =
atomically $ writeTVar (clientServerInfo client) (Just info)
updateLogContextFromInfo :: ClientState -> Info -> IO ()
updateLogContextFromInfo client info =
updateLogContext loggerApi (clientLogContext client) $ \ctx ->
ctx { lcClientId = client_id info }
setConnectName :: ClientState -> Maybe BS.ByteString -> IO ()
setConnectName client maybeName =
updateLogContext loggerApi (clientLogContext client) $ \ctx ->
ctx { lcConnectName = BC.unpack <$> maybeName }
setEndpoint :: ClientState -> (String, Int) -> IO ()
setEndpoint client (host, port) =
updateLogContext loggerApi (clientLogContext client) $ \ctx ->
ctx { lcServer = Just (host ++ ":" ++ show port) }
notifyServerError :: ClientState -> ServerError -> IO ServerErrorEnqueueResult
notifyServerError client = atomically . enqueueServerErrorSTM client
enqueueServerErrorSTM :: ClientState -> ServerError -> STM ServerErrorEnqueueResult
enqueueServerErrorSTM client serverError = do
pending <- readTVar (clientPendingServerCallbacks client)
pendingBytes <- readTVar (clientPendingServerCallbackBytes client)
let reasonBytes = BS.length (serverErrorReason serverError)
hasCapacity =
pending < serverCallbackLimit
&& reasonBytes <= serverCallbackByteLimit - pendingBytes
if hasCapacity
then do
modifyTVar' (clientPendingServerCallbacks client) (+ 1)
modifyTVar' (clientPendingServerCallbackBytes client) (+ reasonBytes)
writeTQueue (clientCallbacks client) (CallbackServerError serverError)
pure ServerErrorQueued
else do
alreadyReported <- readTVar (clientServerCallbackOverflowed client)
if alreadyReported
then pure ServerErrorDropped
else do
writeTVar (clientServerCallbackOverflowed client) True
pure ServerErrorDroppedReport
enqueueCallbackSTM :: ClientState -> ClientCallback -> STM Bool
enqueueCallbackSTM client event =
case event of
CallbackServerError serverError ->
(== ServerErrorQueued) <$> enqueueServerErrorSTM client serverError
CallbackConnectionEvent _ -> do
writeTQueue (clientCallbacks client) event
pure True
serverCallbackLimit :: Int
serverCallbackLimit = 256
serverCallbackByteLimit :: Int
serverCallbackByteLimit = 1024 * 1024
startCallbackWorker :: ClientState -> IO CallbackWorker
startCallbackWorker client = do
done <- newEmptyTMVarIO
thread <- forkIOWithUnmask $ \unmask -> do
threadId <- myThreadId
let signalDone = atomically (void (tryPutTMVar done ()))
registerManagedThread client threadId
(unmask loop `finally` unregisterManagedThread client threadId)
`finally` signalDone
pure (CallbackWorker thread done)
where
loop = do
next <- atomically $
(Just <$> readTQueue (clientCallbacks client))
`orElse` do
status <- readTVar (clientStatus client)
case status of
ConnectionClosed _ -> do
empty <- isEmptyTQueue (clientCallbacks client)
check empty
pure Nothing
_ -> retry
case next of
Nothing -> pure ()
Just (CallbackServerError serverError) -> do
atomically $ do
modifyTVar' (clientPendingServerCallbacks client) (subtract 1)
modifyTVar'
(clientPendingServerCallbackBytes client)
(subtract (BS.length (serverErrorReason serverError)))
pending <- readTVar (clientPendingServerCallbacks client)
pendingBytes <- readTVar (clientPendingServerCallbackBytes client)
when
( pending <= serverCallbackLimit `div` 2
&& pendingBytes <= serverCallbackByteLimit `div` 2
)
(writeTVar (clientServerCallbackOverflowed client) False)
result <- trySync (serverErrorHandler (clientConfig client) serverError)
case result of
Right () -> pure ()
Left _ ->
runClient client $
logMessage Error "server error handler failed"
loop
Just (CallbackConnectionEvent event) -> do
result <- trySync (connectionEventHandler (clientConfig client) event)
case result of
Right () -> pure ()
Left _ ->
runClient client $
logMessage Error "connection event handler failed"
loop
waitForCallbackWorker :: CallbackWorker -> STM ()
waitForCallbackWorker = readTMVar . callbackWorkerDone
stopCallbackWorker :: CallbackWorker -> IO ()
stopCallbackWorker worker = do
killThread (callbackWorkerThreadId worker)
atomically (waitForCallbackWorker worker)
readStatus :: ClientState -> IO ConnectionState
readStatus = readTVarIO . clientStatus
waitForConnected :: ClientState -> STM (Either ClientExitReason ())
waitForConnected client = do
status <- readTVar (clientStatus client)
case status of
ConnectionConnecting -> retry
ConnectionConnected -> pure (Right ())
ConnectionReconnecting -> retry
ConnectionClosing reason -> pure (Left reason)
ConnectionClosed reason -> pure (Left reason)
setReconnecting :: ClientState -> IO Int
setReconnecting client =
atomically $ do
generation <- readTVar (clientConnectionGeneration client)
previous <- readTVar (clientStatus client)
case previous of
ConnectionClosing _ -> pure ()
ConnectionClosed _ -> pure ()
ConnectionConnected -> do
writeTVar (clientStatus client) ConnectionReconnecting
void $ enqueueCallbackSTM client
(CallbackConnectionEvent ConnectionEventDisconnected)
_ -> writeTVar (clientStatus client) ConnectionReconnecting
modifyTVar' (clientAttemptEpoch client) (+ 1)
clearPendingPingsSTM client
Queue.closeAndDiscardConnectionScoped (clientQueue client)
pure generation
setClosing :: ClientState -> ClientExitReason -> IO ()
setClosing client reason =
atomically $ do
modifyTVar' (clientStatus client) $ \case
ConnectionClosed result -> ConnectionClosed result
ConnectionClosing result -> ConnectionClosing result
ConnectionConnecting -> ConnectionClosing reason
ConnectionConnected -> ConnectionClosing reason
ConnectionReconnecting -> ConnectionClosing reason
modifyTVar' (clientAttemptEpoch client) (+ 1)
clearPendingPingsSTM client
Queue.closeAndDiscardAll (clientQueue client)
beginConnectionAttempt :: ClientState -> IO (Maybe Int)
beginConnectionAttempt client = atomically $ do
status <- readTVar (clientStatus client)
case status of
ConnectionConnecting -> begin
ConnectionReconnecting -> begin
ConnectionConnected -> pure Nothing
ConnectionClosing _ -> pure Nothing
ConnectionClosed _ -> pure Nothing
where
begin = do
modifyTVar' (clientAttemptEpoch client) (+ 1)
epoch <- readTVar (clientAttemptEpoch client)
pure (Just epoch)
activateConnectionAttempt :: ClientState -> Int -> IO Bool
activateConnectionAttempt client attemptEpoch = atomically $ do
status <- readTVar (clientStatus client)
currentEpoch <- readTVar (clientAttemptEpoch client)
if currentEpoch /= attemptEpoch
then pure False
else case status of
ConnectionConnecting -> activate
ConnectionReconnecting -> activate
ConnectionConnected -> pure False
ConnectionClosing _ -> pure False
ConnectionClosed _ -> pure False
where
activate = pure True
markClosed :: ClientState -> ClientExitReason -> IO (Maybe ClientExitReason)
markClosed client fallbackReason =
atomically $ do
clearPendingPingsSTM client
Queue.closeAndDiscardAll (clientQueue client)
status <- readTVar (clientStatus client)
case status of
ConnectionClosed _ ->
pure Nothing
ConnectionClosing reason -> do
writeTVar (clientStatus client) (ConnectionClosed reason)
void $ enqueueCallbackSTM client
(CallbackConnectionEvent (ConnectionEventClosed reason))
pure (Just reason)
_ -> do
writeTVar (clientStatus client) (ConnectionClosed fallbackReason)
void $ enqueueCallbackSTM client
(CallbackConnectionEvent (ConnectionEventClosed fallbackReason))
pure (Just fallbackReason)
waitForClosed :: ClientState -> STM ()
waitForClosed client = do
status <- readTVar (clientStatus client)
case status of
ConnectionClosed _ -> pure ()
_ -> retry
waitForNotRunning :: ClientState -> STM ()
waitForNotRunning client = do
status <- readTVar (clientStatus client)
case status of
ConnectionClosing _ -> pure ()
ConnectionClosed _ -> pure ()
_ -> retry
waitForServerInfo :: ClientState -> STM ()
waitForServerInfo client = do
info <- readTVar (clientServerInfo client)
case info of
Just _ -> pure ()
Nothing -> retry
connectedOnce :: ClientState -> IO Bool
connectedOnce = readTVarIO . clientConnectedOnce
markConnectionReady :: ClientState -> Int -> IO Bool
markConnectionReady client attemptEpoch = do
(accepted, rejectedPublishes) <-
markConnectionReadyWithRejectedPublishes client attemptEpoch
sequence_ rejectedPublishes
pure accepted
markConnectionReadyWithRejectedPublishes
:: ClientState
-> Int
-> IO (Bool, [IO ()])
markConnectionReadyWithRejectedPublishes client attemptEpoch =
atomically $ do
status <- readTVar (clientStatus client)
currentEpoch <- readTVar (clientAttemptEpoch client)
if currentEpoch /= attemptEpoch
then pure (False, [])
else case status of
ConnectionConnecting -> markReady
ConnectionReconnecting -> markReady
ConnectionConnected -> pure (False, [])
ConnectionClosing _ -> pure (False, [])
ConnectionClosed _ -> pure (False, [])
where
markReady = do
wasConnected <- readTVar (clientConnectedOnce client)
serverInfo <- readTVar (clientServerInfo client)
rejectedPublishes <-
Queue.discardOversizedPayloads
(clientQueue client)
(effectivePayloadLimit client serverInfo)
modifyTVar' (clientConnectionGeneration client) (+ 1)
writeTVar (clientConnectedOnce client) True
writeTVar (clientStatus client) ConnectionConnected
Queue.openSTM (clientQueue client)
void (tryPutTMVar (clientInitialConnection client) (Right ()))
when wasConnected . void $ enqueueCallbackSTM client
(CallbackConnectionEvent ConnectionEventReconnected)
pure (True, rejectedPublishes)
waitForInitialConnection :: ClientState -> STM (Either ConnectError ())
waitForInitialConnection = readTMVar . clientInitialConnection
failInitialConnection :: ClientState -> ConnectError -> IO ()
failInitialConnection client err =
atomically $ void (tryPutTMVar (clientInitialConnection client) (Left err))
readConnectionGeneration :: ClientState -> IO Int
readConnectionGeneration =
readTVarIO . clientConnectionGeneration
waitForConnectionGenerationAfter :: ClientState -> Int -> STM ()
waitForConnectionGenerationAfter client generation = do
status <- readTVar (clientStatus client)
current <- readTVar (clientConnectionGeneration client)
unless (status == ConnectionConnected && current > generation) retry
waitForConnectionGenerationLoss :: ClientState -> Int -> STM ClientExitReason
waitForConnectionGenerationLoss client expectedGeneration = do
status <- readTVar (clientStatus client)
generation <- readTVar (clientConnectionGeneration client)
case status of
ConnectionConnected
| generation == expectedGeneration -> retry
| otherwise -> pure ExitResetRequested
ConnectionClosing reason -> pure reason
ConnectionClosed reason -> pure reason
ConnectionConnecting -> pure ExitResetRequested
ConnectionReconnecting -> pure ExitResetRequested
readAttemptIndex :: ClientState -> IO Int
readAttemptIndex = readTVarIO . clientAttemptIndex
incrementAttemptIndex :: ClientState -> IO ()
incrementAttemptIndex client =
atomically (modifyTVar' (clientAttemptIndex client) (+ 1))
registerManagedThread :: ClientState -> ThreadId -> IO ()
registerManagedThread client threadId =
atomically $ modifyTVar' (clientManagedThreads client) (threadId :)
unregisterManagedThread :: ClientState -> ThreadId -> IO ()
unregisterManagedThread client threadId =
atomically $ modifyTVar' (clientManagedThreads client) (delete threadId)
isManagedThread :: ClientState -> ThreadId -> IO Bool
isManagedThread client threadId =
elem threadId <$> readTVarIO (clientManagedThreads client)
withSubscriptionGate :: ClientState -> IO a -> IO a
withSubscriptionGate client =
bracket_
(atomically (takeTMVar (clientSubscriptionGate client)))
(atomically (putTMVar (clientSubscriptionGate client) ()))
clearPendingPingsSTM :: ClientState -> STM ()
clearPendingPingsSTM client = loop
where
loop = do
waiter <- tryReadTQueue (clientPings client)
case waiter of
Nothing -> pure ()
Just (_, result) -> do
void (tryPutTMVar result PingConnectionLost)
loop