2024-07-09 01:11:01 +00:00
|
|
|
{- P2P protocol over HTTP, server state
|
|
|
|
-
|
|
|
|
- https://git-annex.branchable.com/design/p2p_protocol_over_http/
|
|
|
|
-
|
|
|
|
- Copyright 2024 Joey Hess <id@joeyh.name>
|
|
|
|
-
|
|
|
|
- Licensed under the GNU AGPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
|
|
|
{-# LANGUAGE BangPatterns #-}
|
2024-07-10 13:13:01 +00:00
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
2024-07-10 20:06:39 +00:00
|
|
|
{-# LANGUAGE FlexibleContexts #-}
|
implement proxy connection pool
removeOldestProxyConnectionPool will be innefficient the larger the pool
is. A better data structure could be more efficient. Eg, make each value
in the pool include the timestamp of its oldest element, then the oldest
value can be found and modified, rather than rebuilding the whole Map.
But, for pools of a few hundred items, this should be fine. It's O(n*n log n)
or so.
Also, when more than 1 connection with the same pool key exists,
it's efficient even for larger pools, since removeOldestProxyConnectionPool
is not needed.
The default of 1 idle connection could perhaps be larger.. like the
number of jobs? Otoh, it seems good to ramp up and down the number of
connections, which does happen. With 1, there is at most one stale
connection, which might cause a request to fail.
2024-07-26 20:34:08 +00:00
|
|
|
{-# LANGUAGE TupleSections #-}
|
2024-07-09 01:11:01 +00:00
|
|
|
|
|
|
|
module P2P.Http.State where
|
|
|
|
|
|
|
|
import Annex.Common
|
2024-07-10 16:19:47 +00:00
|
|
|
import qualified Annex
|
2024-07-09 01:11:01 +00:00
|
|
|
import P2P.Http.Types
|
2024-07-09 13:08:42 +00:00
|
|
|
import qualified P2P.Protocol as P2P
|
2024-07-11 13:55:17 +00:00
|
|
|
import qualified P2P.IO as P2P
|
2024-07-09 17:37:55 +00:00
|
|
|
import P2P.IO
|
|
|
|
import P2P.Annex
|
|
|
|
import Annex.UUID
|
2024-07-24 18:25:40 +00:00
|
|
|
import Types.NumCopies
|
2024-07-10 16:19:47 +00:00
|
|
|
import Types.WorkerPool
|
|
|
|
import Annex.WorkerPool
|
2024-07-28 16:39:42 +00:00
|
|
|
import Annex.BranchState
|
2024-07-26 14:24:23 +00:00
|
|
|
import Types.Cluster
|
2024-07-10 16:19:47 +00:00
|
|
|
import CmdLine.Action (startConcurrency)
|
2024-07-24 18:25:40 +00:00
|
|
|
import Utility.ThreadScheduler
|
|
|
|
import Utility.HumanTime
|
2024-07-28 20:04:20 +00:00
|
|
|
import Logs.Proxy
|
2024-07-25 17:15:05 +00:00
|
|
|
import Annex.Proxy
|
2024-07-28 14:16:35 +00:00
|
|
|
import Annex.Cluster
|
2024-07-25 19:39:57 +00:00
|
|
|
import qualified P2P.Proxy as Proxy
|
2024-07-26 14:24:23 +00:00
|
|
|
import qualified Types.Remote as Remote
|
2024-07-09 01:11:01 +00:00
|
|
|
|
2024-07-09 17:37:55 +00:00
|
|
|
import Servant
|
implement proxy connection pool
removeOldestProxyConnectionPool will be innefficient the larger the pool
is. A better data structure could be more efficient. Eg, make each value
in the pool include the timestamp of its oldest element, then the oldest
value can be found and modified, rather than rebuilding the whole Map.
But, for pools of a few hundred items, this should be fine. It's O(n*n log n)
or so.
Also, when more than 1 connection with the same pool key exists,
it's efficient even for larger pools, since removeOldestProxyConnectionPool
is not needed.
The default of 1 idle connection could perhaps be larger.. like the
number of jobs? Otoh, it seems good to ramp up and down the number of
connections, which does happen. With 1, there is at most one stale
connection, which might cause a request to fail.
2024-07-26 20:34:08 +00:00
|
|
|
import qualified Data.Map.Strict as M
|
2024-07-26 14:24:23 +00:00
|
|
|
import qualified Data.Set as S
|
2024-07-09 01:11:01 +00:00
|
|
|
import Control.Concurrent.Async
|
|
|
|
import Control.Concurrent.STM
|
implement proxy connection pool
removeOldestProxyConnectionPool will be innefficient the larger the pool
is. A better data structure could be more efficient. Eg, make each value
in the pool include the timestamp of its oldest element, then the oldest
value can be found and modified, rather than rebuilding the whole Map.
But, for pools of a few hundred items, this should be fine. It's O(n*n log n)
or so.
Also, when more than 1 connection with the same pool key exists,
it's efficient even for larger pools, since removeOldestProxyConnectionPool
is not needed.
The default of 1 idle connection could perhaps be larger.. like the
number of jobs? Otoh, it seems good to ramp up and down the number of
connections, which does happen. With 1, there is at most one stale
connection, which might cause a request to fail.
2024-07-26 20:34:08 +00:00
|
|
|
import Data.Time.Clock.POSIX
|
2024-07-09 01:11:01 +00:00
|
|
|
|
|
|
|
data P2PHttpServerState = P2PHttpServerState
|
2024-07-09 17:37:55 +00:00
|
|
|
{ acquireP2PConnection :: AcquireP2PConnection
|
2024-07-10 16:19:47 +00:00
|
|
|
, annexWorkerPool :: AnnexWorkerPool
|
2024-07-09 21:30:55 +00:00
|
|
|
, getServerMode :: GetServerMode
|
2024-07-09 17:37:55 +00:00
|
|
|
, openLocks :: TMVar (M.Map LockID Locker)
|
2024-07-09 01:11:01 +00:00
|
|
|
}
|
|
|
|
|
2024-07-10 16:19:47 +00:00
|
|
|
type AnnexWorkerPool = TMVar (WorkerPool (Annex.AnnexState, Annex.AnnexRead))
|
|
|
|
|
|
|
|
-- Nothing when the server is not allowed to serve any requests.
|
|
|
|
type GetServerMode = IsSecure -> Maybe Auth -> Maybe P2P.ServerMode
|
|
|
|
|
|
|
|
mkP2PHttpServerState :: AcquireP2PConnection -> AnnexWorkerPool -> GetServerMode -> IO P2PHttpServerState
|
|
|
|
mkP2PHttpServerState acquireconn annexworkerpool getservermode = P2PHttpServerState
|
2024-07-09 17:37:55 +00:00
|
|
|
<$> pure acquireconn
|
2024-07-10 16:19:47 +00:00
|
|
|
<*> pure annexworkerpool
|
2024-07-09 21:30:55 +00:00
|
|
|
<*> pure getservermode
|
2024-07-09 17:37:55 +00:00
|
|
|
<*> newTMVarIO mempty
|
2024-07-09 01:11:01 +00:00
|
|
|
|
2024-07-10 00:52:56 +00:00
|
|
|
data ActionClass = ReadAction | WriteAction | RemoveAction
|
|
|
|
deriving (Eq)
|
|
|
|
|
2024-07-09 17:37:55 +00:00
|
|
|
withP2PConnection
|
|
|
|
:: APIVersion v
|
|
|
|
=> v
|
|
|
|
-> P2PHttpServerState
|
2024-07-09 13:08:42 +00:00
|
|
|
-> B64UUID ClientSide
|
|
|
|
-> B64UUID ServerSide
|
|
|
|
-> [B64UUID Bypass]
|
2024-07-10 03:44:40 +00:00
|
|
|
-> IsSecure
|
2024-07-10 00:52:56 +00:00
|
|
|
-> Maybe Auth
|
|
|
|
-> ActionClass
|
2024-07-22 15:26:22 +00:00
|
|
|
-> (ConnectionParams -> ConnectionParams)
|
2024-07-11 13:55:17 +00:00
|
|
|
-> (P2PConnectionPair -> Handler (Either ProtoFailure a))
|
2024-07-09 17:37:55 +00:00
|
|
|
-> Handler a
|
2024-07-22 15:26:22 +00:00
|
|
|
withP2PConnection apiver st cu su bypass sec auth actionclass fconnparams connaction =
|
|
|
|
withP2PConnection' apiver st cu su bypass sec auth actionclass fconnparams connaction'
|
2024-07-10 20:06:39 +00:00
|
|
|
where
|
2024-07-11 13:55:17 +00:00
|
|
|
connaction' conn = connaction conn >>= \case
|
2024-07-10 20:06:39 +00:00
|
|
|
Right r -> return r
|
|
|
|
Left err -> throwError $
|
|
|
|
err500 { errBody = encodeBL (describeProtoFailure err) }
|
|
|
|
|
2024-07-22 15:26:22 +00:00
|
|
|
withP2PConnection'
|
|
|
|
:: APIVersion v
|
|
|
|
=> v
|
|
|
|
-> P2PHttpServerState
|
|
|
|
-> B64UUID ClientSide
|
|
|
|
-> B64UUID ServerSide
|
|
|
|
-> [B64UUID Bypass]
|
|
|
|
-> IsSecure
|
|
|
|
-> Maybe Auth
|
|
|
|
-> ActionClass
|
|
|
|
-> (ConnectionParams -> ConnectionParams)
|
|
|
|
-> (P2PConnectionPair -> Handler a)
|
|
|
|
-> Handler a
|
|
|
|
withP2PConnection' apiver st cu su bypass sec auth actionclass fconnparams connaction = do
|
|
|
|
conn <- getP2PConnection apiver st cu su bypass sec auth actionclass fconnparams
|
|
|
|
connaction conn
|
|
|
|
`finally` liftIO (releaseP2PConnection conn)
|
|
|
|
|
2024-07-10 20:06:39 +00:00
|
|
|
getP2PConnection
|
|
|
|
:: APIVersion v
|
|
|
|
=> v
|
|
|
|
-> P2PHttpServerState
|
|
|
|
-> B64UUID ClientSide
|
|
|
|
-> B64UUID ServerSide
|
|
|
|
-> [B64UUID Bypass]
|
|
|
|
-> IsSecure
|
|
|
|
-> Maybe Auth
|
|
|
|
-> ActionClass
|
2024-07-22 14:20:18 +00:00
|
|
|
-> (ConnectionParams -> ConnectionParams)
|
2024-07-11 13:55:17 +00:00
|
|
|
-> Handler P2PConnectionPair
|
2024-07-22 14:20:18 +00:00
|
|
|
getP2PConnection apiver st cu su bypass sec auth actionclass fconnparams =
|
2024-07-22 23:15:52 +00:00
|
|
|
checkAuthActionClass st sec auth actionclass go
|
2024-07-10 00:52:56 +00:00
|
|
|
where
|
|
|
|
go servermode = liftIO (acquireP2PConnection st cp) >>= \case
|
2024-07-09 17:37:55 +00:00
|
|
|
Left (ConnectionFailed err) ->
|
|
|
|
throwError err502 { errBody = encodeBL err }
|
|
|
|
Left TooManyConnections ->
|
|
|
|
throwError err503
|
2024-07-10 20:06:39 +00:00
|
|
|
Right v -> return v
|
2024-07-10 00:52:56 +00:00
|
|
|
where
|
2024-07-22 14:20:18 +00:00
|
|
|
cp = fconnparams $ ConnectionParams
|
2024-07-10 00:52:56 +00:00
|
|
|
{ connectionProtocolVersion = protocolVersion apiver
|
|
|
|
, connectionServerUUID = fromB64UUID su
|
|
|
|
, connectionClientUUID = fromB64UUID cu
|
|
|
|
, connectionBypass = map fromB64UUID bypass
|
|
|
|
, connectionServerMode = servermode
|
2024-07-22 14:20:18 +00:00
|
|
|
, connectionWaitVar = True
|
2024-07-10 00:52:56 +00:00
|
|
|
}
|
2024-07-10 13:13:01 +00:00
|
|
|
|
2024-07-22 23:15:52 +00:00
|
|
|
checkAuthActionClass
|
|
|
|
:: P2PHttpServerState
|
|
|
|
-> IsSecure
|
|
|
|
-> Maybe Auth
|
|
|
|
-> ActionClass
|
|
|
|
-> (P2P.ServerMode -> Handler a)
|
|
|
|
-> Handler a
|
|
|
|
checkAuthActionClass st sec auth actionclass go =
|
|
|
|
case (getServerMode st sec auth, actionclass) of
|
|
|
|
(Just P2P.ServeReadWrite, _) -> go P2P.ServeReadWrite
|
|
|
|
(Just P2P.ServeAppendOnly, RemoveAction) -> throwError err403
|
|
|
|
(Just P2P.ServeAppendOnly, _) -> go P2P.ServeAppendOnly
|
|
|
|
(Just P2P.ServeReadOnly, ReadAction) -> go P2P.ServeReadOnly
|
|
|
|
(Just P2P.ServeReadOnly, _) -> throwError err403
|
|
|
|
(Nothing, _) -> throwError basicAuthRequired
|
|
|
|
|
2024-07-10 13:13:01 +00:00
|
|
|
basicAuthRequired :: ServerError
|
|
|
|
basicAuthRequired = err401 { errHeaders = [(h, v)] }
|
|
|
|
where
|
|
|
|
h = "WWW-Authenticate"
|
|
|
|
v = "Basic realm=\"git-annex\", charset=\"UTF-8\""
|
2024-07-09 17:37:55 +00:00
|
|
|
|
|
|
|
data ConnectionParams = ConnectionParams
|
|
|
|
{ connectionProtocolVersion :: P2P.ProtocolVersion
|
|
|
|
, connectionServerUUID :: UUID
|
|
|
|
, connectionClientUUID :: UUID
|
|
|
|
, connectionBypass :: [UUID]
|
|
|
|
, connectionServerMode :: P2P.ServerMode
|
2024-07-22 14:20:18 +00:00
|
|
|
, connectionWaitVar :: Bool
|
2024-07-09 17:37:55 +00:00
|
|
|
}
|
|
|
|
deriving (Show, Eq, Ord)
|
|
|
|
|
|
|
|
data ConnectionProblem
|
|
|
|
= ConnectionFailed String
|
|
|
|
| TooManyConnections
|
|
|
|
deriving (Show, Eq)
|
|
|
|
|
2024-07-11 13:55:17 +00:00
|
|
|
proxyClientNetProto :: P2PConnectionPair -> P2P.Proto a -> IO (Either P2P.ProtoFailure a)
|
|
|
|
proxyClientNetProto conn = runNetProto
|
|
|
|
(clientRunState conn) (clientP2PConnection conn)
|
2024-07-09 17:37:55 +00:00
|
|
|
|
2024-07-11 13:55:17 +00:00
|
|
|
type AcquireP2PConnection
|
|
|
|
= ConnectionParams
|
|
|
|
-> IO (Either ConnectionProblem P2PConnectionPair)
|
2024-07-10 20:06:39 +00:00
|
|
|
|
implement proxy connection pool
removeOldestProxyConnectionPool will be innefficient the larger the pool
is. A better data structure could be more efficient. Eg, make each value
in the pool include the timestamp of its oldest element, then the oldest
value can be found and modified, rather than rebuilding the whole Map.
But, for pools of a few hundred items, this should be fine. It's O(n*n log n)
or so.
Also, when more than 1 connection with the same pool key exists,
it's efficient even for larger pools, since removeOldestProxyConnectionPool
is not needed.
The default of 1 idle connection could perhaps be larger.. like the
number of jobs? Otoh, it seems good to ramp up and down the number of
connections, which does happen. With 1, there is at most one stale
connection, which might cause a request to fail.
2024-07-26 20:34:08 +00:00
|
|
|
withP2PConnections
|
|
|
|
:: AnnexWorkerPool
|
|
|
|
-> ProxyConnectionPoolSize
|
2024-07-28 14:36:22 +00:00
|
|
|
-> ClusterConcurrency
|
implement proxy connection pool
removeOldestProxyConnectionPool will be innefficient the larger the pool
is. A better data structure could be more efficient. Eg, make each value
in the pool include the timestamp of its oldest element, then the oldest
value can be found and modified, rather than rebuilding the whole Map.
But, for pools of a few hundred items, this should be fine. It's O(n*n log n)
or so.
Also, when more than 1 connection with the same pool key exists,
it's efficient even for larger pools, since removeOldestProxyConnectionPool
is not needed.
The default of 1 idle connection could perhaps be larger.. like the
number of jobs? Otoh, it seems good to ramp up and down the number of
connections, which does happen. With 1, there is at most one stale
connection, which might cause a request to fail.
2024-07-26 20:34:08 +00:00
|
|
|
-> (AcquireP2PConnection -> Annex a)
|
|
|
|
-> Annex a
|
2024-07-28 14:36:22 +00:00
|
|
|
withP2PConnections workerpool proxyconnectionpoolsize clusterconcurrency a = do
|
2024-07-28 16:39:42 +00:00
|
|
|
enableInteractiveBranchAccess
|
2024-07-11 18:37:52 +00:00
|
|
|
myuuid <- getUUID
|
2024-07-28 20:04:20 +00:00
|
|
|
myproxies <- M.lookup myuuid <$> getProxies
|
2024-07-09 17:37:55 +00:00
|
|
|
reqv <- liftIO newEmptyTMVarIO
|
|
|
|
relv <- liftIO newEmptyTMVarIO
|
2024-07-23 00:37:37 +00:00
|
|
|
endv <- liftIO newEmptyTMVarIO
|
implement proxy connection pool
removeOldestProxyConnectionPool will be innefficient the larger the pool
is. A better data structure could be more efficient. Eg, make each value
in the pool include the timestamp of its oldest element, then the oldest
value can be found and modified, rather than rebuilding the whole Map.
But, for pools of a few hundred items, this should be fine. It's O(n*n log n)
or so.
Also, when more than 1 connection with the same pool key exists,
it's efficient even for larger pools, since removeOldestProxyConnectionPool
is not needed.
The default of 1 idle connection could perhaps be larger.. like the
number of jobs? Otoh, it seems good to ramp up and down the number of
connections, which does happen. With 1, there is at most one stale
connection, which might cause a request to fail.
2024-07-26 20:34:08 +00:00
|
|
|
proxypool <- liftIO $ newTMVarIO (0, mempty)
|
2024-07-25 17:15:05 +00:00
|
|
|
asyncservicer <- liftIO $ async $
|
2024-07-28 20:04:20 +00:00
|
|
|
servicer myuuid myproxies proxypool reqv relv endv
|
2024-07-23 00:37:37 +00:00
|
|
|
let endit = do
|
|
|
|
liftIO $ atomically $ putTMVar endv ()
|
|
|
|
liftIO $ wait asyncservicer
|
|
|
|
a (acquireconn reqv) `finally` endit
|
2024-07-09 17:37:55 +00:00
|
|
|
where
|
|
|
|
acquireconn reqv connparams = do
|
|
|
|
respvar <- newEmptyTMVarIO
|
2024-07-10 20:06:39 +00:00
|
|
|
atomically $ putTMVar reqv (connparams, respvar)
|
|
|
|
atomically $ takeTMVar respvar
|
2024-07-09 17:37:55 +00:00
|
|
|
|
2024-07-28 20:04:20 +00:00
|
|
|
servicer myuuid myproxies proxypool reqv relv endv = do
|
2024-07-09 17:37:55 +00:00
|
|
|
reqrel <- liftIO $
|
|
|
|
atomically $
|
|
|
|
(Right <$> takeTMVar reqv)
|
|
|
|
`orElse`
|
2024-07-23 00:37:37 +00:00
|
|
|
(Left . Right <$> takeTMVar relv)
|
|
|
|
`orElse`
|
|
|
|
(Left . Left <$> takeTMVar endv)
|
2024-07-09 17:37:55 +00:00
|
|
|
case reqrel of
|
2024-07-23 00:37:37 +00:00
|
|
|
Right (connparams, respvar) -> do
|
2024-07-28 20:04:20 +00:00
|
|
|
servicereq myuuid myproxies proxypool relv connparams
|
2024-07-11 18:37:52 +00:00
|
|
|
>>= atomically . putTMVar respvar
|
2024-07-28 20:04:20 +00:00
|
|
|
servicer myuuid myproxies proxypool reqv relv endv
|
2024-07-23 00:37:37 +00:00
|
|
|
Left (Right releaseconn) -> do
|
cleanly close proxy connection on interrupted PUT
An interrupted PUT to cluster that has a node that is a special remote
over http left open the connection to the cluster, so the next request
opens another one. So did an interrupted PUT directly to the proxied
special remote over http.
proxySpecialRemote was stuck waiting for all the DATA. Its connection
remained open so it kept waiting.
In servePut, checktooshort handles closing the P2P connection
when too short a data is received from PUT. But, checktooshort was only
called after the protoaction, which is what runs the proxy, which is
what was getting stuck. Modified it to run as a background thread,
which waits for the tooshortv to be written to, which gather always does
once it gets to the end of the data received from the http client.
That makes proxyConnection's releaseconn run once all data is received
from the http client. Made it close the connection handles before
waiting on the asyncworker thread. This lets proxySpecialRemote finish
processing any data from the handle, and then it will give up,
more or less cleanly, if it didn't receive enough data.
I say "more or less cleanly" because with both sides of the P2P
connection taken down, some protocol unhappyness results. Which can lead
to some ugly debug messages. But also can cause the asyncworker thread
to throw an exception. So made withP2PConnections not crash when it
receives an exception from releaseconn.
This did have a small change to the behavior of an interrupted PUT when
proxying to a regular remote. proxyConnection has a protoerrorhandler
that closes the proxy connection on a protocol error. But the proxy
connection is also closed by checktooshort when it closes the P2P
connection. Closing the same proxy connection twice is not a problem,
it just results in duplicated debug messages about it.
2024-07-29 14:33:26 +00:00
|
|
|
void $ tryNonAsync releaseconn
|
2024-07-28 20:04:20 +00:00
|
|
|
servicer myuuid myproxies proxypool reqv relv endv
|
2024-07-23 00:37:37 +00:00
|
|
|
Left (Left ()) -> return ()
|
2024-07-09 17:37:55 +00:00
|
|
|
|
2024-07-28 20:04:20 +00:00
|
|
|
servicereq myuuid myproxies proxypool relv connparams
|
2024-07-25 17:15:05 +00:00
|
|
|
| connectionServerUUID connparams == myuuid =
|
|
|
|
localConnection relv connparams workerpool
|
|
|
|
| otherwise =
|
implement proxy connection pool
removeOldestProxyConnectionPool will be innefficient the larger the pool
is. A better data structure could be more efficient. Eg, make each value
in the pool include the timestamp of its oldest element, then the oldest
value can be found and modified, rather than rebuilding the whole Map.
But, for pools of a few hundred items, this should be fine. It's O(n*n log n)
or so.
Also, when more than 1 connection with the same pool key exists,
it's efficient even for larger pools, since removeOldestProxyConnectionPool
is not needed.
The default of 1 idle connection could perhaps be larger.. like the
number of jobs? Otoh, it seems good to ramp up and down the number of
connections, which does happen. With 1, there is at most one stale
connection, which might cause a request to fail.
2024-07-26 20:34:08 +00:00
|
|
|
atomically (getProxyConnectionPool proxypool connparams) >>= \case
|
|
|
|
Just conn -> proxyConnection proxyconnectionpoolsize relv connparams workerpool proxypool conn
|
2024-07-28 20:04:20 +00:00
|
|
|
Nothing -> checkcanproxy myproxies proxypool relv connparams
|
2024-07-25 17:15:05 +00:00
|
|
|
|
2024-07-28 20:04:20 +00:00
|
|
|
checkcanproxy myproxies proxypool relv connparams =
|
2024-07-25 17:15:05 +00:00
|
|
|
inAnnexWorker' workerpool
|
2024-07-28 20:04:20 +00:00
|
|
|
(checkCanProxy' myproxies (connectionServerUUID connparams))
|
2024-07-25 17:15:05 +00:00
|
|
|
>>= \case
|
|
|
|
Right (Left reason) -> return $ Left $
|
|
|
|
ConnectionFailed $
|
|
|
|
fromMaybe "unknown uuid" reason
|
2024-07-28 14:16:35 +00:00
|
|
|
Right (Right (Right proxyremote)) -> proxyconnection $
|
2024-07-26 14:24:23 +00:00
|
|
|
openProxyConnectionToRemote workerpool
|
|
|
|
(connectionProtocolVersion connparams)
|
2024-07-28 14:16:35 +00:00
|
|
|
bypass proxyremote
|
|
|
|
Right (Right (Left clusteruuid)) -> proxyconnection $
|
|
|
|
openProxyConnectionToCluster workerpool
|
|
|
|
(connectionProtocolVersion connparams)
|
2024-07-28 14:36:22 +00:00
|
|
|
bypass clusteruuid clusterconcurrency
|
2024-07-28 14:16:35 +00:00
|
|
|
Left ex -> return $ Left $
|
|
|
|
ConnectionFailed $ show ex
|
|
|
|
where
|
|
|
|
bypass = P2P.Bypass $ S.fromList $ connectionBypass connparams
|
|
|
|
proxyconnection openconn = openconn >>= \case
|
|
|
|
Right conn -> proxyConnection proxyconnectionpoolsize
|
|
|
|
relv connparams workerpool proxypool conn
|
2024-07-25 17:15:05 +00:00
|
|
|
Left ex -> return $ Left $
|
|
|
|
ConnectionFailed $ show ex
|
2024-07-25 19:39:57 +00:00
|
|
|
|
2024-07-26 14:24:23 +00:00
|
|
|
data P2PConnectionPair = P2PConnectionPair
|
|
|
|
{ clientRunState :: RunState
|
|
|
|
, clientP2PConnection :: P2PConnection
|
2024-07-26 16:49:06 +00:00
|
|
|
, serverP2PConnection :: P2PConnection
|
2024-07-26 14:24:23 +00:00
|
|
|
, releaseP2PConnection :: IO ()
|
|
|
|
-- ^ Releases a P2P connection, which can be reused for other
|
|
|
|
-- requests.
|
|
|
|
, closeP2PConnection :: IO ()
|
|
|
|
-- ^ Closes a P2P connection, which is in a state where it is no
|
|
|
|
-- longer usable.
|
|
|
|
}
|
2024-07-25 17:15:05 +00:00
|
|
|
|
2024-07-25 19:39:57 +00:00
|
|
|
localConnection
|
2024-07-25 17:15:05 +00:00
|
|
|
:: TMVar (IO ())
|
|
|
|
-> ConnectionParams
|
2024-07-25 19:39:57 +00:00
|
|
|
-> AnnexWorkerPool
|
2024-07-25 17:15:05 +00:00
|
|
|
-> IO (Either ConnectionProblem P2PConnectionPair)
|
2024-07-25 19:39:57 +00:00
|
|
|
localConnection relv connparams workerpool =
|
2024-07-26 14:24:23 +00:00
|
|
|
localP2PConnectionPair connparams relv $ \serverrunst serverconn ->
|
2024-07-25 19:39:57 +00:00
|
|
|
inAnnexWorker' workerpool $
|
|
|
|
void $ runFullProto serverrunst serverconn $
|
|
|
|
P2P.serveOneCommandAuthed
|
|
|
|
(connectionServerMode connparams)
|
|
|
|
(connectionServerUUID connparams)
|
2024-07-25 17:15:05 +00:00
|
|
|
|
2024-07-26 14:24:23 +00:00
|
|
|
localP2PConnectionPair
|
2024-07-11 18:37:52 +00:00
|
|
|
:: ConnectionParams
|
|
|
|
-> TMVar (IO ())
|
|
|
|
-> (RunState -> P2PConnection -> IO (Either SomeException ()))
|
|
|
|
-> IO (Either ConnectionProblem P2PConnectionPair)
|
2024-07-26 14:24:23 +00:00
|
|
|
localP2PConnectionPair connparams relv startworker = do
|
|
|
|
(clientconn, serverconn) <- mkP2PConnectionPair connparams
|
|
|
|
("http client", "http server")
|
|
|
|
clientrunst <- mkClientRunState connparams
|
|
|
|
serverrunst <- mkServerRunState connparams
|
|
|
|
asyncworker <- async $
|
|
|
|
startworker serverrunst serverconn
|
|
|
|
let releaseconn = atomically $ void $ tryPutTMVar relv $
|
|
|
|
liftIO $ wait asyncworker
|
|
|
|
>>= either throwM return
|
|
|
|
return $ Right $ P2PConnectionPair
|
|
|
|
{ clientRunState = clientrunst
|
|
|
|
, clientP2PConnection = clientconn
|
2024-07-26 16:49:06 +00:00
|
|
|
, serverP2PConnection = serverconn
|
2024-07-26 14:24:23 +00:00
|
|
|
, releaseP2PConnection = releaseconn
|
|
|
|
, closeP2PConnection = releaseconn
|
|
|
|
}
|
|
|
|
|
|
|
|
mkP2PConnectionPair
|
|
|
|
:: ConnectionParams
|
|
|
|
-> (String, String)
|
|
|
|
-> IO (P2PConnection, P2PConnection)
|
|
|
|
mkP2PConnectionPair connparams (n1, n2) = do
|
2024-07-11 18:37:52 +00:00
|
|
|
hdl1 <- newEmptyTMVarIO
|
|
|
|
hdl2 <- newEmptyTMVarIO
|
|
|
|
wait1 <- newEmptyTMVarIO
|
|
|
|
wait2 <- newEmptyTMVarIO
|
2024-07-26 19:25:15 +00:00
|
|
|
closed1 <- newEmptyTMVarIO
|
|
|
|
closed2 <- newEmptyTMVarIO
|
|
|
|
let h1 = P2PHandleTMVar hdl1
|
|
|
|
(if connectionWaitVar connparams then Just wait1 else Nothing)
|
|
|
|
closed1
|
|
|
|
let h2 = P2PHandleTMVar hdl2
|
|
|
|
(if connectionWaitVar connparams then Just wait2 else Nothing)
|
|
|
|
closed2
|
2024-07-11 18:37:52 +00:00
|
|
|
let clientconn = P2PConnection Nothing
|
|
|
|
(const True) h2 h1
|
2024-07-26 14:24:23 +00:00
|
|
|
(ConnIdent (Just n1))
|
|
|
|
let serverconn = P2PConnection Nothing
|
|
|
|
(const True) h1 h2
|
|
|
|
(ConnIdent (Just n2))
|
|
|
|
return (clientconn, serverconn)
|
|
|
|
|
|
|
|
mkServerRunState :: ConnectionParams -> IO RunState
|
|
|
|
mkServerRunState connparams = do
|
|
|
|
prototvar <- newTVarIO $ connectionProtocolVersion connparams
|
|
|
|
mkRunState $ const $ Serving
|
|
|
|
(connectionClientUUID connparams)
|
|
|
|
Nothing
|
|
|
|
prototvar
|
|
|
|
|
|
|
|
mkClientRunState :: ConnectionParams -> IO RunState
|
|
|
|
mkClientRunState connparams = do
|
|
|
|
prototvar <- newTVarIO $ connectionProtocolVersion connparams
|
|
|
|
mkRunState $ const $ Client prototvar
|
|
|
|
|
|
|
|
proxyConnection
|
implement proxy connection pool
removeOldestProxyConnectionPool will be innefficient the larger the pool
is. A better data structure could be more efficient. Eg, make each value
in the pool include the timestamp of its oldest element, then the oldest
value can be found and modified, rather than rebuilding the whole Map.
But, for pools of a few hundred items, this should be fine. It's O(n*n log n)
or so.
Also, when more than 1 connection with the same pool key exists,
it's efficient even for larger pools, since removeOldestProxyConnectionPool
is not needed.
The default of 1 idle connection could perhaps be larger.. like the
number of jobs? Otoh, it seems good to ramp up and down the number of
connections, which does happen. With 1, there is at most one stale
connection, which might cause a request to fail.
2024-07-26 20:34:08 +00:00
|
|
|
:: ProxyConnectionPoolSize
|
|
|
|
-> TMVar (IO ())
|
2024-07-26 14:24:23 +00:00
|
|
|
-> ConnectionParams
|
|
|
|
-> AnnexWorkerPool
|
implement proxy connection pool
removeOldestProxyConnectionPool will be innefficient the larger the pool
is. A better data structure could be more efficient. Eg, make each value
in the pool include the timestamp of its oldest element, then the oldest
value can be found and modified, rather than rebuilding the whole Map.
But, for pools of a few hundred items, this should be fine. It's O(n*n log n)
or so.
Also, when more than 1 connection with the same pool key exists,
it's efficient even for larger pools, since removeOldestProxyConnectionPool
is not needed.
The default of 1 idle connection could perhaps be larger.. like the
number of jobs? Otoh, it seems good to ramp up and down the number of
connections, which does happen. With 1, there is at most one stale
connection, which might cause a request to fail.
2024-07-26 20:34:08 +00:00
|
|
|
-> TMVar ProxyConnectionPool
|
2024-07-26 14:24:23 +00:00
|
|
|
-> ProxyConnection
|
|
|
|
-> IO (Either ConnectionProblem P2PConnectionPair)
|
implement proxy connection pool
removeOldestProxyConnectionPool will be innefficient the larger the pool
is. A better data structure could be more efficient. Eg, make each value
in the pool include the timestamp of its oldest element, then the oldest
value can be found and modified, rather than rebuilding the whole Map.
But, for pools of a few hundred items, this should be fine. It's O(n*n log n)
or so.
Also, when more than 1 connection with the same pool key exists,
it's efficient even for larger pools, since removeOldestProxyConnectionPool
is not needed.
The default of 1 idle connection could perhaps be larger.. like the
number of jobs? Otoh, it seems good to ramp up and down the number of
connections, which does happen. With 1, there is at most one stale
connection, which might cause a request to fail.
2024-07-26 20:34:08 +00:00
|
|
|
proxyConnection proxyconnectionpoolsize relv connparams workerpool proxypool proxyconn = do
|
2024-07-26 16:49:06 +00:00
|
|
|
(clientconn, proxyfromclientconn) <-
|
|
|
|
mkP2PConnectionPair connparams ("http client", "proxy")
|
2024-07-26 14:24:23 +00:00
|
|
|
clientrunst <- mkClientRunState connparams
|
|
|
|
proxyfromclientrunst <- mkClientRunState connparams
|
2024-07-11 18:37:52 +00:00
|
|
|
asyncworker <- async $
|
2024-07-26 14:24:23 +00:00
|
|
|
inAnnexWorker' workerpool $ do
|
|
|
|
proxystate <- liftIO Proxy.mkProxyState
|
|
|
|
let proxyparams = Proxy.ProxyParams
|
|
|
|
{ Proxy.proxyMethods = mkProxyMethods
|
|
|
|
, Proxy.proxyState = proxystate
|
|
|
|
, Proxy.proxyServerMode = connectionServerMode connparams
|
|
|
|
, Proxy.proxyClientSide = Proxy.ClientSide proxyfromclientrunst proxyfromclientconn
|
|
|
|
, Proxy.proxyUUID = proxyConnectionRemoteUUID proxyconn
|
2024-07-28 14:16:35 +00:00
|
|
|
, Proxy.proxySelector = proxyConnectionSelector proxyconn
|
|
|
|
, Proxy.proxyConcurrencyConfig = proxyConnectionConcurrency proxyconn
|
2024-07-26 17:57:28 +00:00
|
|
|
, Proxy.proxyClientProtocolVersion = connectionProtocolVersion connparams
|
2024-07-26 14:24:23 +00:00
|
|
|
}
|
|
|
|
let proxy mrequestmessage = case mrequestmessage of
|
|
|
|
Just requestmessage -> do
|
|
|
|
Proxy.proxyRequest proxydone proxyparams
|
|
|
|
requestcomplete requestmessage protoerrhandler
|
|
|
|
Nothing -> return ()
|
|
|
|
protoerrhandler proxy $
|
|
|
|
liftIO $ runNetProto proxyfromclientrunst proxyfromclientconn $
|
|
|
|
P2P.net P2P.receiveMessage
|
|
|
|
|
2024-07-29 15:03:18 +00:00
|
|
|
let closebothsides = do
|
|
|
|
liftIO $ closeConnection proxyfromclientconn
|
|
|
|
liftIO $ closeConnection clientconn
|
|
|
|
|
|
|
|
let releaseconn connstillusable = do
|
2024-07-26 19:25:15 +00:00
|
|
|
atomically $ void $ tryPutTMVar relv $ do
|
2024-07-29 15:03:18 +00:00
|
|
|
unless connstillusable
|
|
|
|
closebothsides
|
cleanly close proxy connection on interrupted PUT
An interrupted PUT to cluster that has a node that is a special remote
over http left open the connection to the cluster, so the next request
opens another one. So did an interrupted PUT directly to the proxied
special remote over http.
proxySpecialRemote was stuck waiting for all the DATA. Its connection
remained open so it kept waiting.
In servePut, checktooshort handles closing the P2P connection
when too short a data is received from PUT. But, checktooshort was only
called after the protoaction, which is what runs the proxy, which is
what was getting stuck. Modified it to run as a background thread,
which waits for the tooshortv to be written to, which gather always does
once it gets to the end of the data received from the http client.
That makes proxyConnection's releaseconn run once all data is received
from the http client. Made it close the connection handles before
waiting on the asyncworker thread. This lets proxySpecialRemote finish
processing any data from the handle, and then it will give up,
more or less cleanly, if it didn't receive enough data.
I say "more or less cleanly" because with both sides of the P2P
connection taken down, some protocol unhappyness results. Which can lead
to some ugly debug messages. But also can cause the asyncworker thread
to throw an exception. So made withP2PConnections not crash when it
receives an exception from releaseconn.
This did have a small change to the behavior of an interrupted PUT when
proxying to a regular remote. proxyConnection has a protoerrorhandler
that closes the proxy connection on a protocol error. But the proxy
connection is also closed by checktooshort when it closes the P2P
connection. Closing the same proxy connection twice is not a problem,
it just results in duplicated debug messages about it.
2024-07-29 14:33:26 +00:00
|
|
|
r <- liftIO $ wait asyncworker
|
2024-07-29 15:03:18 +00:00
|
|
|
when connstillusable
|
|
|
|
closebothsides
|
|
|
|
if connstillusable
|
implement proxy connection pool
removeOldestProxyConnectionPool will be innefficient the larger the pool
is. A better data structure could be more efficient. Eg, make each value
in the pool include the timestamp of its oldest element, then the oldest
value can be found and modified, rather than rebuilding the whole Map.
But, for pools of a few hundred items, this should be fine. It's O(n*n log n)
or so.
Also, when more than 1 connection with the same pool key exists,
it's efficient even for larger pools, since removeOldestProxyConnectionPool
is not needed.
The default of 1 idle connection could perhaps be larger.. like the
number of jobs? Otoh, it seems good to ramp up and down the number of
connections, which does happen. With 1, there is at most one stale
connection, which might cause a request to fail.
2024-07-26 20:34:08 +00:00
|
|
|
then liftIO $ do
|
|
|
|
now <- getPOSIXTime
|
|
|
|
evicted <- atomically $ putProxyConnectionPool proxypool proxyconnectionpoolsize connparams $
|
|
|
|
proxyconn { proxyConnectionLastUsed = now }
|
|
|
|
maybe noop closeproxyconnection evicted
|
|
|
|
else closeproxyconnection proxyconn
|
2024-07-26 19:25:15 +00:00
|
|
|
either throwM return r
|
implement proxy connection pool
removeOldestProxyConnectionPool will be innefficient the larger the pool
is. A better data structure could be more efficient. Eg, make each value
in the pool include the timestamp of its oldest element, then the oldest
value can be found and modified, rather than rebuilding the whole Map.
But, for pools of a few hundred items, this should be fine. It's O(n*n log n)
or so.
Also, when more than 1 connection with the same pool key exists,
it's efficient even for larger pools, since removeOldestProxyConnectionPool
is not needed.
The default of 1 idle connection could perhaps be larger.. like the
number of jobs? Otoh, it seems good to ramp up and down the number of
connections, which does happen. With 1, there is at most one stale
connection, which might cause a request to fail.
2024-07-26 20:34:08 +00:00
|
|
|
|
2024-07-11 18:37:52 +00:00
|
|
|
return $ Right $ P2PConnectionPair
|
|
|
|
{ clientRunState = clientrunst
|
|
|
|
, clientP2PConnection = clientconn
|
2024-07-26 16:49:06 +00:00
|
|
|
, serverP2PConnection = proxyfromclientconn
|
2024-07-26 14:24:23 +00:00
|
|
|
, releaseP2PConnection = releaseconn True
|
|
|
|
, closeP2PConnection = releaseconn False
|
2024-07-11 18:37:52 +00:00
|
|
|
}
|
|
|
|
where
|
2024-07-26 14:24:23 +00:00
|
|
|
protoerrhandler cont a = a >>= \case
|
2024-07-28 14:16:35 +00:00
|
|
|
Left _ -> proxyConnectionCloser proxyconn
|
2024-07-26 19:25:15 +00:00
|
|
|
Right v -> cont v
|
implement proxy connection pool
removeOldestProxyConnectionPool will be innefficient the larger the pool
is. A better data structure could be more efficient. Eg, make each value
in the pool include the timestamp of its oldest element, then the oldest
value can be found and modified, rather than rebuilding the whole Map.
But, for pools of a few hundred items, this should be fine. It's O(n*n log n)
or so.
Also, when more than 1 connection with the same pool key exists,
it's efficient even for larger pools, since removeOldestProxyConnectionPool
is not needed.
The default of 1 idle connection could perhaps be larger.. like the
number of jobs? Otoh, it seems good to ramp up and down the number of
connections, which does happen. With 1, there is at most one stale
connection, which might cause a request to fail.
2024-07-26 20:34:08 +00:00
|
|
|
|
2024-07-26 14:24:23 +00:00
|
|
|
proxydone = return ()
|
implement proxy connection pool
removeOldestProxyConnectionPool will be innefficient the larger the pool
is. A better data structure could be more efficient. Eg, make each value
in the pool include the timestamp of its oldest element, then the oldest
value can be found and modified, rather than rebuilding the whole Map.
But, for pools of a few hundred items, this should be fine. It's O(n*n log n)
or so.
Also, when more than 1 connection with the same pool key exists,
it's efficient even for larger pools, since removeOldestProxyConnectionPool
is not needed.
The default of 1 idle connection could perhaps be larger.. like the
number of jobs? Otoh, it seems good to ramp up and down the number of
connections, which does happen. With 1, there is at most one stale
connection, which might cause a request to fail.
2024-07-26 20:34:08 +00:00
|
|
|
|
2024-07-26 14:24:23 +00:00
|
|
|
requestcomplete () = return ()
|
implement proxy connection pool
removeOldestProxyConnectionPool will be innefficient the larger the pool
is. A better data structure could be more efficient. Eg, make each value
in the pool include the timestamp of its oldest element, then the oldest
value can be found and modified, rather than rebuilding the whole Map.
But, for pools of a few hundred items, this should be fine. It's O(n*n log n)
or so.
Also, when more than 1 connection with the same pool key exists,
it's efficient even for larger pools, since removeOldestProxyConnectionPool
is not needed.
The default of 1 idle connection could perhaps be larger.. like the
number of jobs? Otoh, it seems good to ramp up and down the number of
connections, which does happen. With 1, there is at most one stale
connection, which might cause a request to fail.
2024-07-26 20:34:08 +00:00
|
|
|
|
|
|
|
closeproxyconnection =
|
2024-07-28 14:16:35 +00:00
|
|
|
void . inAnnexWorker' workerpool . proxyConnectionCloser
|
2024-07-09 13:08:42 +00:00
|
|
|
|
2024-07-09 01:11:01 +00:00
|
|
|
data Locker = Locker
|
|
|
|
{ lockerThread :: Async ()
|
|
|
|
, lockerVar :: TMVar Bool
|
|
|
|
-- ^ Left empty until the thread has taken the lock
|
|
|
|
-- (or failed to do so), then True while the lock is held,
|
|
|
|
-- and setting to False causes the lock to be released.
|
2024-07-24 18:25:40 +00:00
|
|
|
, lockerTimeoutDisable :: TMVar ()
|
|
|
|
-- ^ Until this is filled, the lock will be subject to timeout.
|
|
|
|
-- Once filled the lock will remain held until explicitly dropped.
|
2024-07-09 01:11:01 +00:00
|
|
|
}
|
|
|
|
|
2024-07-24 18:25:40 +00:00
|
|
|
mkLocker :: (IO (Maybe a)) -> (a -> IO ()) -> IO (Maybe (Locker, LockID))
|
2024-07-09 01:11:01 +00:00
|
|
|
mkLocker lock unlock = do
|
|
|
|
lv <- newEmptyTMVarIO
|
2024-07-24 18:25:40 +00:00
|
|
|
timeoutdisablev <- newEmptyTMVarIO
|
2024-07-09 01:11:01 +00:00
|
|
|
let setlocked = putTMVar lv
|
2024-07-24 18:25:40 +00:00
|
|
|
locktid <- async $ lock >>= \case
|
2024-07-22 21:36:56 +00:00
|
|
|
Nothing ->
|
|
|
|
atomically $ setlocked False
|
|
|
|
Just st -> do
|
|
|
|
atomically $ setlocked True
|
|
|
|
atomically $ do
|
|
|
|
v <- takeTMVar lv
|
|
|
|
if v
|
|
|
|
then retry
|
|
|
|
else setlocked False
|
|
|
|
unlock st
|
2024-07-09 01:11:01 +00:00
|
|
|
locksuccess <- atomically $ readTMVar lv
|
|
|
|
if locksuccess
|
|
|
|
then do
|
2024-07-24 18:25:40 +00:00
|
|
|
timeouttid <- async $ do
|
|
|
|
threadDelaySeconds $ Seconds $ fromIntegral $
|
|
|
|
durationSeconds p2pDefaultLockContentRetentionDuration
|
|
|
|
atomically (tryReadTMVar timeoutdisablev) >>= \case
|
|
|
|
Nothing -> void $ atomically $
|
|
|
|
writeTMVar lv False
|
|
|
|
Just () -> noop
|
|
|
|
tid <- async $ do
|
|
|
|
wait locktid
|
|
|
|
cancel timeouttid
|
2024-07-09 01:11:01 +00:00
|
|
|
lckid <- B64UUID <$> genUUID
|
2024-07-24 18:25:40 +00:00
|
|
|
return (Just (Locker tid lv timeoutdisablev, lckid))
|
2024-07-09 01:11:01 +00:00
|
|
|
else do
|
2024-07-24 18:25:40 +00:00
|
|
|
wait locktid
|
2024-07-09 01:11:01 +00:00
|
|
|
return Nothing
|
|
|
|
|
|
|
|
storeLock :: LockID -> Locker -> P2PHttpServerState -> IO ()
|
|
|
|
storeLock lckid locker st = atomically $ do
|
|
|
|
m <- takeTMVar (openLocks st)
|
|
|
|
let !m' = M.insert lckid locker m
|
|
|
|
putTMVar (openLocks st) m'
|
|
|
|
|
2024-07-24 18:25:40 +00:00
|
|
|
keepingLocked :: LockID -> P2PHttpServerState -> IO ()
|
|
|
|
keepingLocked lckid st = do
|
|
|
|
m <- atomically $ readTMVar (openLocks st)
|
|
|
|
case M.lookup lckid m of
|
|
|
|
Nothing -> return ()
|
|
|
|
Just locker ->
|
|
|
|
atomically $ void $
|
|
|
|
tryPutTMVar (lockerTimeoutDisable locker) ()
|
|
|
|
|
2024-07-09 01:11:01 +00:00
|
|
|
dropLock :: LockID -> P2PHttpServerState -> IO ()
|
|
|
|
dropLock lckid st = do
|
|
|
|
v <- atomically $ do
|
|
|
|
m <- takeTMVar (openLocks st)
|
|
|
|
let (mlocker, !m') =
|
|
|
|
M.updateLookupWithKey (\_ _ -> Nothing) lckid m
|
|
|
|
putTMVar (openLocks st) m'
|
|
|
|
case mlocker of
|
|
|
|
Nothing -> return Nothing
|
2024-07-22 21:36:56 +00:00
|
|
|
-- Signal to the locker's thread that it can
|
|
|
|
-- release the lock.
|
2024-07-09 01:11:01 +00:00
|
|
|
Just locker -> do
|
|
|
|
_ <- swapTMVar (lockerVar locker) False
|
|
|
|
return (Just locker)
|
|
|
|
case v of
|
|
|
|
Nothing -> return ()
|
|
|
|
Just locker -> wait (lockerThread locker)
|
2024-07-10 16:19:47 +00:00
|
|
|
|
|
|
|
getAnnexWorkerPool :: (AnnexWorkerPool -> Annex a) -> Annex a
|
|
|
|
getAnnexWorkerPool a = startConcurrency transferStages $
|
|
|
|
Annex.getState Annex.workers >>= \case
|
|
|
|
Nothing -> giveup "Use -Jn or set annex.jobs to configure the number of worker threads."
|
|
|
|
Just wp -> a wp
|
|
|
|
|
|
|
|
inAnnexWorker :: P2PHttpServerState -> Annex a -> IO (Either SomeException a)
|
2024-07-11 18:37:52 +00:00
|
|
|
inAnnexWorker st = inAnnexWorker' (annexWorkerPool st)
|
|
|
|
|
|
|
|
inAnnexWorker' :: AnnexWorkerPool -> Annex a -> IO (Either SomeException a)
|
|
|
|
inAnnexWorker' poolv annexaction = do
|
|
|
|
(workerstrd, workerstage) <- atomically $ waitStartWorkerSlot poolv
|
2024-07-10 16:19:47 +00:00
|
|
|
resv <- newEmptyTMVarIO
|
|
|
|
aid <- async $ do
|
|
|
|
(res, strd) <- Annex.run workerstrd annexaction
|
|
|
|
atomically $ putTMVar resv res
|
|
|
|
return strd
|
|
|
|
atomically $ do
|
2024-07-11 18:37:52 +00:00
|
|
|
pool <- takeTMVar poolv
|
2024-07-10 16:19:47 +00:00
|
|
|
let !pool' = addWorkerPool (ActiveWorker aid workerstage) pool
|
2024-07-11 18:37:52 +00:00
|
|
|
putTMVar poolv pool'
|
2024-07-10 16:19:47 +00:00
|
|
|
(res, workerstrd') <- waitCatch aid >>= \case
|
|
|
|
Right strd -> do
|
|
|
|
r <- atomically $ takeTMVar resv
|
|
|
|
return (Right r, strd)
|
|
|
|
Left err -> return (Left err, workerstrd)
|
|
|
|
atomically $ do
|
2024-07-11 18:37:52 +00:00
|
|
|
pool <- takeTMVar poolv
|
2024-07-10 16:19:47 +00:00
|
|
|
let !pool' = deactivateWorker pool aid workerstrd'
|
2024-07-11 18:37:52 +00:00
|
|
|
putTMVar poolv pool'
|
2024-07-10 16:19:47 +00:00
|
|
|
return res
|
2024-07-25 17:15:05 +00:00
|
|
|
|
|
|
|
data ProxyConnection = ProxyConnection
|
2024-07-26 14:24:23 +00:00
|
|
|
{ proxyConnectionRemoteUUID :: UUID
|
2024-07-28 14:16:35 +00:00
|
|
|
, proxyConnectionSelector :: Proxy.ProxySelector
|
|
|
|
, proxyConnectionCloser :: Annex ()
|
|
|
|
, proxyConnectionConcurrency :: Proxy.ConcurrencyConfig
|
implement proxy connection pool
removeOldestProxyConnectionPool will be innefficient the larger the pool
is. A better data structure could be more efficient. Eg, make each value
in the pool include the timestamp of its oldest element, then the oldest
value can be found and modified, rather than rebuilding the whole Map.
But, for pools of a few hundred items, this should be fine. It's O(n*n log n)
or so.
Also, when more than 1 connection with the same pool key exists,
it's efficient even for larger pools, since removeOldestProxyConnectionPool
is not needed.
The default of 1 idle connection could perhaps be larger.. like the
number of jobs? Otoh, it seems good to ramp up and down the number of
connections, which does happen. With 1, there is at most one stale
connection, which might cause a request to fail.
2024-07-26 20:34:08 +00:00
|
|
|
, proxyConnectionLastUsed :: POSIXTime
|
2024-07-25 19:39:57 +00:00
|
|
|
}
|
2024-07-28 14:16:35 +00:00
|
|
|
|
|
|
|
instance Show ProxyConnection where
|
|
|
|
show pc = unwords
|
|
|
|
[ "ProxyConnection"
|
|
|
|
, show (proxyConnectionRemoteUUID pc)
|
|
|
|
, show (proxyConnectionLastUsed pc)
|
|
|
|
]
|
|
|
|
|
|
|
|
openedProxyConnection
|
|
|
|
:: UUID
|
2024-07-29 13:48:06 +00:00
|
|
|
-> String
|
2024-07-28 14:16:35 +00:00
|
|
|
-> Proxy.ProxySelector
|
|
|
|
-> Annex ()
|
|
|
|
-> Proxy.ConcurrencyConfig
|
2024-07-29 13:48:06 +00:00
|
|
|
-> Annex ProxyConnection
|
|
|
|
openedProxyConnection u desc selector closer concurrency = do
|
|
|
|
now <- liftIO getPOSIXTime
|
|
|
|
fastDebug "P2P.Http" ("Opened proxy connection to " ++ desc)
|
|
|
|
return $ ProxyConnection u selector closer' concurrency now
|
|
|
|
where
|
|
|
|
closer' = do
|
|
|
|
fastDebug "P2P.Http" ("Closing proxy connection to " ++ desc)
|
|
|
|
closer
|
|
|
|
fastDebug "P2P.Http" ("Closed proxy connection to " ++ desc)
|
2024-07-25 19:39:57 +00:00
|
|
|
|
2024-07-26 14:24:23 +00:00
|
|
|
openProxyConnectionToRemote
|
|
|
|
:: AnnexWorkerPool
|
|
|
|
-> P2P.ProtocolVersion
|
2024-07-28 14:16:35 +00:00
|
|
|
-> P2P.Bypass
|
2024-07-26 14:24:23 +00:00
|
|
|
-> Remote
|
|
|
|
-> IO (Either SomeException ProxyConnection)
|
2024-07-26 17:39:43 +00:00
|
|
|
openProxyConnectionToRemote workerpool clientmaxversion bypass remote =
|
2024-07-28 14:16:35 +00:00
|
|
|
inAnnexWorker' workerpool $ do
|
|
|
|
remoteside <- proxyRemoteSide clientmaxversion bypass remote
|
|
|
|
concurrencyconfig <- Proxy.noConcurrencyConfig
|
2024-07-29 13:48:06 +00:00
|
|
|
openedProxyConnection (Remote.uuid remote)
|
|
|
|
("remote " ++ Remote.name remote)
|
2024-07-28 14:16:35 +00:00
|
|
|
(Proxy.singleProxySelector remoteside)
|
|
|
|
(Proxy.closeRemoteSide remoteside)
|
|
|
|
concurrencyconfig
|
|
|
|
|
2024-07-28 14:36:22 +00:00
|
|
|
type ClusterConcurrency = Int
|
|
|
|
|
2024-07-28 14:16:35 +00:00
|
|
|
openProxyConnectionToCluster
|
|
|
|
:: AnnexWorkerPool
|
|
|
|
-> P2P.ProtocolVersion
|
|
|
|
-> P2P.Bypass
|
|
|
|
-> ClusterUUID
|
2024-07-28 14:36:22 +00:00
|
|
|
-> ClusterConcurrency
|
2024-07-28 14:16:35 +00:00
|
|
|
-> IO (Either SomeException ProxyConnection)
|
2024-07-28 14:36:22 +00:00
|
|
|
openProxyConnectionToCluster workerpool clientmaxversion bypass clusteruuid concurrency =
|
2024-07-28 14:16:35 +00:00
|
|
|
inAnnexWorker' workerpool $ do
|
2024-07-28 14:36:22 +00:00
|
|
|
(proxyselector, closenodes) <-
|
2024-07-28 14:16:35 +00:00
|
|
|
clusterProxySelector clusteruuid clientmaxversion bypass
|
2024-07-28 14:36:22 +00:00
|
|
|
concurrencyconfig <- Proxy.mkConcurrencyConfig concurrency
|
2024-07-29 13:48:06 +00:00
|
|
|
openedProxyConnection (fromClusterUUID clusteruuid)
|
|
|
|
("cluster " ++ fromUUID (fromClusterUUID clusteruuid))
|
2024-07-28 14:16:35 +00:00
|
|
|
proxyselector closenodes concurrencyconfig
|
2024-07-26 14:24:23 +00:00
|
|
|
|
implement proxy connection pool
removeOldestProxyConnectionPool will be innefficient the larger the pool
is. A better data structure could be more efficient. Eg, make each value
in the pool include the timestamp of its oldest element, then the oldest
value can be found and modified, rather than rebuilding the whole Map.
But, for pools of a few hundred items, this should be fine. It's O(n*n log n)
or so.
Also, when more than 1 connection with the same pool key exists,
it's efficient even for larger pools, since removeOldestProxyConnectionPool
is not needed.
The default of 1 idle connection could perhaps be larger.. like the
number of jobs? Otoh, it seems good to ramp up and down the number of
connections, which does happen. With 1, there is at most one stale
connection, which might cause a request to fail.
2024-07-26 20:34:08 +00:00
|
|
|
type ProxyConnectionPool = (Integer, M.Map ProxyConnectionPoolKey [ProxyConnection])
|
|
|
|
|
|
|
|
type ProxyConnectionPoolSize = Integer
|
|
|
|
|
|
|
|
-- Returns any older ProxyConnection that was evicted from the pool.
|
|
|
|
putProxyConnectionPool
|
|
|
|
:: TMVar ProxyConnectionPool
|
|
|
|
-> ProxyConnectionPoolSize
|
|
|
|
-> ConnectionParams
|
|
|
|
-> ProxyConnection
|
|
|
|
-> STM (Maybe ProxyConnection)
|
|
|
|
putProxyConnectionPool proxypool maxsz connparams conn = do
|
|
|
|
(sz, m) <- takeTMVar proxypool
|
|
|
|
let ((sz', m'), evicted) = case M.lookup k m of
|
|
|
|
Nothing -> ((succ sz, M.insert k [conn] m), Nothing)
|
|
|
|
Just [] -> ((succ sz, M.insert k [conn] m), Nothing)
|
|
|
|
Just cs -> if sz >= maxsz
|
|
|
|
then ((sz, M.insert k (conn : dropFromEnd 1 cs) m), lastMaybe cs)
|
|
|
|
else ((sz, M.insert k (conn : cs) m), Nothing)
|
|
|
|
let ((sz'', m''), evicted') = if sz' > maxsz
|
|
|
|
then removeOldestProxyConnectionPool (sz', m')
|
|
|
|
else ((sz', m'), Nothing)
|
|
|
|
putTMVar proxypool (sz'', m'')
|
|
|
|
return (evicted <|> evicted')
|
|
|
|
where
|
|
|
|
k = proxyConnectionPoolKey connparams
|
|
|
|
|
|
|
|
removeOldestProxyConnectionPool :: ProxyConnectionPool -> (ProxyConnectionPool, Maybe ProxyConnection)
|
|
|
|
removeOldestProxyConnectionPool (sz, m) =
|
|
|
|
((pred sz, m'), snd <$> headMaybe l)
|
|
|
|
where
|
|
|
|
m' = M.fromListWith (++) $ map (\(k', v) -> (k', [v])) (drop 1 l)
|
|
|
|
l = sortOn (proxyConnectionLastUsed . snd) $
|
|
|
|
concatMap (\(k', pl) -> map (k', ) pl) $
|
|
|
|
M.toList m
|
2024-07-25 17:15:05 +00:00
|
|
|
|
implement proxy connection pool
removeOldestProxyConnectionPool will be innefficient the larger the pool
is. A better data structure could be more efficient. Eg, make each value
in the pool include the timestamp of its oldest element, then the oldest
value can be found and modified, rather than rebuilding the whole Map.
But, for pools of a few hundred items, this should be fine. It's O(n*n log n)
or so.
Also, when more than 1 connection with the same pool key exists,
it's efficient even for larger pools, since removeOldestProxyConnectionPool
is not needed.
The default of 1 idle connection could perhaps be larger.. like the
number of jobs? Otoh, it seems good to ramp up and down the number of
connections, which does happen. With 1, there is at most one stale
connection, which might cause a request to fail.
2024-07-26 20:34:08 +00:00
|
|
|
getProxyConnectionPool
|
2024-07-25 19:39:57 +00:00
|
|
|
:: TMVar ProxyConnectionPool
|
2024-07-25 17:15:05 +00:00
|
|
|
-> ConnectionParams
|
|
|
|
-> STM (Maybe ProxyConnection)
|
implement proxy connection pool
removeOldestProxyConnectionPool will be innefficient the larger the pool
is. A better data structure could be more efficient. Eg, make each value
in the pool include the timestamp of its oldest element, then the oldest
value can be found and modified, rather than rebuilding the whole Map.
But, for pools of a few hundred items, this should be fine. It's O(n*n log n)
or so.
Also, when more than 1 connection with the same pool key exists,
it's efficient even for larger pools, since removeOldestProxyConnectionPool
is not needed.
The default of 1 idle connection could perhaps be larger.. like the
number of jobs? Otoh, it seems good to ramp up and down the number of
connections, which does happen. With 1, there is at most one stale
connection, which might cause a request to fail.
2024-07-26 20:34:08 +00:00
|
|
|
getProxyConnectionPool proxypool connparams = do
|
|
|
|
(sz, m) <- takeTMVar proxypool
|
2024-07-25 19:39:57 +00:00
|
|
|
case M.lookup k m of
|
2024-07-25 17:15:05 +00:00
|
|
|
Just (c:cs) -> do
|
implement proxy connection pool
removeOldestProxyConnectionPool will be innefficient the larger the pool
is. A better data structure could be more efficient. Eg, make each value
in the pool include the timestamp of its oldest element, then the oldest
value can be found and modified, rather than rebuilding the whole Map.
But, for pools of a few hundred items, this should be fine. It's O(n*n log n)
or so.
Also, when more than 1 connection with the same pool key exists,
it's efficient even for larger pools, since removeOldestProxyConnectionPool
is not needed.
The default of 1 idle connection could perhaps be larger.. like the
number of jobs? Otoh, it seems good to ramp up and down the number of
connections, which does happen. With 1, there is at most one stale
connection, which might cause a request to fail.
2024-07-26 20:34:08 +00:00
|
|
|
putTMVar proxypool (sz-1, M.insert k cs m)
|
2024-07-25 17:15:05 +00:00
|
|
|
return (Just c)
|
implement proxy connection pool
removeOldestProxyConnectionPool will be innefficient the larger the pool
is. A better data structure could be more efficient. Eg, make each value
in the pool include the timestamp of its oldest element, then the oldest
value can be found and modified, rather than rebuilding the whole Map.
But, for pools of a few hundred items, this should be fine. It's O(n*n log n)
or so.
Also, when more than 1 connection with the same pool key exists,
it's efficient even for larger pools, since removeOldestProxyConnectionPool
is not needed.
The default of 1 idle connection could perhaps be larger.. like the
number of jobs? Otoh, it seems good to ramp up and down the number of
connections, which does happen. With 1, there is at most one stale
connection, which might cause a request to fail.
2024-07-26 20:34:08 +00:00
|
|
|
_ -> do
|
|
|
|
putTMVar proxypool (sz, m)
|
|
|
|
return Nothing
|
2024-07-25 19:39:57 +00:00
|
|
|
where
|
implement proxy connection pool
removeOldestProxyConnectionPool will be innefficient the larger the pool
is. A better data structure could be more efficient. Eg, make each value
in the pool include the timestamp of its oldest element, then the oldest
value can be found and modified, rather than rebuilding the whole Map.
But, for pools of a few hundred items, this should be fine. It's O(n*n log n)
or so.
Also, when more than 1 connection with the same pool key exists,
it's efficient even for larger pools, since removeOldestProxyConnectionPool
is not needed.
The default of 1 idle connection could perhaps be larger.. like the
number of jobs? Otoh, it seems good to ramp up and down the number of
connections, which does happen. With 1, there is at most one stale
connection, which might cause a request to fail.
2024-07-26 20:34:08 +00:00
|
|
|
k = proxyConnectionPoolKey connparams
|
|
|
|
|
|
|
|
type ProxyConnectionPoolKey = (UUID, UUID, [UUID], P2P.ProtocolVersion)
|
|
|
|
|
|
|
|
proxyConnectionPoolKey :: ConnectionParams -> ProxyConnectionPoolKey
|
|
|
|
proxyConnectionPoolKey connparams =
|
|
|
|
( connectionServerUUID connparams
|
|
|
|
, connectionClientUUID connparams
|
|
|
|
, connectionBypass connparams
|
|
|
|
, connectionProtocolVersion connparams
|
|
|
|
)
|