packages feed

cachix-1.11.0: src/Cachix/Daemon/SocketStore.hs

module Cachix.Daemon.SocketStore
  ( newSocketStore,
    addSocket,
    removeSocket,
    addPublisherThread,
    cancelPublisherThread,
    toList,
    Socket (..),
    sendAll,
  )
where

import Cachix.Daemon.Types.PushEvent (PushRequestId)
import Cachix.Daemon.Types.SocketStore (Socket (..), SocketId, SocketStore (..))
import Control.Concurrent.MVar
import Control.Concurrent.STM.TVar
import Control.Monad.IO.Unlift (MonadUnliftIO)
import Data.ByteString.Lazy qualified as LBS
import Data.HashMap.Strict qualified as HashMap
import Data.UUID.V4 qualified as UUID
import Network.Socket qualified
import Network.Socket.ByteString.Lazy qualified as Socket.LBS
import Protolude hiding (toList)
import UnliftIO.Async qualified as Async

newSocketStore :: (MonadIO m) => m SocketStore
newSocketStore = SocketStore <$> liftIO (newTVarIO mempty)

newSocketId :: (MonadIO m) => m SocketId
newSocketId = liftIO UUID.nextRandom

addSocket :: (MonadUnliftIO m) => Network.Socket.Socket -> (SocketId -> Network.Socket.Socket -> m ()) -> SocketStore -> m ()
addSocket socket handler (SocketStore st) = do
  socketId <- newSocketId
  sendLock <- liftIO $ newMVar ()
  handlerThread <- Async.async (handler socketId socket)
  publisherThreads <- liftIO $ newTVarIO HashMap.empty
  liftIO $ atomically $ modifyTVar' st $ HashMap.insert socketId (Socket {..})

removeSocket :: (MonadIO m) => SocketId -> SocketStore -> m ()
removeSocket socketId (SocketStore stvar) = do
  msocket <- liftIO $ atomically $ stateTVar stvar $ \st ->
    let msocket = HashMap.lookup socketId st
     in (msocket, HashMap.delete socketId st)

  for_ msocket $ \sock -> do
    -- Cancel all publisher threads for this socket
    publishers <- liftIO $ readTVarIO (publisherThreads sock)
    mapM_ Async.cancel (HashMap.elems publishers)
    -- Cancel handler thread
    Async.uninterruptibleCancel (handlerThread sock)

addPublisherThread :: (MonadIO m) => SocketId -> PushRequestId -> Async () -> SocketStore -> m ()
addPublisherThread socketId pushId thread (SocketStore stvar) = liftIO $ atomically $ do
  sockets <- readTVar stvar
  for_ (HashMap.lookup socketId sockets) $ \sock ->
    modifyTVar' (publisherThreads sock) $ HashMap.insert pushId thread

cancelPublisherThread :: (MonadIO m) => SocketId -> PushRequestId -> SocketStore -> m ()
cancelPublisherThread socketId pushId (SocketStore stvar) = do
  mthread <- liftIO $ atomically $ do
    sockets <- readTVar stvar
    case HashMap.lookup socketId sockets of
      Nothing -> return Nothing
      Just sock -> stateTVar (publisherThreads sock) $ \threads ->
        (HashMap.lookup pushId threads, HashMap.delete pushId threads)
  mapM_ Async.cancel mthread

toList :: (MonadIO m) => SocketStore -> m [Socket]
toList (SocketStore st) = do
  hm <- liftIO $ readTVarIO st
  return $ HashMap.elems hm

sendAll :: (MonadIO m) => SocketId -> LBS.ByteString -> SocketStore -> m ()
sendAll socketId msg (SocketStore stvar) = do
  mSocket <- liftIO $ readTVarIO stvar
  case HashMap.lookup socketId mSocket of
    Just (Socket {socket, sendLock}) -> liftIO $ withMVar sendLock $ \_ -> Socket.LBS.sendAll socket msg
    Nothing -> return ()