{- git-annex assistant transfer slots - - Copyright 2012 Joey Hess - - Licensed under the GNU GPL version 3 or higher. -} module Assistant.TransferSlots where import Assistant.Common import Utility.ThreadScheduler import Assistant.Types.TransferSlots import Assistant.DaemonStatus import Logs.Transfer import qualified Control.Exception as E import Control.Concurrent import qualified Control.Concurrent.MSemN as MSemN type TransferGenerator = Assistant (Maybe (Transfer, TransferInfo, IO ())) {- Waits until a transfer slot becomes available, then runs a - TransferGenerator, and then runs the transfer action in its own thread. -} inTransferSlot :: TransferGenerator -> Assistant () inTransferSlot gen = do flip MSemN.wait 1 <<~ transferSlots runTransferThread =<< gen {- Runs a TransferGenerator, and its transfer action, - without waiting for a slot to become available. -} inImmediateTransferSlot :: TransferGenerator -> Assistant () inImmediateTransferSlot gen = do flip MSemN.signal (-1) <<~ transferSlots runTransferThread =<< gen {- Runs a transfer action, in an already allocated transfer slot. - Once it finishes, frees the transfer slot. - - Note that the action is subject to being killed when the transfer - is canceled or paused. - - A PauseTransfer exception is handled by letting the action be killed, - then pausing the thread until a ResumeTransfer exception is raised, - then rerunning the action. -} runTransferThread :: Maybe (Transfer, TransferInfo, IO ()) -> Assistant () runTransferThread Nothing = flip MSemN.signal 1 <<~ transferSlots runTransferThread (Just (t, info, a)) = do d <- getAssistant id tid <- liftIO $ forkIO $ go d updateTransferInfo t $ info { transferTid = Just tid } where go d = catchPauseResume d a pause d = catchPauseResume d $ runEvery (Seconds 86400) noop {- Note: This must use E.try, rather than E.catch. - When E.catch is used, and has called go in its exception - handler, Control.Concurrent.throwTo will block sometimes - when signaling. Using E.try avoids the problem. -} catchPauseResume d a' = do r <- E.try a' :: IO (Either E.SomeException ()) case r of Left e -> case E.fromException e of Just PauseTransfer -> pause d Just ResumeTransfer -> go d _ -> done d _ -> done d done d = flip runAssistant d $ flip MSemN.signal 1 <<~ transferSlots