1111{-# LANGUAGE OverloadedStrings #-}
1212{-# LANGUAGE ScopedTypeVariables #-}
1313{-# LANGUAGE StandaloneDeriving #-}
14- {-# LANGUAGE TupleSections #-}
1514{-# LANGUAGE TypeFamilies #-}
15+ {-# LANGUAGE TupleSections #-}
1616
1717module Simplex.Messaging.Server.MsgStore.Journal
1818 ( JournalMsgStore (queues , senders , notifiers , random ),
1919 JournalQueue ,
2020 JournalMsgQueue (queue , state ),
2121 JMQueue (queueDirectory , statePath ),
2222 JournalStoreConfig (.. ),
23- getQueueMessages ,
2423 closeMsgQueue ,
2524 closeMsgQueueHandles ,
2625 -- below are exported for tests
@@ -50,7 +49,7 @@ import qualified Data.ByteString.Char8 as B
5049import Data.Functor (($>) )
5150import Data.Int (Int64 )
5251import Data.List (intercalate )
53- import Data.Maybe (catMaybes , fromMaybe )
52+ import Data.Maybe (catMaybes , fromMaybe , isNothing )
5453import qualified Data.Text as T
5554import Data.Time.Clock (getCurrentTime )
5655import Data.Time.Clock.System (SystemTime (.. ), getSystemTime )
@@ -105,7 +104,9 @@ data JournalQueue = JournalQueue
105104 queueRec :: TVar (Maybe QueueRec ),
106105 msgQueue_ :: TVar (Maybe JournalMsgQueue ),
107106 -- system time in seconds since epoch
108- activeAt :: TVar Int64
107+ activeAt :: TVar Int64 ,
108+ -- Just True - empty, Just False - non-empty, Nothing - unknown
109+ isEmpty :: TVar (Maybe Bool )
109110 }
110111
111112data JMQueue = JMQueue
@@ -224,10 +225,11 @@ instance STMQueueStore JournalMsgStore where
224225 storeLog' = storeLog
225226 mkQueue st qr = do
226227 lock <- getMapLock (queueLocks st) $ recipientId qr
227- q <- newTVar $! Just qr
228+ q <- newTVar $ Just qr
228229 mq <- newTVar Nothing
229230 activeAt <- newTVar 0
230- pure $ JournalQueue lock q mq activeAt
231+ isEmpty <- newTVar Nothing
232+ pure $ JournalQueue lock q mq activeAt isEmpty
231233 msgQueue_' = msgQueue_
232234
233235instance MsgStoreClass JournalMsgStore where
@@ -322,7 +324,7 @@ instance MsgStoreClass JournalMsgStore where
322324 statePath = msgQueueStatePath dir $ B. unpack (strEncode rId)
323325 queue = JMQueue {queueDirectory = dir, statePath}
324326 q <- ifM (doesDirectoryExist dir) (openMsgQueue ms queue) (createQ queue)
325- atomically $ writeTVar msgQueue_ $! Just q
327+ atomically $ writeTVar msgQueue_ $ Just q
326328 pure q
327329 where
328330 createQ :: JMQueue -> IO JournalMsgQueue
@@ -332,14 +334,39 @@ instance MsgStoreClass JournalMsgStore where
332334 journalId <- newJournalId random
333335 mkJournalQueue queue (newMsgQueueState journalId) Nothing
334336
337+ getPeekMsgQueue :: JournalMsgStore -> RecipientId -> JournalQueue -> StoreIO (Maybe (JournalMsgQueue , Message ))
338+ getPeekMsgQueue ms rId q@ JournalQueue {isEmpty} =
339+ StoreIO (readTVarIO isEmpty) >>= \ case
340+ Just True -> pure Nothing
341+ Just False -> peek
342+ Nothing -> do
343+ -- We only close the queue if we just learnt it's empty.
344+ -- This is needed to reduce file descriptors and memory usage
345+ -- after the server just started and many clients subscribe.
346+ -- In case the queue became non-empty on write and then again empty on read
347+ -- we won't be closing it, to avoid frequent open/close on active queues.
348+ r <- peek
349+ when (isNothing r) $ StoreIO $ closeMsgQueue q
350+ pure r
351+ where
352+ peek = do
353+ mq <- getMsgQueue ms rId q
354+ (mq,) <$$> tryPeekMsg_ q mq
355+
356+ -- only runs action if queue is not empty
335357 withIdleMsgQueue :: Int64 -> JournalMsgStore -> RecipientId -> JournalQueue -> (JournalMsgQueue -> StoreIO a ) -> StoreIO (Maybe a , Int )
336358 withIdleMsgQueue now ms@ JournalMsgStore {config} rId q action =
337359 StoreIO $ readTVarIO (msgQueue_ q) >>= \ case
338360 Nothing ->
339- E. bracket (unStoreIO $ getMsgQueue ms rId q) (\ _ -> closeMsgQueue q) $ \ mq -> unStoreIO $ do
340- r <- action mq
341- sz <- getQueueSize_ mq
342- pure (Just r, sz)
361+ E. bracket
362+ (unStoreIO $ getPeekMsgQueue ms rId q)
363+ (mapM_ $ \ _ -> closeMsgQueue q)
364+ (maybe (pure (Nothing , 0 )) (unStoreIO . run))
365+ where
366+ run (mq, _) = do
367+ r <- action mq
368+ sz <- getQueueSize_ mq
369+ pure (Just r, sz)
343370 Just mq -> do
344371 ts <- readTVarIO $ activeAt q
345372 r <- if now - ts >= idleInterval config
@@ -378,6 +405,7 @@ instance MsgStoreClass JournalMsgStore where
378405 let empty = size == 0
379406 if canWrite || empty
380407 then do
408+ atomically $ writeTVar (isEmpty q') (Just False )
381409 let canWrt' = quota > size
382410 if canWrt'
383411 then writeToJournal q st canWrt' msg $> Just (msg, empty)
@@ -426,16 +454,19 @@ instance MsgStoreClass JournalMsgStore where
426454 getQueueSize_ :: JournalMsgQueue -> StoreIO Int
427455 getQueueSize_ JournalMsgQueue {state} = StoreIO $ size <$> readTVarIO state
428456
429- tryPeekMsg_ :: JournalMsgQueue -> StoreIO (Maybe Message )
430- tryPeekMsg_ q@ JournalMsgQueue {tipMsg, handles} =
431- StoreIO $ readTVarIO handles $>>= chooseReadJournal q True $>>= peekMsg
457+ tryPeekMsg_ :: JournalQueue -> JournalMsgQueue -> StoreIO (Maybe Message )
458+ tryPeekMsg_ q mq @ JournalMsgQueue {tipMsg, handles} =
459+ StoreIO $ ( readTVarIO handles $>>= chooseReadJournal mq True $>>= peekMsg) >>= setEmpty
432460 where
433461 peekMsg (rs, h) = readTVarIO tipMsg >>= maybe readMsg (pure . fmap fst )
434462 where
435463 readMsg = do
436464 ml@ (msg, _) <- hGetMsgAt h $ bytePos rs
437465 atomically $ writeTVar tipMsg $ Just (Just ml)
438466 pure $ Just msg
467+ setEmpty msg = do
468+ atomically $ writeTVar (isEmpty q) (Just $ isNothing msg)
469+ pure msg
439470
440471 tryDeleteMsg_ :: JournalQueue -> JournalMsgQueue -> Bool -> StoreIO ()
441472 tryDeleteMsg_ q mq@ JournalMsgQueue {tipMsg, handles} logState = StoreIO $ (`E.finally` when logState (updateActiveAt q)) $
0 commit comments