Compare commits

...
9 Commits
Author SHA1 Message Date
Evgeny Poberezkin e64b6cba4b 5.5.2.0 2024-02-10 22:07:02 +00:00
Evgeny Poberezkin 189885c50d protocol: ignore tail bytes in handshake blocks to allow future extension (#983)
* protocol: ignore tail bytes in handshake blocks to allow future extension

* cleanup
2024-02-05 21:01:20 +00:00
Evgeny Poberezkin a516c2f72c 5.5.1.2 2024-02-02 08:19:02 +00:00
Evgeny Poberezkin 2ae1c9f79d agent: only retry connecting SMP clients when there are pending subscriptions (#981)
* agent: only retry connecting SMP clients when there are pending subscriptions

* fix

* remove retry on creating clients

* simplify
2024-02-01 16:17:37 +00:00
Alexander BondarenkoandEvgeny Poberezkin 24b84106a6 core: add activity tests to workers details (#980)
* core: add activity tests to workers details

* remove names

---------

Co-authored-by: Evgeny Poberezkin <[email protected]>
2024-02-01 11:02:31 +00:00
Alexander BondarenkoandEvgeny Poberezkin 15bc027f23 core: fix reconnection bugs (#979)
* core: fix reconnection bugs

* untangle newProtocolClient

* refactor

* report busy clientVar error

* log error

* comments

---------

Co-authored-by: Evgeny Poberezkin <[email protected]>
2024-02-01 10:55:50 +00:00
Alexander Bondarenko cb64dabf75 tests: fix sporadic failures from reading stats (#975) 2024-01-30 11:36:07 +00:00
Evgeny Poberezkin 7a0cd8041b 5.5.1.1 2024-01-22 14:06:52 +00:00
Evgeny Poberezkinandspaced4ndy fd4eeb36db agent: optimize expired messages query (#976)
* agent: optimize expired messages query

* schema

* fix query

* fix

* typo

Co-authored-by: spaced4ndy <[email protected]>

* fix

* refactor

* comment

* refactor2

---------

Co-authored-by: spaced4ndy <[email protected]>
2024-01-22 14:04:57 +00:00
18 changed files with 160 additions and 128 deletions
+17
View File
@@ -1,3 +1,20 @@
# 5.5.2
Extensible handshake for clients and SMP/NTF servers (ignore extra data).
# 5.5.1
SMP servers:
- do not keep stats file open
- additional stats about currently stored messages
Agent:
- support multiple notification servers (only one can be used at a time).
- expire messages after "quota exceeded" error after 7 days (instead of 21 days previously).
- stabilize message delivery, remove unnecessary subscription retries and traffic.
- improve database performance for message delivery.
- fix sockets/memory leak - a very old bug "activated" by improvements in v5.5.0.
# 5.5.0
Code:
+1 -1
View File
@@ -1,5 +1,5 @@
name: simplexmq
version: 5.5.1.0
version: 5.5.2.0
synopsis: SimpleXMQ message broker
description: |
This package includes <./docs/Simplex-Messaging-Server.html server>,
+2 -2
View File
@@ -5,7 +5,7 @@ cabal-version: 1.12
-- see: https://github.com/sol/hpack
name: simplexmq
version: 5.5.1.0
version: 5.5.2.0
synopsis: SimpleXMQ message broker
description: This package includes <./docs/Simplex-Messaging-Server.html server>,
<./docs/Simplex-Messaging-Client.html client> and
@@ -101,7 +101,7 @@ library
Simplex.Messaging.Agent.Store.SQLite.Migrations.M20230829_crypto_files
Simplex.Messaging.Agent.Store.SQLite.Migrations.M20231222_command_created_at
Simplex.Messaging.Agent.Store.SQLite.Migrations.M20231225_failed_work_items
Simplex.Messaging.Agent.TAsyncs
Simplex.Messaging.Agent.Store.SQLite.Migrations.M20240121_message_delivery_indexes
Simplex.Messaging.Agent.TRcvQueues
Simplex.Messaging.Client
Simplex.Messaging.Client.Agent
+1 -1
View File
@@ -33,7 +33,7 @@ import System.IO (BufferMode (..), hSetBuffering, stderr, stdout)
import Text.Read (readMaybe)
xftpServerVersion :: String
xftpServerVersion = "1.2.1.0"
xftpServerVersion = "1.2.2.0"
xftpServerCLI :: FilePath -> FilePath -> IO ()
xftpServerCLI cfgPath logPath = do
+53 -62
View File
@@ -155,6 +155,7 @@ import Data.Text.Encoding
import Data.Time (UTCTime, defaultTimeLocale, formatTime, getCurrentTime)
import Data.Time.Clock.System (getSystemTime)
import Data.Word (Word16)
-- import GHC.Conc (unsafeIOToSTM)
import Network.Socket (HostName)
import Simplex.FileTransfer.Client (XFTPChunkSpec (..), XFTPClient, XFTPClientConfig (..), XFTPClientError)
@@ -171,7 +172,6 @@ import Simplex.Messaging.Agent.RetryInterval
import Simplex.Messaging.Agent.Store
import Simplex.Messaging.Agent.Store.SQLite (SQLiteStore (..), withTransaction)
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
import Simplex.Messaging.Agent.TAsyncs
import Simplex.Messaging.Agent.TRcvQueues (TRcvQueues (getRcvQueues))
import qualified Simplex.Messaging.Agent.TRcvQueues as RQ
import Simplex.Messaging.Client
@@ -228,7 +228,7 @@ data SessionVar a = SessionVar
sessionVarId :: Int
}
type ClientVar msg = SessionVar (Either AgentErrorType (Client msg))
type ClientVar msg = SessionVar (Either AgentErrorType (Client msg))
type SMPClientVar = ClientVar SMP.BrokerMsg
@@ -277,7 +277,6 @@ data AgentClient = AgentClient
deleteLock :: Lock,
-- smpSubWorkers for SMP servers sessions
smpSubWorkers :: TMap SMPTransportSession (SessionVar (Async ())),
asyncClients :: TAsyncs,
agentStats :: TMap AgentStatsKey (TVar Int),
clientId :: Int,
agentEnv :: Env
@@ -417,7 +416,6 @@ newAgentClient clientId InitialAgentServers {smp, ntf, xftp, netCfg} agentEnv =
invLocks <- TM.empty
deleteLock <- createLock
smpSubWorkers <- TM.empty
asyncClients <- newTAsyncs
agentStats <- TM.empty
return
AgentClient
@@ -451,7 +449,6 @@ newAgentClient clientId InitialAgentServers {smp, ntf, xftp, netCfg} agentEnv =
invLocks,
deleteLock,
smpSubWorkers,
asyncClients,
agentStats,
clientId,
agentEnv
@@ -502,10 +499,11 @@ instance ProtocolServerClient XFTPErrorType FileResponse where
getSMPServerClient :: forall m. AgentMonad m => AgentClient -> SMPTransportSession -> m SMPClient
getSMPServerClient c@AgentClient {active, smpClients, msgQ} tSess@(userId, srv, _) = do
unlessM (readTVarIO active) . throwError $ INACTIVE
atomically (getTSessVar c tSess smpClients)
>>= either newClient (waitForProtocolClient c tSess)
v <- atomically (getTSessVar c tSess smpClients)
either newClient (waitForProtocolClient c tSess) v
`catchAgentError` \e -> resubscribeSMPSession c tSess >> throwError e
where
newClient = newProtocolClient c tSess smpClients connectClient resubscribeSMPSession
newClient = newProtocolClient c tSess smpClients connectClient
connectClient :: SMPClientVar -> m SMPClient
connectClient v = do
cfg <- getClientConfig c smpCfg
@@ -540,8 +538,13 @@ getSMPServerClient c@AgentClient {active, smpClients, msgQ} tSess@(userId, srv,
resubscribeSMPSession :: AgentMonad' m => AgentClient -> SMPTransportSession -> m ()
resubscribeSMPSession c@AgentClient {smpSubWorkers} tSess =
atomically (getTSessVar c tSess smpSubWorkers) >>= either newSubWorker (\_ -> pure ())
atomically getWorkerVar >>= mapM_ (either newSubWorker (\_ -> pure ()))
where
getWorkerVar =
ifM
(null <$> getPending)
(pure Nothing) -- prevent race with cleanup and adding pending queues in another call
(Just <$> getTSessVar c tSess smpSubWorkers)
newSubWorker v = do
a <- async $ void (E.tryAny runSubWorker) >> atomically (cleanup v)
atomically $ putTMVar (sessionVar v) a
@@ -549,10 +552,11 @@ resubscribeSMPSession c@AgentClient {smpSubWorkers} tSess =
ri <- asks $ reconnectInterval . config
timeoutCounts <- newTVarIO 0
withRetryInterval ri $ \_ loop -> do
pending <- atomically . RQ.getSessQueues tSess $ pendingSubs c
pending <- atomically getPending
forM_ (L.nonEmpty pending) $ \qs -> do
void . tryAgentError' $ reconnectSMPClient timeoutCounts c tSess qs
loop
getPending = RQ.getSessQueues tSess $ pendingSubs c
cleanup :: SessionVar (Async ()) -> STM ()
cleanup v = do
-- Here we wait until TMVar is not empty to prevent worker cleanup happening before worker is added to TMVar.
@@ -596,7 +600,7 @@ getNtfServerClient c@AgentClient {active, ntfClients} tSess@(userId, srv, _) = d
unlessM (readTVarIO active) . throwError $ INACTIVE
atomically (getTSessVar c tSess ntfClients)
>>= either
(newProtocolClient c tSess ntfClients connectClient $ \_ _ -> pure ())
(newProtocolClient c tSess ntfClients connectClient)
(waitForProtocolClient c tSess)
where
connectClient :: NtfClientVar -> m NtfClient
@@ -616,7 +620,7 @@ getXFTPServerClient c@AgentClient {active, xftpClients, useNetworkConfig} tSess@
unlessM (readTVarIO active) . throwError $ INACTIVE
atomically (getTSessVar c tSess xftpClients)
>>= either
(newProtocolClient c tSess xftpClients connectClient $ \_ _ -> pure ())
(newProtocolClient c tSess xftpClients connectClient)
(waitForProtocolClient c tSess)
where
connectClient :: XFTPClientVar -> m XFTPClient
@@ -665,35 +669,22 @@ newProtocolClient ::
TransportSession msg ->
TMap (TransportSession msg) (ClientVar msg) ->
(ClientVar msg -> m (Client msg)) ->
(AgentClient -> TransportSession msg -> m ()) ->
ClientVar msg ->
m (Client msg)
newProtocolClient c tSess@(userId, srv, entityId_) clients connectClient clientConnected v = tryConnectClient pure tryConnectAsync
where
tryConnectClient :: (Client msg -> m a) -> m () -> m a
tryConnectClient successAction retryAction =
tryAgentError (connectClient v) >>= \case
Right client -> do
logInfo . decodeUtf8 $ "Agent connected to " <> showServer srv <> " (user " <> bshow userId <> maybe "" (" for entity " <>) entityId_ <> ")"
atomically $ putTMVar (sessionVar v) (Right client)
liftIO $ incClientStat c userId client "CLIENT" "OK"
atomically $ writeTBQueue (subQ c) ("", "", APC SAENone $ hostEvent CONNECT client)
successAction client
Left e -> do
liftIO $ incServerStat c userId srv "CLIENT" $ strEncode e
if temporaryAgentError e
then retryAction
else atomically $ do
putTMVar (sessionVar v) (Left e)
removeTSessVar v tSess clients
throwError e
tryConnectAsync :: m ()
tryConnectAsync = newAsyncAction connectAsync $ asyncClients c
connectAsync :: Int -> m ()
connectAsync aId = do
ri <- asks $ reconnectInterval . config
withRetryInterval ri $ \_ loop -> void $ tryConnectClient (const $ clientConnected c tSess) loop
atomically . removeAsyncAction aId $ asyncClients c
newProtocolClient c tSess@(userId, srv, entityId_) clients connectClient v =
tryAgentError (connectClient v) >>= \case
Right client -> do
logInfo . decodeUtf8 $ "Agent connected to " <> showServer srv <> " (user " <> bshow userId <> maybe "" (" for entity " <>) entityId_ <> ")"
atomically $ putTMVar (sessionVar v) (Right client)
liftIO $ incClientStat c userId client "CLIENT" "OK"
atomically $ writeTBQueue (subQ c) ("", "", APC SAENone $ hostEvent CONNECT client)
pure client
Left e -> do
liftIO $ incServerStat c userId srv "CLIENT" $ strEncode e
atomically $ do
removeTSessVar v tSess clients
putTMVar (sessionVar v) (Left e)
throwError e -- signal error to caller
hostEvent :: forall err msg. (ProtocolTypeI (ProtoType msg), ProtocolServerClient err msg) => (AProtocolType -> TransportHost -> ACommand 'Agent 'AENone) -> Client msg -> ACommand 'Agent 'AENone
hostEvent event = event (AProtocolType $ protocolTypeI @(ProtoType msg)) . clientTransportHost
@@ -711,7 +702,6 @@ closeAgentClient c = liftIO $ do
closeProtocolServerClients c ntfClients
closeProtocolServerClients c xftpClients
atomically (swapTVar (smpSubWorkers c) M.empty) >>= mapM_ cancelReconnect
cancelActions . actions $ asyncClients c
clearWorkers smpDeliveryWorkers >>= mapM_ (cancelWorker . fst)
clearWorkers asyncCmdWorkers >>= mapM_ cancelWorker
clear connCmdsQueued
@@ -724,7 +714,7 @@ closeAgentClient c = liftIO $ do
clearWorkers workers = atomically $ swapTVar (workers c) mempty
clear :: Monoid m => (AgentClient -> TVar m) -> IO ()
clear sel = atomically $ writeTVar (sel c) mempty
cancelReconnect :: SessionVar (Async ()) -> IO ()
cancelReconnect :: SessionVar (Async ()) -> IO ()
cancelReconnect v = void . forkIO $ atomically (readTMVar $ sessionVar v) >>= uninterruptibleCancel
cancelWorker :: Worker -> IO ()
@@ -763,9 +753,6 @@ closeXFTPServerClient :: AgentMonad' m => AgentClient -> UserId -> XFTPServer ->
closeXFTPServerClient c userId server (FileDigest chunkDigest) =
mkTransportSession c userId server chunkDigest >>= liftIO . closeClient c xftpClients
cancelActions :: (Foldable f, Monoid (f (Async ()))) => TVar (f (Async ())) -> IO ()
cancelActions as = atomically (swapTVar as mempty) >>= mapM_ (forkIO . uninterruptibleCancel)
withConnLock :: MonadUnliftIO m => AgentClient -> ConnId -> String -> m a -> m a
withConnLock _ "" _ = id
withConnLock AgentClient {connLocks} connId name = withLockMap_ connLocks connId name
@@ -1556,27 +1543,32 @@ data AgentWorkersDetails = AgentWorkersDetails
{ smpClients_ :: [Text],
ntfClients_ :: [Text],
xftpClients_ :: [Text],
smpDeliveryWorkers_ :: Map Text Int,
asyncCmdWorkers_ :: Map Text Int,
smpDeliveryWorkers_ :: Map Text WorkersDetails,
asyncCmdWorkers_ :: Map Text WorkersDetails,
smpSubWorkers_ :: [Text],
asyncCients_ :: [Int],
ntfWorkers_ :: Map Text Int,
ntfSMPWorkers_ :: Map Text Int,
xftpRcvWorkers_ :: Map Text Int,
xftpSndWorkers_ :: Map Text Int,
xftpDelWorkers_ :: Map Text Int
ntfWorkers_ :: Map Text WorkersDetails,
ntfSMPWorkers_ :: Map Text WorkersDetails,
xftpRcvWorkers_ :: Map Text WorkersDetails,
xftpSndWorkers_ :: Map Text WorkersDetails,
xftpDelWorkers_ :: Map Text WorkersDetails
}
deriving (Show)
data WorkersDetails = WorkersDetails
{ restarts :: Int,
hasWork :: Bool,
hasAction :: Bool
}
deriving (Show)
getAgentWorkersDetails :: MonadIO m => AgentClient -> m AgentWorkersDetails
getAgentWorkersDetails AgentClient {smpClients, ntfClients, xftpClients, smpDeliveryWorkers, asyncCmdWorkers, smpSubWorkers, asyncClients = TAsyncs {actions}, agentEnv} = do
getAgentWorkersDetails AgentClient {smpClients, ntfClients, xftpClients, smpDeliveryWorkers, asyncCmdWorkers, smpSubWorkers, agentEnv} = do
smpClients_ <- textKeys <$> readTVarIO smpClients
ntfClients_ <- textKeys <$> readTVarIO ntfClients
xftpClients_ <- textKeys <$> readTVarIO xftpClients
smpDeliveryWorkers_ <- workerStats . fmap fst =<< readTVarIO smpDeliveryWorkers
asyncCmdWorkers_ <- workerStats =<< readTVarIO asyncCmdWorkers
smpSubWorkers_ <- textKeys <$> readTVarIO smpSubWorkers
asyncCients_ <- M.keys <$> readTVarIO actions
ntfWorkers_ <- workerStats =<< readTVarIO ntfWorkers
ntfSMPWorkers_ <- workerStats =<< readTVarIO ntfSMPWorkers
xftpRcvWorkers_ <- workerStats =<< readTVarIO xftpRcvWorkers
@@ -1590,7 +1582,6 @@ getAgentWorkersDetails AgentClient {smpClients, ntfClients, xftpClients, smpDeli
smpDeliveryWorkers_,
asyncCmdWorkers_,
smpSubWorkers_,
asyncCients_,
ntfWorkers_,
ntfSMPWorkers_,
xftpRcvWorkers_,
@@ -1602,10 +1593,12 @@ getAgentWorkersDetails AgentClient {smpClients, ntfClients, xftpClients, smpDeli
textKeys = map textKey . M.keys
textKey :: StrEncoding k => k -> Text
textKey = decodeASCII . strEncode
workerStats :: (StrEncoding k, MonadIO m) => Map k Worker -> m (Map Text Int)
workerStats ws = fmap M.fromList . forM (M.toList ws) $ \(qa, Worker {restarts}) -> do
workerStats :: (StrEncoding k, MonadIO m) => Map k Worker -> m (Map Text WorkersDetails)
workerStats ws = fmap M.fromList . forM (M.toList ws) $ \(qa, Worker {restarts, doWork, action}) -> do
RestartCount {restartCount} <- readTVarIO restarts
pure (textKey qa, restartCount)
hasWork <- atomically $ not <$> isEmptyTMVar doWork
hasAction <- atomically $ not <$> isEmptyTMVar action
pure (textKey qa, WorkersDetails {restarts = restartCount, hasWork, hasAction})
Env {ntfSupervisor, xftpAgent} = agentEnv
NtfSupervisor {ntfWorkers, ntfSMPWorkers} = ntfSupervisor
XFTPAgent {xftpRcvWorkers, xftpSndWorkers, xftpDelWorkers} = xftpAgent
@@ -1617,7 +1610,6 @@ data AgentWorkersSummary = AgentWorkersSummary
smpDeliveryWorkersCount :: Int,
asyncCmdWorkersCount :: Int,
smpSubWorkersCount :: Int,
asyncCientsCount :: Int,
ntfWorkersCount :: Int,
ntfSMPWorkersCount :: Int,
xftpRcvWorkersCount :: Int,
@@ -1627,14 +1619,13 @@ data AgentWorkersSummary = AgentWorkersSummary
deriving (Show)
getAgentWorkersSummary :: MonadIO m => AgentClient -> m AgentWorkersSummary
getAgentWorkersSummary AgentClient {smpClients, ntfClients, xftpClients, smpDeliveryWorkers, asyncCmdWorkers, smpSubWorkers, asyncClients = TAsyncs {actions}, agentEnv} = do
getAgentWorkersSummary AgentClient {smpClients, ntfClients, xftpClients, smpDeliveryWorkers, asyncCmdWorkers, smpSubWorkers, agentEnv} = do
smpClientsCount <- M.size <$> readTVarIO smpClients
ntfClientsCount <- M.size <$> readTVarIO ntfClients
xftpClientsCount <- M.size <$> readTVarIO xftpClients
smpDeliveryWorkersCount <- M.size <$> readTVarIO smpDeliveryWorkers
asyncCmdWorkersCount <- M.size <$> readTVarIO asyncCmdWorkers
smpSubWorkersCount <- M.size <$> readTVarIO smpSubWorkers
asyncCientsCount <- M.size <$> readTVarIO actions
ntfWorkersCount <- M.size <$> readTVarIO ntfWorkers
ntfSMPWorkersCount <- M.size <$> readTVarIO ntfSMPWorkers
xftpRcvWorkersCount <- M.size <$> readTVarIO xftpRcvWorkers
@@ -1648,7 +1639,6 @@ getAgentWorkersSummary AgentClient {smpClients, ntfClients, xftpClients, smpDeli
smpDeliveryWorkersCount,
asyncCmdWorkersCount,
smpSubWorkersCount,
asyncCientsCount,
ntfWorkersCount,
ntfSMPWorkersCount,
xftpRcvWorkersCount,
@@ -1670,6 +1660,7 @@ $(J.deriveJSON defaultJSON ''SubInfo)
$(J.deriveJSON defaultJSON ''SubscriptionsInfo)
$(J.deriveJSON defaultJSON ''WorkersDetails)
$(J.deriveJSON defaultJSON {J.fieldLabelModifier = takeWhile (/= '_')} ''AgentWorkersDetails)
$(J.deriveJSON defaultJSON ''AgentWorkersSummary)
+4 -4
View File
@@ -34,21 +34,21 @@ import UnliftIO.STM
-- See a full agent executable here: https://github.com/simplex-chat/simplexmq/blob/master/apps/smp-agent/Main.hs
runSMPAgent :: (MonadRandom m, MonadUnliftIO m) => ATransport -> AgentConfig -> InitialAgentServers -> SQLiteStore -> m ()
runSMPAgent t cfg initServers store =
runSMPAgentBlocking t cfg initServers store =<< newEmptyTMVarIO
runSMPAgentBlocking t cfg initServers store 0 =<< newEmptyTMVarIO
-- | Runs an SMP agent as a TCP service using passed configuration with signalling.
--
-- This function uses passed TMVar to signal when the server is ready to accept TCP requests (True)
-- and when it is disconnected from the TCP socket once the server thread is killed (False).
runSMPAgentBlocking :: (MonadRandom m, MonadUnliftIO m) => ATransport -> AgentConfig -> InitialAgentServers -> SQLiteStore -> TMVar Bool -> m ()
runSMPAgentBlocking (ATransport t) cfg@AgentConfig {tcpPort, caCertificateFile, certificateFile, privateKeyFile} initServers store started = do
runSMPAgentBlocking :: (MonadRandom m, MonadUnliftIO m) => ATransport -> AgentConfig -> InitialAgentServers -> SQLiteStore -> Int -> TMVar Bool -> m ()
runSMPAgentBlocking (ATransport t) cfg@AgentConfig {tcpPort, caCertificateFile, certificateFile, privateKeyFile} initServers store initClientId started = do
liftIO (newSMPAgentEnv cfg store) >>= runReaderT (smpAgent t)
where
smpAgent :: forall c m'. (Transport c, MonadUnliftIO m', MonadReader Env m') => TProxy c -> m' ()
smpAgent _ = do
-- tlsServerParams is not in Env to avoid breaking functional API w/t key and certificate generation
tlsServerParams <- liftIO $ loadTLSServerParams caCertificateFile certificateFile privateKeyFile
clientId <- newTVarIO 0
clientId <- newTVarIO initClientId
runTransportServer started tcpPort tlsServerParams defaultTransportServerConfig $ \(h :: c) -> do
liftIO . putLn h $ "Welcome to SMP agent v" <> B.pack simplexMQVersion
cId <- atomically $ stateTVar clientId $ \i -> (i + 1, i + 1)
+25 -12
View File
@@ -1043,18 +1043,31 @@ deletePendingMsgs db connId SndQueue {dbQueueId} =
DB.execute db "DELETE FROM snd_message_deliveries WHERE conn_id = ? AND snd_queue_id = ?" (connId, dbQueueId)
getExpiredSndMessages :: DB.Connection -> ConnId -> SndQueue -> UTCTime -> IO [InternalId]
getExpiredSndMessages db connId SndQueue {dbQueueId} expireTs =
map fromOnly
<$> DB.query
db
[sql|
SELECT d.internal_id
FROM snd_message_deliveries d
JOIN messages m ON m.conn_id = d.conn_id AND m.internal_id = d.internal_id
WHERE d.conn_id = ? AND d.snd_queue_id = ? AND d.failed = 0 AND m.internal_ts < ?
ORDER BY d.internal_id ASC
|]
(connId, dbQueueId, expireTs)
getExpiredSndMessages db connId SndQueue {dbQueueId} expireTs = do
-- type is Maybe InternalId because MAX always returns one row, possibly with NULL value
maxId :: [Maybe InternalId] <-
map fromOnly
<$> DB.query
db
[sql|
SELECT MAX(internal_id)
FROM messages
WHERE conn_id = ? AND internal_snd_id IS NOT NULL AND internal_ts < ?
|]
(connId, expireTs)
case maxId of
Just msgId : _ ->
map fromOnly
<$> DB.query
db
[sql|
SELECT internal_id
FROM snd_message_deliveries
WHERE conn_id = ? AND snd_queue_id = ? AND failed = 0 AND internal_id <= ?
ORDER BY internal_id ASC
|]
(connId, dbQueueId, msgId)
_ -> pure []
setMsgUserAck :: DB.Connection -> ConnId -> InternalId -> IO (Either StoreError (RcvQueue, SMP.MsgId))
setMsgUserAck db connId agentMsgId = runExceptT $ do
@@ -67,6 +67,7 @@ import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20230814_indexes
import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20230829_crypto_files
import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20231222_command_created_at
import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20231225_failed_work_items
import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20240121_message_delivery_indexes
import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (dropPrefix, sumTypeJSON)
import Simplex.Messaging.Transport.Client (TransportHost)
@@ -102,7 +103,8 @@ schemaMigrations =
("m20230814_indexes", m20230814_indexes, Just down_m20230814_indexes),
("m20230829_crypto_files", m20230829_crypto_files, Just down_m20230829_crypto_files),
("m20231222_command_created_at", m20231222_command_created_at, Just down_m20231222_command_created_at),
("m20231225_failed_work_items", m20231225_failed_work_items, Just down_m20231225_failed_work_items)
("m20231225_failed_work_items", m20231225_failed_work_items, Just down_m20231225_failed_work_items),
("m20240121_message_delivery_indexes", m20240121_message_delivery_indexes, Just down_m20240121_message_delivery_indexes)
]
-- | The list of migrations in ascending order by date
@@ -0,0 +1,20 @@
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Messaging.Agent.Store.SQLite.Migrations.M20240121_message_delivery_indexes where
import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql)
m20240121_message_delivery_indexes :: Query
m20240121_message_delivery_indexes =
[sql|
CREATE INDEX idx_messages_snd_expired ON messages(conn_id, internal_snd_id, internal_ts);
CREATE INDEX idx_snd_message_deliveries_expired ON snd_message_deliveries(conn_id, snd_queue_id, failed, internal_id);
|]
down_m20240121_message_delivery_indexes :: Query
down_m20240121_message_delivery_indexes =
[sql|
DROP INDEX idx_messages_snd_expired;
DROP INDEX idx_snd_message_deliveries_expired;
|]
@@ -497,3 +497,14 @@ CREATE INDEX idx_commands_server_commands ON commands(
CREATE INDEX idx_rcv_files_status_created_at ON rcv_files(status, created_at);
CREATE INDEX idx_snd_files_status_created_at ON snd_files(status, created_at);
CREATE INDEX idx_snd_files_snd_file_entity_id ON snd_files(snd_file_entity_id);
CREATE INDEX idx_messages_snd_expired ON messages(
conn_id,
internal_snd_id,
internal_ts
);
CREATE INDEX idx_snd_message_deliveries_expired ON snd_message_deliveries(
conn_id,
snd_queue_id,
failed,
internal_id
);
-24
View File
@@ -1,24 +0,0 @@
module Simplex.Messaging.Agent.TAsyncs where
import Control.Monad.IO.Unlift (MonadUnliftIO)
import Simplex.Messaging.TMap (TMap)
import qualified Simplex.Messaging.TMap as TM
import UnliftIO.Async (Async, async)
import UnliftIO.STM
data TAsyncs = TAsyncs
{ actionId :: TVar Int,
actions :: TMap Int (Async ())
}
newTAsyncs :: STM TAsyncs
newTAsyncs = TAsyncs <$> newTVar 0 <*> TM.empty
newAsyncAction :: MonadUnliftIO m => (Int -> m ()) -> TAsyncs -> m ()
newAsyncAction action as = do
aId <- atomically $ stateTVar (actionId as) $ \i -> (i + 1, i + 1)
a <- async $ action aId
atomically $ TM.insert aId a $ actions as
removeAsyncAction :: Int -> TAsyncs -> STM ()
removeAsyncAction aId = TM.delete aId . actions
+5 -5
View File
@@ -346,12 +346,12 @@ getProtocolClient transportSession@(_, srv, _) cfg@ProtocolClientConfig {qSize,
action <-
async $
runTransportClient tcConfig (Just username) useHost port' (Just $ keyHash srv) (client t c cVar)
`finally` atomically (putTMVar cVar $ Left PCENetworkError)
`finally` atomically (tryPutTMVar cVar $ Left PCENetworkError)
c_ <- tcpConnectTimeout `timeout` atomically (takeTMVar cVar)
pure $ case c_ of
Just (Right c') -> Right c' {action = Just action}
Just (Left e) -> Left e
Nothing -> Left PCENetworkError
case c_ of
Just (Right c') -> pure $ Right c' {action = Just action}
Just (Left e) -> pure $ Left e
Nothing -> cancel action $> Left PCENetworkError
useTransport :: (ServiceName, ATransport)
useTransport = case port srv of
@@ -30,7 +30,7 @@ import System.IO (BufferMode (..), hSetBuffering, stderr, stdout)
import Text.Read (readMaybe)
ntfServerVersion :: String
ntfServerVersion = "1.7.1.0"
ntfServerVersion = "1.7.2.0"
defaultSMPBatchDelay :: Int
defaultSMPBatchDelay = 10000
+4 -2
View File
@@ -65,6 +65,7 @@ import Control.Monad.Except
import Control.Monad.Trans.Except (throwE)
import qualified Data.Aeson.TH as J
import Data.Attoparsec.ByteString.Char8 (Parser)
import qualified Data.Attoparsec.ByteString.Char8 as A
import Data.Bifunctor (first)
import Data.Bitraversable (bimapM)
import Data.ByteString.Char8 (ByteString)
@@ -80,7 +81,7 @@ import qualified Network.TLS.Extra as TE
import qualified Paths_simplexmq as SMQ
import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Encoding
import Simplex.Messaging.Parsers (dropPrefix, parse, parseRead1, sumTypeJSON)
import Simplex.Messaging.Parsers (dropPrefix, parseRead1, sumTypeJSON)
import Simplex.Messaging.Transport.Buffer
import Simplex.Messaging.Util (bshow, catchAll, catchAll_)
import Simplex.Messaging.Version
@@ -358,8 +359,9 @@ smpThHandle th v = (th :: THandle c) {thVersion = v, batch = v >= 4}
sendHandshake :: (Transport c, Encoding smp) => THandle c -> smp -> ExceptT TransportError IO ()
sendHandshake th = ExceptT . tPutBlock th . smpEncode
-- ignores tail bytes to allow future extensions
getHandshake :: (Transport c, Encoding smp) => THandle c -> ExceptT TransportError IO smp
getHandshake th = ExceptT $ (parse smpP (TEHandshake PARSE) =<<) <$> tGetBlock th
getHandshake th = ExceptT $ (first (\_ -> TEHandshake PARSE) . A.parseOnly smpP =<<) <$> tGetBlock th
smpTHandle :: Transport c => c -> THandle c
smpTHandle c = THandle {connection = c, sessionId = tlsUnique c, blockSize = smpBlockSize, thVersion = 0, batch = False}
+3 -2
View File
@@ -21,6 +21,7 @@ module Simplex.Messaging.Transport.Client
where
import Control.Applicative (optional)
import Control.Logger.Simple (logError)
import Control.Monad.IO.Unlift
import Data.Aeson (FromJSON (..), ToJSON (..))
import qualified Data.Attoparsec.ByteString.Char8 as A
@@ -48,7 +49,7 @@ import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (parseAll, parseString)
import Simplex.Messaging.Transport
import Simplex.Messaging.Transport.KeepAlive
import Simplex.Messaging.Util (bshow, (<$?>))
import Simplex.Messaging.Util (bshow, (<$?>), catchAll, tshow)
import System.IO.Error
import Text.Read (readMaybe)
import UnliftIO.Exception (IOException)
@@ -135,7 +136,7 @@ runTLSTransportClient tlsParams caStore_ cfg@TransportClientConfig {socksProxy,
_ -> connectTCPClient hostName
c <- liftIO $ do
sock <- connectTCP port
mapM_ (setSocketKeepAlive sock) tcpKeepAlive
mapM_ (setSocketKeepAlive sock) tcpKeepAlive `catchAll` \e -> logError ("Error setting TCP keep-alive" <> tshow e)
let tCfg = clientTransportConfig cfg
connectTLS (Just hostName) tCfg clientParams sock >>= getClientConnection tCfg
client c `E.finally` liftIO (closeConnection c)
+5 -6
View File
@@ -359,7 +359,6 @@ testServerConnectionAfterError t _ = do
withAgent2 $ \alice -> do
withServer $ do
connect (bob, "bob") (alice, "alice")
bob <#. ("", "", DOWN server ["alice"])
alice <#. ("", "", DOWN server ["bob"])
alice #: ("1", "bob", "SEND F 5\nhello") #> ("1", "bob", MID 4)
@@ -386,10 +385,10 @@ testServerConnectionAfterError t _ = do
where
server = SMPServer "localhost" testPort2 testKeyHash
withServer test' = withSmpServerStoreLogOn (ATransport t) testPort2 (const test') `shouldReturn` ()
withAgent1 = withAgent agentTestPort testDB
withAgent2 = withAgent agentTestPort2 testDB2
withAgent :: String -> FilePath -> (c -> IO a) -> IO a
withAgent agentPort agentDB = withSmpAgentThreadOn_ (ATransport t) (agentPort, testPort2, agentDB) (pure ()) . const . testSMPAgentClientOn agentPort
withAgent1 = withAgent agentTestPort testDB 0
withAgent2 = withAgent agentTestPort2 testDB2 10
withAgent :: String -> FilePath -> Int -> (c -> IO a) -> IO a
withAgent agentPort agentDB initClientId = withSmpAgentThreadOn_ (ATransport t) (agentPort, testPort2, agentDB) initClientId (pure ()) . const . testSMPAgentClientOn agentPort
testMsgDeliveryAgentRestart :: Transport c => TProxy c -> c -> IO ()
testMsgDeliveryAgentRestart t bob = do
@@ -424,7 +423,7 @@ testMsgDeliveryAgentRestart t bob = do
removeFile testDB
where
withServer test' = withSmpServerStoreLogOn (ATransport t) testPort2 (const test') `shouldReturn` ()
withAgent = withSmpAgentThreadOn_ (ATransport t) (agentTestPort, testPort, testDB) (pure ()) . const . testSMPAgentClientOn agentTestPort
withAgent = withSmpAgentThreadOn_ (ATransport t) (agentTestPort, testPort, testDB) 0 (pure ()) . const . testSMPAgentClientOn agentTestPort
testConcurrentMsgDelivery :: Transport c => TProxy c -> c -> c -> IO ()
testConcurrentMsgDelivery _ alice bob = do
+4 -4
View File
@@ -225,15 +225,15 @@ fastMessageRetryInterval = RetryInterval2 {riFast = fastRetryInterval, riSlow =
type AgentTestMonad m = (MonadUnliftIO m, MonadRandom m, MonadFail m)
withSmpAgentThreadOn_ :: AgentTestMonad m => ATransport -> (ServiceName, ServiceName, FilePath) -> m () -> (ThreadId -> m a) -> m a
withSmpAgentThreadOn_ t (port', smpPort', db') afterProcess =
withSmpAgentThreadOn_ :: AgentTestMonad m => ATransport -> (ServiceName, ServiceName, FilePath) -> Int -> m () -> (ThreadId -> m a) -> m a
withSmpAgentThreadOn_ t (port', smpPort', db') initClientId afterProcess =
let cfg' = agentCfg {tcpPort = port'}
initServers' = initAgentServers {smp = userServers [ProtoServerWithAuth (SMPServer "localhost" smpPort' testKeyHash) Nothing]}
in serverBracket
( \started -> do
Right st <- liftIO $ createAgentStore db' "" False MCError
when (dbNew st) . liftIO $ withTransaction' st (`SQL.execute_` "INSERT INTO users (user_id) VALUES (1)")
runSMPAgentBlocking t cfg' initServers' st started
runSMPAgentBlocking t cfg' initServers' st initClientId started
)
afterProcess
@@ -241,7 +241,7 @@ userServers :: NonEmpty (ProtoServerWithAuth p) -> Map UserId (NonEmpty (ProtoSe
userServers srvs = M.fromList [(1, srvs)]
withSmpAgentThreadOn :: AgentTestMonad m => ATransport -> (ServiceName, ServiceName, FilePath) -> (ThreadId -> m a) -> m a
withSmpAgentThreadOn t a@(_, _, db') = withSmpAgentThreadOn_ t a $ removeFile db'
withSmpAgentThreadOn t a@(_, _, db') = withSmpAgentThreadOn_ t a 0 $ removeFile db'
withSmpAgentOn :: AgentTestMonad m => ATransport -> (ServiceName, ServiceName, FilePath) -> m a -> m a
withSmpAgentOn t (port', smpPort', db') = withSmpAgentThreadOn t (port', smpPort', db') . const
+1 -1
View File
@@ -130,7 +130,7 @@ serverBracket process afterProcess f = do
E.bracket
(forkIOWithUnmask ($ process started))
(\t -> killThread t >> afterProcess >> waitFor started "stop")
(\t -> waitFor started "start" >> f t)
(\t -> waitFor started "start" >> f t >>= \r -> r <$ threadDelay 100000)
where
waitFor started s =
5_000_000 `timeout` atomically (takeTMVar started) >>= \case