mirror of
https://github.com/simplex-chat/simplexmq.git
synced 2026-09-01 15:58:23 +00:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8201f2263 | ||
|
|
c1b33e68e3 | ||
|
|
1b8bab91b5 | ||
|
|
70a4984305 | ||
|
|
1aff769285 | ||
|
|
ea714c731c | ||
|
|
cb89b963bf | ||
|
|
d8ec57602f | ||
|
|
b9a5b7802c |
+1
-2
@@ -65,8 +65,7 @@ dependencies:
|
|||||||
- sqlcipher-simple == 0.4.*
|
- sqlcipher-simple == 0.4.*
|
||||||
- stm == 2.5.*
|
- stm == 2.5.*
|
||||||
- temporary == 1.3.*
|
- temporary == 1.3.*
|
||||||
- time == 1.9.*
|
- time == 1.12.*
|
||||||
- time-compat == 1.9.*
|
|
||||||
- time-manager == 0.0.*
|
- time-manager == 0.0.*
|
||||||
- tls >= 1.7.0 && < 1.8
|
- tls >= 1.7.0 && < 1.8
|
||||||
- transformers == 0.6.*
|
- transformers == 0.6.*
|
||||||
|
|||||||
+7
-14
@@ -212,8 +212,7 @@ library
|
|||||||
, sqlcipher-simple ==0.4.*
|
, sqlcipher-simple ==0.4.*
|
||||||
, stm ==2.5.*
|
, stm ==2.5.*
|
||||||
, temporary ==1.3.*
|
, temporary ==1.3.*
|
||||||
, time ==1.9.*
|
, time ==1.12.*
|
||||||
, time-compat ==1.9.*
|
|
||||||
, time-manager ==0.0.*
|
, time-manager ==0.0.*
|
||||||
, tls >=1.7.0 && <1.8
|
, tls >=1.7.0 && <1.8
|
||||||
, transformers ==0.6.*
|
, transformers ==0.6.*
|
||||||
@@ -285,8 +284,7 @@ executable ntf-server
|
|||||||
, sqlcipher-simple ==0.4.*
|
, sqlcipher-simple ==0.4.*
|
||||||
, stm ==2.5.*
|
, stm ==2.5.*
|
||||||
, temporary ==1.3.*
|
, temporary ==1.3.*
|
||||||
, time ==1.9.*
|
, time ==1.12.*
|
||||||
, time-compat ==1.9.*
|
|
||||||
, time-manager ==0.0.*
|
, time-manager ==0.0.*
|
||||||
, tls >=1.7.0 && <1.8
|
, tls >=1.7.0 && <1.8
|
||||||
, transformers ==0.6.*
|
, transformers ==0.6.*
|
||||||
@@ -358,8 +356,7 @@ executable smp-agent
|
|||||||
, sqlcipher-simple ==0.4.*
|
, sqlcipher-simple ==0.4.*
|
||||||
, stm ==2.5.*
|
, stm ==2.5.*
|
||||||
, temporary ==1.3.*
|
, temporary ==1.3.*
|
||||||
, time ==1.9.*
|
, time ==1.12.*
|
||||||
, time-compat ==1.9.*
|
|
||||||
, time-manager ==0.0.*
|
, time-manager ==0.0.*
|
||||||
, tls >=1.7.0 && <1.8
|
, tls >=1.7.0 && <1.8
|
||||||
, transformers ==0.6.*
|
, transformers ==0.6.*
|
||||||
@@ -431,8 +428,7 @@ executable smp-server
|
|||||||
, sqlcipher-simple ==0.4.*
|
, sqlcipher-simple ==0.4.*
|
||||||
, stm ==2.5.*
|
, stm ==2.5.*
|
||||||
, temporary ==1.3.*
|
, temporary ==1.3.*
|
||||||
, time ==1.9.*
|
, time ==1.12.*
|
||||||
, time-compat ==1.9.*
|
|
||||||
, time-manager ==0.0.*
|
, time-manager ==0.0.*
|
||||||
, tls >=1.7.0 && <1.8
|
, tls >=1.7.0 && <1.8
|
||||||
, transformers ==0.6.*
|
, transformers ==0.6.*
|
||||||
@@ -504,8 +500,7 @@ executable xftp
|
|||||||
, sqlcipher-simple ==0.4.*
|
, sqlcipher-simple ==0.4.*
|
||||||
, stm ==2.5.*
|
, stm ==2.5.*
|
||||||
, temporary ==1.3.*
|
, temporary ==1.3.*
|
||||||
, time ==1.9.*
|
, time ==1.12.*
|
||||||
, time-compat ==1.9.*
|
|
||||||
, time-manager ==0.0.*
|
, time-manager ==0.0.*
|
||||||
, tls >=1.7.0 && <1.8
|
, tls >=1.7.0 && <1.8
|
||||||
, transformers ==0.6.*
|
, transformers ==0.6.*
|
||||||
@@ -577,8 +572,7 @@ executable xftp-server
|
|||||||
, sqlcipher-simple ==0.4.*
|
, sqlcipher-simple ==0.4.*
|
||||||
, stm ==2.5.*
|
, stm ==2.5.*
|
||||||
, temporary ==1.3.*
|
, temporary ==1.3.*
|
||||||
, time ==1.9.*
|
, time ==1.12.*
|
||||||
, time-compat ==1.9.*
|
|
||||||
, time-manager ==0.0.*
|
, time-manager ==0.0.*
|
||||||
, tls >=1.7.0 && <1.8
|
, tls >=1.7.0 && <1.8
|
||||||
, transformers ==0.6.*
|
, transformers ==0.6.*
|
||||||
@@ -687,8 +681,7 @@ test-suite simplexmq-test
|
|||||||
, sqlcipher-simple ==0.4.*
|
, sqlcipher-simple ==0.4.*
|
||||||
, stm ==2.5.*
|
, stm ==2.5.*
|
||||||
, temporary ==1.3.*
|
, temporary ==1.3.*
|
||||||
, time ==1.9.*
|
, time ==1.12.*
|
||||||
, time-compat ==1.9.*
|
|
||||||
, time-manager ==0.0.*
|
, time-manager ==0.0.*
|
||||||
, timeit ==2.0.*
|
, timeit ==2.0.*
|
||||||
, tls >=1.7.0 && <1.8
|
, tls >=1.7.0 && <1.8
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ module Simplex.Messaging.Agent
|
|||||||
resubscribeConnection,
|
resubscribeConnection,
|
||||||
resubscribeConnections,
|
resubscribeConnections,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
|
sendMessages,
|
||||||
ackMessage,
|
ackMessage,
|
||||||
switchConnection,
|
switchConnection,
|
||||||
abortConnectionSwitch,
|
abortConnectionSwitch,
|
||||||
@@ -119,14 +120,17 @@ import Data.Bifunctor (bimap, first, second)
|
|||||||
import Data.ByteString.Char8 (ByteString)
|
import Data.ByteString.Char8 (ByteString)
|
||||||
import qualified Data.ByteString.Char8 as B
|
import qualified Data.ByteString.Char8 as B
|
||||||
import Data.Composition ((.:), (.:.), (.::), (.::.))
|
import Data.Composition ((.:), (.:.), (.::), (.::.))
|
||||||
import Data.Foldable (foldl')
|
import Data.Either (rights)
|
||||||
|
import Data.Foldable (foldl', toList)
|
||||||
import Data.Functor (($>))
|
import Data.Functor (($>))
|
||||||
|
import Data.Functor.Identity
|
||||||
import Data.List (find)
|
import Data.List (find)
|
||||||
import Data.List.NonEmpty (NonEmpty (..))
|
import Data.List.NonEmpty (NonEmpty (..))
|
||||||
import qualified Data.List.NonEmpty as L
|
import qualified Data.List.NonEmpty as L
|
||||||
import Data.Map.Strict (Map)
|
import Data.Map.Strict (Map)
|
||||||
import qualified Data.Map.Strict as M
|
import qualified Data.Map.Strict as M
|
||||||
import Data.Maybe (catMaybes, fromMaybe, isJust, isNothing)
|
import Data.Maybe (catMaybes, fromMaybe, isJust, isNothing, mapMaybe)
|
||||||
|
import qualified Data.Set as S
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Data.Time.Clock
|
import Data.Time.Clock
|
||||||
@@ -277,6 +281,12 @@ resubscribeConnections c = withAgentEnv c . resubscribeConnections' c
|
|||||||
sendMessage :: AgentErrorMonad m => AgentClient -> ConnId -> MsgFlags -> MsgBody -> m AgentMsgId
|
sendMessage :: AgentErrorMonad m => AgentClient -> ConnId -> MsgFlags -> MsgBody -> m AgentMsgId
|
||||||
sendMessage c = withAgentEnv c .:. sendMessage' c
|
sendMessage c = withAgentEnv c .:. sendMessage' c
|
||||||
|
|
||||||
|
type MsgReq = (ConnId, MsgFlags, MsgBody)
|
||||||
|
|
||||||
|
-- | Send multiple messages to different connections (SEND command)
|
||||||
|
sendMessages :: MonadUnliftIO m => AgentClient -> [MsgReq] -> m [Either AgentErrorType AgentMsgId]
|
||||||
|
sendMessages c = withAgentEnv c . sendMessages' c
|
||||||
|
|
||||||
ackMessage :: AgentErrorMonad m => AgentClient -> ConnId -> AgentMsgId -> Maybe MsgReceiptInfo -> m ()
|
ackMessage :: AgentErrorMonad m => AgentClient -> ConnId -> AgentMsgId -> Maybe MsgReceiptInfo -> m ()
|
||||||
ackMessage c = withAgentEnv c .:. ackMessage' c
|
ackMessage c = withAgentEnv c .:. ackMessage' c
|
||||||
|
|
||||||
@@ -867,17 +877,29 @@ getNotificationMessage' c nonce encNtfInfo = do
|
|||||||
|
|
||||||
-- | Send message to the connection (SEND command) in Reader monad
|
-- | Send message to the connection (SEND command) in Reader monad
|
||||||
sendMessage' :: forall m. AgentMonad m => AgentClient -> ConnId -> MsgFlags -> MsgBody -> m AgentMsgId
|
sendMessage' :: forall m. AgentMonad m => AgentClient -> ConnId -> MsgFlags -> MsgBody -> m AgentMsgId
|
||||||
sendMessage' c connId msgFlags msg = withConnLock c connId "sendMessage" $ do
|
sendMessage' c connId msgFlags msg = liftEither . runIdentity =<< sendMessagesB c (Identity (Right (connId, msgFlags, msg)))
|
||||||
SomeConn _ conn <- withStore c (`getConn` connId)
|
|
||||||
case conn of
|
-- | Send multiple messages to different connections (SEND command) in Reader monad
|
||||||
DuplexConnection cData _ sqs -> enqueueMsgs cData sqs
|
sendMessages' :: forall m. AgentMonad' m => AgentClient -> [MsgReq] -> m [Either AgentErrorType AgentMsgId]
|
||||||
SndConnection cData sq -> enqueueMsgs cData [sq]
|
sendMessages' c = sendMessagesB c . map Right
|
||||||
_ -> throwError $ CONN SIMPLEX
|
|
||||||
|
sendMessagesB :: forall m t. (AgentMonad' m, Traversable t) => AgentClient -> t (Either AgentErrorType MsgReq) -> m (t (Either AgentErrorType AgentMsgId))
|
||||||
|
sendMessagesB c reqs = withConnLocks c connIds "sendMessages" $ do
|
||||||
|
reqs' <- withStoreBatch c (\db -> fmap (mapE $ \req@(connId, _, _) -> bimap storeError (req,) <$> getConn db connId) reqs)
|
||||||
|
let reqs'' = fmap (>>= prepareConn) reqs'
|
||||||
|
enqueueMessagesB c reqs''
|
||||||
where
|
where
|
||||||
enqueueMsgs :: ConnData -> NonEmpty SndQueue -> m AgentMsgId
|
prepareConn :: (MsgReq, SomeConn) -> Either AgentErrorType (ConnData, NonEmpty SndQueue, MsgFlags, AMessage)
|
||||||
enqueueMsgs cData sqs = do
|
prepareConn ((_, msgFlags, msg), SomeConn _ conn) = case conn of
|
||||||
when (ratchetSyncSendProhibited cData) $ throwError $ CMD PROHIBITED
|
DuplexConnection cData _ sqs -> prepareMsgs cData sqs
|
||||||
enqueueMessages c cData sqs msgFlags $ A_MSG msg
|
SndConnection cData sq -> prepareMsgs cData [sq]
|
||||||
|
_ -> Left $ CONN SIMPLEX
|
||||||
|
where
|
||||||
|
prepareMsgs :: ConnData -> NonEmpty SndQueue -> Either AgentErrorType (ConnData, NonEmpty SndQueue, MsgFlags, AMessage)
|
||||||
|
prepareMsgs cData sqs
|
||||||
|
| ratchetSyncSendProhibited cData = Left $ CMD PROHIBITED
|
||||||
|
| otherwise = Right (cData, sqs, msgFlags, A_MSG msg)
|
||||||
|
connIds = foldl' (\cs -> either (\_ -> cs) (\(connId, _, _) -> S.insert connId cs)) S.empty reqs
|
||||||
|
|
||||||
-- / async command processing v v v
|
-- / async command processing v v v
|
||||||
|
|
||||||
@@ -1056,22 +1078,37 @@ enqueueMessages c cData sqs msgFlags aMessage = do
|
|||||||
enqueueMessages' c cData sqs msgFlags aMessage
|
enqueueMessages' c cData sqs msgFlags aMessage
|
||||||
|
|
||||||
enqueueMessages' :: AgentMonad m => AgentClient -> ConnData -> NonEmpty SndQueue -> MsgFlags -> AMessage -> m AgentMsgId
|
enqueueMessages' :: AgentMonad m => AgentClient -> ConnData -> NonEmpty SndQueue -> MsgFlags -> AMessage -> m AgentMsgId
|
||||||
enqueueMessages' c cData (sq :| sqs) msgFlags aMessage = do
|
enqueueMessages' c cData sqs msgFlags aMessage =
|
||||||
msgId <- enqueueMessage c cData sq msgFlags aMessage
|
liftEither . runIdentity =<< enqueueMessagesB c (Identity (Right (cData, sqs, msgFlags, aMessage)))
|
||||||
mapM_ (enqueueSavedMessage c cData msgId) $
|
|
||||||
filter (\SndQueue {status} -> status == Secured || status == Active) sqs
|
enqueueMessagesB :: (AgentMonad' m, Traversable t) => AgentClient -> t (Either AgentErrorType (ConnData, NonEmpty SndQueue, MsgFlags, AMessage)) -> m (t (Either AgentErrorType AgentMsgId))
|
||||||
pure msgId
|
enqueueMessagesB c reqs = do
|
||||||
|
reqs' <- enqueueMessageB c reqs
|
||||||
|
enqueueSavedMessageB c $ mapMaybe snd $ rights $ toList reqs'
|
||||||
|
pure $ fst <$$> reqs'
|
||||||
|
|
||||||
|
isActiveSndQ :: SndQueue -> Bool
|
||||||
|
isActiveSndQ SndQueue {status} = status == Secured || status == Active
|
||||||
|
|
||||||
enqueueMessage :: forall m. AgentMonad m => AgentClient -> ConnData -> SndQueue -> MsgFlags -> AMessage -> m AgentMsgId
|
enqueueMessage :: forall m. AgentMonad m => AgentClient -> ConnData -> SndQueue -> MsgFlags -> AMessage -> m AgentMsgId
|
||||||
enqueueMessage c cData@ConnData {connId} sq msgFlags aMessage = do
|
enqueueMessage c cData sq msgFlags aMessage =
|
||||||
resumeMsgDelivery c cData sq
|
liftEither . fmap fst . runIdentity =<< enqueueMessageB c (Identity (Right (cData, [sq], msgFlags, aMessage)))
|
||||||
aVRange <- asks $ smpAgentVRange . config
|
|
||||||
msgId <- storeSentMsg $ maxVersion aVRange
|
-- this function is used only for sending messages in batch, it returns the list of successes to enqueue additional deliveries
|
||||||
queuePendingMsgs c sq [msgId]
|
enqueueMessageB :: forall m t. (AgentMonad' m, Traversable t) => AgentClient -> t (Either AgentErrorType (ConnData, NonEmpty SndQueue, MsgFlags, AMessage)) -> m (t (Either AgentErrorType (AgentMsgId, Maybe (ConnData, [SndQueue], AgentMsgId))))
|
||||||
pure $ unId msgId
|
enqueueMessageB c reqs = do
|
||||||
|
void . forME reqs $ \(cData, sq :| _, _, _) ->
|
||||||
|
runExceptT $ resumeMsgDelivery c cData sq
|
||||||
|
aVRange <- asks $ maxVersion . smpAgentVRange . config
|
||||||
|
reqMids <- withStoreBatch c $ \db -> fmap (mapE $ storeSentMsg db aVRange) reqs
|
||||||
|
forME reqMids $ \((cData, sq :| sqs, _, _), mId) -> do
|
||||||
|
let InternalId msgId = mId
|
||||||
|
queuePendingMsgs c sq [mId]
|
||||||
|
let sqs' = filter isActiveSndQ sqs
|
||||||
|
pure $ Right (msgId, if null sqs' then Nothing else Just (cData, sqs', msgId))
|
||||||
where
|
where
|
||||||
storeSentMsg :: Version -> m InternalId
|
storeSentMsg :: DB.Connection -> Version -> (ConnData, NonEmpty SndQueue, MsgFlags, AMessage) -> IO (Either AgentErrorType ((ConnData, NonEmpty SndQueue, MsgFlags, AMessage), InternalId))
|
||||||
storeSentMsg agentVersion = withStore c $ \db -> runExceptT $ do
|
storeSentMsg db agentVersion req@(ConnData {connId}, sq :| _, msgFlags, aMessage) = fmap (first storeError) $ runExceptT $ do
|
||||||
internalTs <- liftIO getCurrentTime
|
internalTs <- liftIO getCurrentTime
|
||||||
(internalId, internalSndId, prevMsgHash) <- liftIO $ updateSndIds db connId
|
(internalId, internalSndId, prevMsgHash) <- liftIO $ updateSndIds db connId
|
||||||
let privHeader = APrivHeader (unSndId internalSndId) prevMsgHash
|
let privHeader = APrivHeader (unSndId internalSndId) prevMsgHash
|
||||||
@@ -1084,14 +1121,25 @@ enqueueMessage c cData@ConnData {connId} sq msgFlags aMessage = do
|
|||||||
msgData = SndMsgData {internalId, internalSndId, internalTs, msgType, msgFlags, msgBody, internalHash, prevMsgHash}
|
msgData = SndMsgData {internalId, internalSndId, internalTs, msgType, msgFlags, msgBody, internalHash, prevMsgHash}
|
||||||
liftIO $ createSndMsg db connId msgData
|
liftIO $ createSndMsg db connId msgData
|
||||||
liftIO $ createSndMsgDelivery db connId sq internalId
|
liftIO $ createSndMsgDelivery db connId sq internalId
|
||||||
pure internalId
|
pure (req, internalId)
|
||||||
|
|
||||||
enqueueSavedMessage :: AgentMonad m => AgentClient -> ConnData -> AgentMsgId -> SndQueue -> m ()
|
enqueueSavedMessage :: AgentMonad' m => AgentClient -> ConnData -> AgentMsgId -> SndQueue -> m ()
|
||||||
enqueueSavedMessage c cData@ConnData {connId} msgId sq = do
|
enqueueSavedMessage c cData msgId sq = enqueueSavedMessageB c $ Identity (cData, [sq], msgId)
|
||||||
resumeMsgDelivery c cData sq
|
|
||||||
let mId = InternalId msgId
|
enqueueSavedMessageB :: (AgentMonad' m, Foldable t) => AgentClient -> t (ConnData, [SndQueue], AgentMsgId) -> m ()
|
||||||
queuePendingMsgs c sq [mId]
|
enqueueSavedMessageB c reqs = do
|
||||||
withStore' c $ \db -> createSndMsgDelivery db connId sq mId
|
-- saving to the database has to be before enqueue to avoid race condition when delivery is read from queue before it is saved
|
||||||
|
void $ withStoreBatch' c $ \db -> concatMap (storeDeliveries db) reqs
|
||||||
|
forM_ reqs $ \(cData, sqs, msgId) ->
|
||||||
|
forM sqs $ \sq -> do
|
||||||
|
void . runExceptT $ resumeMsgDelivery c cData sq
|
||||||
|
let mId = InternalId msgId
|
||||||
|
queuePendingMsgs c sq [mId]
|
||||||
|
where
|
||||||
|
storeDeliveries :: DB.Connection -> (ConnData, [SndQueue], AgentMsgId) -> [IO ()]
|
||||||
|
storeDeliveries db (ConnData {connId}, sqs, msgId) = do
|
||||||
|
let mId = InternalId msgId
|
||||||
|
in map (\sq -> createSndMsgDelivery db connId sq mId) sqs
|
||||||
|
|
||||||
resumeMsgDelivery :: forall m. AgentMonad m => AgentClient -> ConnData -> SndQueue -> m ()
|
resumeMsgDelivery :: forall m. AgentMonad m => AgentClient -> ConnData -> SndQueue -> m ()
|
||||||
resumeMsgDelivery c cData@ConnData {connId} sq@SndQueue {server, sndId} = do
|
resumeMsgDelivery c cData@ConnData {connId} sq@SndQueue {server, sndId} = do
|
||||||
@@ -1885,7 +1933,7 @@ processSMPTransmission c@AgentClient {smpClients, subQ} (tSess@(_, srv, _), v, s
|
|||||||
conn
|
conn
|
||||||
cData@ConnData {userId, connId, duplexHandshake, connAgentVersion, ratchetSyncState = rss} =
|
cData@ConnData {userId, connId, duplexHandshake, connAgentVersion, ratchetSyncState = rss} =
|
||||||
withConnLock c connId "processSMP" $ case cmd of
|
withConnLock c connId "processSMP" $ case cmd of
|
||||||
SMP.MSG msg@SMP.RcvMessage {msgId = srvMsgId} ->
|
SMP.MSG msg@SMP.RcvMessage {msgId = srvMsgId} ->
|
||||||
handleNotifyAck $ do
|
handleNotifyAck $ do
|
||||||
msg' <- decryptSMPMessage v rq msg
|
msg' <- decryptSMPMessage v rq msg
|
||||||
handleNotifyAck $ case msg' of
|
handleNotifyAck $ case msg' of
|
||||||
@@ -2434,8 +2482,7 @@ storeConfirmation c ConnData {connId, connAgentVersion} sq e2eEncryption_ agentM
|
|||||||
enqueueRatchetKeyMsgs :: forall m. AgentMonad m => AgentClient -> ConnData -> NonEmpty SndQueue -> CR.E2ERatchetParams 'C.X448 -> m AgentMsgId
|
enqueueRatchetKeyMsgs :: forall m. AgentMonad m => AgentClient -> ConnData -> NonEmpty SndQueue -> CR.E2ERatchetParams 'C.X448 -> m AgentMsgId
|
||||||
enqueueRatchetKeyMsgs c cData (sq :| sqs) e2eEncryption = do
|
enqueueRatchetKeyMsgs c cData (sq :| sqs) e2eEncryption = do
|
||||||
msgId <- enqueueRatchetKey c cData sq e2eEncryption
|
msgId <- enqueueRatchetKey c cData sq e2eEncryption
|
||||||
mapM_ (enqueueSavedMessage c cData msgId) $
|
mapM_ (enqueueSavedMessage c cData msgId) $ filter isActiveSndQ sqs
|
||||||
filter (\SndQueue {status} -> status == Secured || status == Active) sqs
|
|
||||||
pure msgId
|
pure msgId
|
||||||
|
|
||||||
enqueueRatchetKey :: forall m. AgentMonad m => AgentClient -> ConnData -> SndQueue -> CR.E2ERatchetParams 'C.X448 -> m AgentMsgId
|
enqueueRatchetKey :: forall m. AgentMonad m => AgentClient -> ConnData -> SndQueue -> CR.E2ERatchetParams 'C.X448 -> m AgentMsgId
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ module Simplex.Messaging.Agent.Client
|
|||||||
ProtocolTestStep (..),
|
ProtocolTestStep (..),
|
||||||
newAgentClient,
|
newAgentClient,
|
||||||
withConnLock,
|
withConnLock,
|
||||||
|
withConnLocks,
|
||||||
withInvLock,
|
withInvLock,
|
||||||
closeAgentClient,
|
closeAgentClient,
|
||||||
closeProtocolServerClients,
|
closeProtocolServerClients,
|
||||||
@@ -99,6 +100,8 @@ module Simplex.Messaging.Agent.Client
|
|||||||
withStore',
|
withStore',
|
||||||
withStoreCtx,
|
withStoreCtx,
|
||||||
withStoreCtx',
|
withStoreCtx',
|
||||||
|
withStoreBatch,
|
||||||
|
withStoreBatch',
|
||||||
storeError,
|
storeError,
|
||||||
userServers,
|
userServers,
|
||||||
pickServer,
|
pickServer,
|
||||||
@@ -658,8 +661,17 @@ withConnLock AgentClient {connLocks} connId name = withLockMap_ connLocks connId
|
|||||||
withInvLock :: MonadUnliftIO m => AgentClient -> ByteString -> String -> m a -> m a
|
withInvLock :: MonadUnliftIO m => AgentClient -> ByteString -> String -> m a -> m a
|
||||||
withInvLock AgentClient {invLocks} = withLockMap_ invLocks
|
withInvLock AgentClient {invLocks} = withLockMap_ invLocks
|
||||||
|
|
||||||
|
withConnLocks :: MonadUnliftIO m => AgentClient -> Set ConnId -> String -> m a -> m a
|
||||||
|
withConnLocks AgentClient {connLocks} = withLocksMap_ connLocks . S.filter (not . B.null)
|
||||||
|
|
||||||
withLockMap_ :: (Ord k, MonadUnliftIO m) => TMap k Lock -> k -> String -> m a -> m a
|
withLockMap_ :: (Ord k, MonadUnliftIO m) => TMap k Lock -> k -> String -> m a -> m a
|
||||||
withLockMap_ locks key = withGetLock $ TM.lookup key locks >>= maybe newLock pure
|
withLockMap_ = withGetLock . getMapLock
|
||||||
|
|
||||||
|
withLocksMap_ :: (Ord k, MonadUnliftIO m) => TMap k Lock -> Set k -> String -> m a -> m a
|
||||||
|
withLocksMap_ = withGetLocks . getMapLock
|
||||||
|
|
||||||
|
getMapLock :: Ord k => TMap k Lock -> k -> STM Lock
|
||||||
|
getMapLock locks key = TM.lookup key locks >>= maybe newLock pure
|
||||||
where
|
where
|
||||||
newLock = createLock >>= \l -> TM.insert key l locks $> l
|
newLock = createLock >>= \l -> TM.insert key l locks $> l
|
||||||
|
|
||||||
@@ -1291,6 +1303,18 @@ withStoreCtx_ ctx_ c action = do
|
|||||||
handleInternal :: String -> E.SomeException -> IO (Either StoreError a)
|
handleInternal :: String -> E.SomeException -> IO (Either StoreError a)
|
||||||
handleInternal ctxStr e = pure . Left . SEInternal . B.pack $ show e <> ctxStr
|
handleInternal ctxStr e = pure . Left . SEInternal . B.pack $ show e <> ctxStr
|
||||||
|
|
||||||
|
withStoreBatch :: (AgentMonad' m, Traversable t) => AgentClient -> (DB.Connection -> t (IO (Either AgentErrorType a))) -> m (t (Either AgentErrorType a))
|
||||||
|
withStoreBatch c actions = do
|
||||||
|
st <- asks store
|
||||||
|
liftIO $ agentOperationBracket c AODatabase (\_ -> pure ()) $
|
||||||
|
withTransaction st $ mapM (`E.catch` handleInternal) . actions
|
||||||
|
where
|
||||||
|
handleInternal :: E.SomeException -> IO (Either AgentErrorType a)
|
||||||
|
handleInternal = pure . Left . INTERNAL . show
|
||||||
|
|
||||||
|
withStoreBatch' :: (AgentMonad' m, Traversable t) => AgentClient -> (DB.Connection -> t (IO a)) -> m (t (Either AgentErrorType a))
|
||||||
|
withStoreBatch' c actions = withStoreBatch c $ \db -> fmap Right <$> actions db
|
||||||
|
|
||||||
storeError :: StoreError -> AgentErrorType
|
storeError :: StoreError -> AgentErrorType
|
||||||
storeError = \case
|
storeError = \case
|
||||||
SEConnNotFound -> CONN NOT_FOUND
|
SEConnNotFound -> CONN NOT_FOUND
|
||||||
|
|||||||
@@ -1,8 +1,20 @@
|
|||||||
module Simplex.Messaging.Agent.Lock where
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
|
|
||||||
|
module Simplex.Messaging.Agent.Lock
|
||||||
|
( Lock,
|
||||||
|
createLock,
|
||||||
|
withLock,
|
||||||
|
withGetLock,
|
||||||
|
withGetLocks,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
import Control.Monad (void)
|
import Control.Monad (void)
|
||||||
import Control.Monad.IO.Unlift
|
import Control.Monad.IO.Unlift
|
||||||
import Data.Functor (($>))
|
import Data.Functor (($>))
|
||||||
|
import Data.Set (Set)
|
||||||
|
import qualified Data.Set as S
|
||||||
|
import UnliftIO.Async (forConcurrently)
|
||||||
import qualified UnliftIO.Exception as E
|
import qualified UnliftIO.Exception as E
|
||||||
import UnliftIO.STM
|
import UnliftIO.STM
|
||||||
|
|
||||||
@@ -18,9 +30,22 @@ withLock lock name =
|
|||||||
(atomically $ putTMVar lock name)
|
(atomically $ putTMVar lock name)
|
||||||
(void . atomically $ takeTMVar lock)
|
(void . atomically $ takeTMVar lock)
|
||||||
|
|
||||||
withGetLock :: MonadUnliftIO m => STM Lock -> String -> m a -> m a
|
withGetLock :: MonadUnliftIO m => (k -> STM Lock) -> k -> String -> m a -> m a
|
||||||
withGetLock getLock name a =
|
withGetLock getLock key name a =
|
||||||
E.bracket
|
E.bracket
|
||||||
(atomically $ getLock >>= \l -> putTMVar l name $> l)
|
(atomically $ getPutLock getLock key name)
|
||||||
(atomically . takeTMVar)
|
(atomically . takeTMVar)
|
||||||
(const a)
|
(const a)
|
||||||
|
|
||||||
|
withGetLocks :: MonadUnliftIO m => (k -> STM Lock) -> Set k -> String -> m a -> m a
|
||||||
|
withGetLocks getLock keys name = E.bracket holdLocks releaseLocks . const
|
||||||
|
where
|
||||||
|
holdLocks = forConcurrently (S.toList keys) $ \key -> atomically $ getPutLock getLock key name
|
||||||
|
-- only this withGetLocks would be holding the locks,
|
||||||
|
-- so it's safe to combine all lock releases into one transaction
|
||||||
|
releaseLocks = atomically . mapM_ takeTMVar
|
||||||
|
|
||||||
|
-- getLock and putTMVar can be in one transaction on the assumption that getLock doesn't write in case the lock already exists,
|
||||||
|
-- and in case it is created and added to some shared resource (we use TMap) it also helps avoid contention for the newly created lock.
|
||||||
|
getPutLock :: (k -> STM Lock) -> k -> String -> STM Lock
|
||||||
|
getPutLock getLock key name = getLock key >>= \l -> putTMVar l name $> l
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import qualified Data.Attoparsec.ByteString.Char8 as A
|
|||||||
import qualified Data.ByteString.Char8 as B
|
import qualified Data.ByteString.Char8 as B
|
||||||
import Data.Set (Set)
|
import Data.Set (Set)
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
import Data.Time.Calendar.Month.Compat (pattern MonthDay)
|
import Data.Time.Calendar.Month (pattern MonthDay)
|
||||||
import Data.Time.Calendar.OrdinalDate (mondayStartWeek)
|
import Data.Time.Calendar.OrdinalDate (mondayStartWeek)
|
||||||
import Data.Time.Clock (UTCTime (..))
|
import Data.Time.Clock (UTCTime (..))
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
|
|||||||
@@ -85,6 +85,26 @@ unlessM b = ifM b $ pure ()
|
|||||||
($>>=) :: (Monad m, Monad f, Traversable f) => m (f a) -> (a -> m (f b)) -> m (f b)
|
($>>=) :: (Monad m, Monad f, Traversable f) => m (f a) -> (a -> m (f b)) -> m (f b)
|
||||||
f $>>= g = f >>= fmap join . mapM g
|
f $>>= g = f >>= fmap join . mapM g
|
||||||
|
|
||||||
|
mapME :: (Monad m, Traversable t) => (a -> m (Either e b)) -> t (Either e a) -> m (t (Either e b))
|
||||||
|
mapME f = mapM (mapE f)
|
||||||
|
{-# INLINE mapME #-}
|
||||||
|
|
||||||
|
mapME_ :: (Monad m, Traversable t) => (a -> m (Either e b)) -> t (Either e a) -> m ()
|
||||||
|
mapME_ f = mapM_ (mapE f)
|
||||||
|
{-# INLINE mapME_ #-}
|
||||||
|
|
||||||
|
mapE :: Monad m => (a -> m (Either e b)) -> Either e a -> m (Either e b)
|
||||||
|
mapE = either (pure . Left)
|
||||||
|
{-# INLINE mapE #-}
|
||||||
|
|
||||||
|
forME :: (Monad m, Traversable t) => t (Either e a) -> (a -> m (Either e b)) -> m (t (Either e b))
|
||||||
|
forME = flip mapME
|
||||||
|
{-# INLINE forME #-}
|
||||||
|
|
||||||
|
forME_ :: (Monad m, Traversable t) => t (Either e a) -> (a -> m (Either e b)) -> m ()
|
||||||
|
forME_ f = void . forME f
|
||||||
|
{-# INLINE forME_ #-}
|
||||||
|
|
||||||
catchAll :: IO a -> (E.SomeException -> IO a) -> IO a
|
catchAll :: IO a -> (E.SomeException -> IO a) -> IO a
|
||||||
catchAll = E.catch
|
catchAll = E.catch
|
||||||
{-# INLINE catchAll #-}
|
{-# INLINE catchAll #-}
|
||||||
|
|||||||
Reference in New Issue
Block a user