e5b4d447b6
So that remotes that use a persistent network connection are restarted. A remote might keep open a long duration network connection, and could fail to deal well with losing the connection. This is particularly a concern now that we have external special reotes. An external special remote that is implemented naively might open the connection only when PREPARE is sent, and if it loses connection, throw errors on each request that is made. (Note that the ssh connection caching should not have this problem; if the long-duration ssh process loses connection, the named pipe is disconnected and the next ssh attempt will reconnect. Also, XMPP already deals with disconnection robustly in its own way.) There's no way for git-annex to know if a lost network connection actually affects a given remote, which might have a transfer in process. It does not make sense to force kill the transferkeys process every time the NetWatcher detects a change. (Especially because the NetWatcher sometimes polls 1 change per hour.) In any case, the NetWatcher only detects connection to a network, not disconnection. So if a transfer is in progress over the network, and the network goes down, that will need to time out on its own. An alternate approch that was considered is to use a separate transferkeys process for each remote, and detect when a request fails, and assume that means that process is in a failing state and restart it. The problem with that approach is that if a resource is not available and a remote fails every time, it degrades to starting a new transferkeys process for every file transfer, which is too expensive. Instead, this commit only handles the network reconnection case, and restarts transferkeys only once the network has reconnected and another transfer needs to be made. So, a transferkeys process will be reused for 1 hour, or until the next network connection. ---- The NotificationBroadcaster was rewritten to use TMVars rather than MSampleVars, to allow checking without blocking if a notification has been received. ---- This commit was sponsored by Tobias Brunner.
96 lines
3.2 KiB
Haskell
96 lines
3.2 KiB
Haskell
{- notification broadcaster
|
|
-
|
|
- This is used to allow clients to block until there is a new notification
|
|
- that some thing occurred. It does not communicate what the change is,
|
|
- it only provides blocking reads to wait on notifications.
|
|
-
|
|
- Multiple clients are supported. Each has a unique id.
|
|
-
|
|
- Copyright 2012 Joey Hess <joey@kitenet.net>
|
|
-
|
|
- Licensed under the GNU GPL version 3 or higher.
|
|
-}
|
|
|
|
module Utility.NotificationBroadcaster (
|
|
NotificationBroadcaster,
|
|
NotificationHandle,
|
|
NotificationId,
|
|
newNotificationBroadcaster,
|
|
newNotificationHandle,
|
|
notificationHandleToId,
|
|
notificationHandleFromId,
|
|
sendNotification,
|
|
waitNotification,
|
|
checkNotification,
|
|
) where
|
|
|
|
import Common
|
|
|
|
import Control.Concurrent.STM
|
|
|
|
{- One TMVar per client, which are empty when no notification is pending,
|
|
- and full when a notification has been sent but not yet seen by the
|
|
- client. The list TMVar is never empty, so never blocks. -}
|
|
type NotificationBroadcaster = TMVar [TMVar ()]
|
|
|
|
newtype NotificationId = NotificationId Int
|
|
deriving (Read, Show, Eq, Ord)
|
|
|
|
{- Handle given out to an individual client. -}
|
|
data NotificationHandle = NotificationHandle NotificationBroadcaster NotificationId
|
|
|
|
newNotificationBroadcaster :: IO NotificationBroadcaster
|
|
newNotificationBroadcaster = atomically $ newTMVar []
|
|
|
|
{- Allocates a notification handle for a client to use.
|
|
-
|
|
- An immediate notification can be forced the first time waitNotification
|
|
- is called on the handle. This is useful in cases where a notification
|
|
- may be sent while the new handle is being constructed. Normally,
|
|
- such a notification would be missed. Forcing causes extra work,
|
|
- but ensures such notifications get seen.
|
|
-}
|
|
newNotificationHandle :: Bool -> NotificationBroadcaster -> IO NotificationHandle
|
|
newNotificationHandle force b = NotificationHandle
|
|
<$> pure b
|
|
<*> addclient
|
|
where
|
|
addclient = atomically $ do
|
|
s <- if force
|
|
then newTMVar ()
|
|
else newEmptyTMVar
|
|
l <- takeTMVar b
|
|
putTMVar b $ l ++ [s]
|
|
return $ NotificationId $ length l
|
|
|
|
{- Extracts the identifier from a notification handle.
|
|
- This can be used to eg, pass the identifier through to a WebApp. -}
|
|
notificationHandleToId :: NotificationHandle -> NotificationId
|
|
notificationHandleToId (NotificationHandle _ i) = i
|
|
|
|
notificationHandleFromId :: NotificationBroadcaster -> NotificationId -> NotificationHandle
|
|
notificationHandleFromId = NotificationHandle
|
|
|
|
{- Sends a notification to all clients. -}
|
|
sendNotification :: NotificationBroadcaster -> IO ()
|
|
sendNotification b = do
|
|
l <- atomically $ readTMVar b
|
|
mapM_ notify l
|
|
where
|
|
notify s = atomically $
|
|
whenM (isEmptyTMVar s) $
|
|
putTMVar s ()
|
|
|
|
{- Used by a client to block until a new notification is available since
|
|
- the last time it tried. -}
|
|
waitNotification :: NotificationHandle -> IO ()
|
|
waitNotification (NotificationHandle b (NotificationId i)) = do
|
|
l <- atomically $ readTMVar b
|
|
atomically $ takeTMVar (l !! i)
|
|
|
|
{- Used by a client to check if there has been a new notification since the
|
|
- last time it checked, without blocking. -}
|
|
checkNotification :: NotificationHandle -> IO Bool
|
|
checkNotification (NotificationHandle b (NotificationId i)) = do
|
|
l <- atomically $ readTMVar b
|
|
maybe False (const True) <$> atomically (tryTakeTMVar (l !! i))
|