@@ -55,6 +55,7 @@ import Control.Monad.Trans.Except
5555import qualified Data.Attoparsec.ByteString.Char8 as A
5656import Data.ByteString.Char8 (ByteString )
5757import qualified Data.ByteString.Char8 as B
58+ import Data.Either (fromRight )
5859import Data.Functor (($>) )
5960import Data.Int (Int64 )
6061import Data.List (intercalate , sort )
@@ -65,10 +66,11 @@ import Data.Time.Clock (NominalDiffTime, UTCTime, addUTCTime, getCurrentTime)
6566import Data.Time.Clock.System (SystemTime (.. ), getSystemTime )
6667import Data.Time.Format.ISO8601 (iso8601Show , iso8601ParseM )
6768import GHC.IO (catchAny )
68- import Simplex.Messaging.Agent.Client (getMapLock , withLockMap )
69+ import Simplex.Messaging.Agent.Client (getMapLock )
6970import Simplex.Messaging.Agent.Lock
7071import Simplex.Messaging.Encoding.String
7172import Simplex.Messaging.Protocol
73+ import Simplex.Messaging.Server.MsgStore.Journal.SharedLock
7274import Simplex.Messaging.Server.MsgStore.Types
7375import Simplex.Messaging.Server.QueueStore
7476import Simplex.Messaging.Server.QueueStore.Postgres
@@ -87,6 +89,7 @@ data JournalMsgStore s = JournalMsgStore
8789 { config :: JournalStoreConfig s ,
8890 random :: TVar StdGen ,
8991 queueLocks :: TMap RecipientId Lock ,
92+ sharedLock :: TMVar RecipientId ,
9093 queueStore_ :: QStore s ,
9194 expireBackupsBefore :: UTCTime
9295 }
@@ -138,6 +141,7 @@ data QStoreCfg s where
138141data JournalQueue (s :: QSType ) = JournalQueue
139142 { recipientId' :: RecipientId ,
140143 queueLock :: Lock ,
144+ sharedLock :: TMVar RecipientId ,
141145 -- To avoid race conditions and errors when restoring queues,
142146 -- Nothing is written to TVar when queue is deleted.
143147 queueRec' :: TVar (Maybe QueueRec ),
@@ -276,7 +280,8 @@ instance StoreQueueClass (JournalQueue s) where
276280 msgQueue = msgQueue'
277281 {-# INLINE msgQueue #-}
278282 withQueueLock :: JournalQueue s -> String -> IO a -> IO a
279- withQueueLock = withLock' . queueLock
283+ withQueueLock JournalQueue {recipientId', queueLock, sharedLock} =
284+ withLockWaitShared recipientId' queueLock sharedLock
280285 {-# INLINE withQueueLock #-}
281286
282287instance QueueStoreClass (JournalQueue s ) (QStore s ) where
@@ -316,6 +321,27 @@ instance QueueStoreClass (JournalQueue s) (QStore s) where
316321 deleteStoreQueue = withQS deleteStoreQueue
317322 {-# INLINE deleteStoreQueue #-}
318323
324+ mkTempQueue :: JournalMsgStore s -> RecipientId -> QueueRec -> IO (JournalQueue s )
325+ mkTempQueue ms rId qr = createLockIO >>= makeQueue_ ms rId qr
326+ {-# INLINE mkTempQueue #-}
327+
328+ makeQueue_ :: JournalMsgStore s -> RecipientId -> QueueRec -> Lock -> IO (JournalQueue s )
329+ makeQueue_ JournalMsgStore {sharedLock} rId qr queueLock = do
330+ queueRec' <- newTVarIO $ Just qr
331+ msgQueue' <- newTVarIO Nothing
332+ activeAt <- newTVarIO 0
333+ queueState <- newTVarIO Nothing
334+ pure $
335+ JournalQueue
336+ { recipientId' = rId,
337+ queueLock,
338+ sharedLock,
339+ queueRec',
340+ msgQueue',
341+ activeAt,
342+ queueState
343+ }
344+
319345instance MsgStoreClass (JournalMsgStore s ) where
320346 type StoreMonad (JournalMsgStore s ) = StoreIO s
321347 type QueueStore (JournalMsgStore s ) = QStore s
@@ -326,9 +352,10 @@ instance MsgStoreClass (JournalMsgStore s) where
326352 newMsgStore config@ JournalStoreConfig {queueStoreCfg} = do
327353 random <- newTVarIO =<< newStdGen
328354 queueLocks <- TM. emptyIO
355+ sharedLock <- newEmptyTMVarIO
329356 queueStore_ <- newQueueStore @ (JournalQueue s ) queueStoreCfg
330357 expireBackupsBefore <- addUTCTime (- expireBackupsAfter config) <$> getCurrentTime
331- pure JournalMsgStore {config, random, queueLocks, queueStore_, expireBackupsBefore}
358+ pure JournalMsgStore {config, random, queueLocks, sharedLock, queueStore_, expireBackupsBefore}
332359
333360 closeMsgStore :: JournalMsgStore s -> IO ()
334361 closeMsgStore ms = do
@@ -341,10 +368,32 @@ instance MsgStoreClass (JournalMsgStore s) where
341368 withActiveMsgQueues :: Monoid a => JournalMsgStore s -> (JournalQueue s -> IO a ) -> IO a
342369 withActiveMsgQueues = withQS withLoadedQueues . queueStore_
343370
344- withAllMsgQueues :: Monoid a => Bool -> JournalMsgStore s -> (JournalQueue s -> IO a ) -> IO a
345- withAllMsgQueues tty ms action = case queueStore_ ms of
346- MQStore st -> withLoadedQueues st action
347- PQStore st -> foldQueues tty st (mkQueue ms) action
371+ -- This function can only be used in server CLI commands or before server is started.
372+ -- It does not cache queues and is NOT concurrency safe.
373+ unsafeWithAllMsgQueues :: Monoid a => Bool -> JournalMsgStore s -> (JournalQueue s -> IO a ) -> IO a
374+ unsafeWithAllMsgQueues tty ms action = case queueStore_ ms of
375+ MQStore st -> withLoadedQueues st run
376+ PQStore st -> foldQueueRecs tty st $ uncurry (mkTempQueue ms) >=> run
377+ where
378+ run q = do
379+ r <- action q
380+ closeMsgQueue q
381+ pure r
382+
383+ -- This function is concurrency safe, it is used to expire queues.
384+ withAllMsgQueues :: forall a . Monoid a => Bool -> String -> JournalMsgStore s -> (JournalQueue s -> StoreIO s a ) -> IO a
385+ withAllMsgQueues tty op ms@ JournalMsgStore {queueLocks, sharedLock} action = case queueStore_ ms of
386+ MQStore st ->
387+ withLoadedQueues st $ \ q ->
388+ run $ isolateQueue q op $ action q
389+ PQStore st ->
390+ foldQueueRecs tty st $ \ (rId, qr) -> do
391+ q <- mkTempQueue ms rId qr
392+ withSharedWaitLock rId queueLocks sharedLock $
393+ run $ tryStore' op rId $ unStoreIO $ action q
394+ where
395+ run :: ExceptT ErrorType IO a -> IO a
396+ run = fmap (fromRight mempty ) . runExceptT
348397
349398 logQueueStates :: JournalMsgStore s -> IO ()
350399 logQueueStates ms = withActiveMsgQueues ms $ unStoreIO . logQueueState
@@ -361,20 +410,11 @@ instance MsgStoreClass (JournalMsgStore s) where
361410
362411 mkQueue :: JournalMsgStore s -> RecipientId -> QueueRec -> IO (JournalQueue s )
363412 mkQueue ms rId qr = do
364- queueLock <- atomically $ getMapLock (queueLocks ms) rId
365- queueRec' <- newTVarIO $ Just qr
366- msgQueue' <- newTVarIO Nothing
367- activeAt <- newTVarIO 0
368- queueState <- newTVarIO Nothing
369- pure $
370- JournalQueue
371- { recipientId' = rId,
372- queueLock,
373- queueRec',
374- msgQueue',
375- activeAt,
376- queueState
377- }
413+ lock <- atomically $ getMapLock (queueLocks ms) rId
414+ makeQueue_ ms rId qr lock
415+
416+ getLoadedQueue :: JournalMsgStore s -> JournalQueue s -> StoreIO s (JournalQueue s )
417+ getLoadedQueue ms sq = StoreIO $ fromMaybe sq <$> TM. lookupIO (recipientId sq) (loadedQueues $ queueStore_ ms)
378418
379419 getMsgQueue :: JournalMsgStore s -> JournalQueue s -> Bool -> StoreIO s (JournalMsgQueue s )
380420 getMsgQueue ms@ JournalMsgStore {random} q'@ JournalQueue {recipientId' = rId, msgQueue'} forWrite =
@@ -546,8 +586,11 @@ instance MsgStoreClass (JournalMsgStore s) where
546586 $>>= \ hs -> updateReadPos q mq logState len hs $> Just ()
547587
548588 isolateQueue :: JournalQueue s -> String -> StoreIO s a -> ExceptT ErrorType IO a
549- isolateQueue JournalQueue {recipientId' = rId, queueLock} op a =
550- tryStore' op rId $ withLock' queueLock op $ unStoreIO a
589+ isolateQueue sq op = tryStore' op (recipientId' sq) . withQueueLock sq op . unStoreIO
590+
591+ unsafeRunStore :: JournalQueue s -> String -> StoreIO s a -> IO a
592+ unsafeRunStore sq op a =
593+ unStoreIO a `E.catch` \ e -> storeError op (recipientId' sq) e >> E. throwIO e
551594
552595updateActiveAt :: JournalQueue s -> IO ()
553596updateActiveAt q = atomically . writeTVar (activeAt q) . systemSeconds =<< getSystemTime
@@ -556,15 +599,16 @@ tryStore' :: String -> RecipientId -> IO a -> ExceptT ErrorType IO a
556599tryStore' op rId = tryStore op rId . fmap Right
557600
558601tryStore :: forall a . String -> RecipientId -> IO (Either ErrorType a ) -> ExceptT ErrorType IO a
559- tryStore op rId a = ExceptT $ E. mask_ $ E. try a >>= either storeErr pure
560- where
561- storeErr :: E. SomeException -> IO (Either ErrorType a )
562- storeErr e =
563- let e' = intercalate " , " [op, B. unpack $ strEncode rId, show e]
564- in logError (" STORE: " <> T. pack e') $> Left (STORE e')
602+ tryStore op rId a = ExceptT $ E. mask_ $ a `E.catch` storeError op rId
603+
604+ storeError :: String -> RecipientId -> E. SomeException -> IO (Either ErrorType a )
605+ storeError op rId e =
606+ let e' = intercalate " , " [op, B. unpack $ strEncode rId, show e]
607+ in logError (" STORE: " <> T. pack e') $> Left (STORE e')
565608
566609isolateQueueId :: String -> JournalMsgStore s -> RecipientId -> IO (Either ErrorType a ) -> ExceptT ErrorType IO a
567- isolateQueueId op ms rId = tryStore op rId . withLockMap (queueLocks ms) rId op
610+ isolateQueueId op JournalMsgStore {queueLocks, sharedLock} rId =
611+ tryStore op rId . withLockMapWaitShared rId queueLocks sharedLock op
568612
569613openMsgQueue :: JournalMsgStore s -> JMQueue -> Bool -> IO (JournalMsgQueue s )
570614openMsgQueue ms@ JournalMsgStore {config} q@ JMQueue {queueDirectory = dir, statePath} forWrite = do
0 commit comments