second-transfer-0.10.0.1: hs-src/SecondTransfer/IOCallbacks/Coupling.hs
{-# LANGUAGE ExistentialQuantification, TemplateHaskell, DeriveDataTypeable, Rank2Types, OverloadedStrings #-}
module SecondTransfer.IOCallbacks.Coupling (
Coupling
, couple
, breakCoupling
, sendSourceToIO
, iocallbacksToSink
, popIOCallbacksIntoExistance
, IOCSideA
, IOCSideB
)where
import Control.Lens
import Control.Concurrent
import Control.Monad.IO.Class (liftIO, MonadIO)
import qualified Control.Exception as E
import Data.IORef
--import Data.Typeable
import Data.Conduit
import qualified Data.Conduit.List as DCL
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as LB
import qualified Data.ByteString.Builder as Bu
import SecondTransfer.IOCallbacks.Types
import SecondTransfer.Exception (IOProblem, NoMoreDataException(..), forkIOExc)
-- | A coupling between two IOCallbacks. It is breakable...
data Coupling = Coupling {
_breakNow_Cou :: MVar ()
}
makeLenses ''Coupling
-- TODO: Consider if we must report errors! how do we do that? Right now I'll re-throw exceptions,
-- but since this runs in its own thread, nobody will notice. A better coping strategy would
-- be to perhaps store the exception somewhere.
pump :: String -> MVar () -> BestEffortPullAction -> PushAction -> IO ()
pump tag break_now pull push = do
let
go = do
must_finish <- tryTakeMVar break_now
case must_finish of
Nothing -> do
either_datum <- E.try (pull True) :: IO (Either IOProblem B.ByteString)
must_finish' <- tryTakeMVar break_now
case (must_finish', either_datum) of
(Nothing, Right datum) -> do
either_ok <- E.try (push . LB.fromStrict $ datum) :: IO (Either IOProblem () )
case either_ok of
Right _ -> go
Left _e -> do
_succeeded <- tryPutMVar break_now ()
return ()
(Just (), _) ->
return ()
(Nothing, Left _e ) -> do
_succeeded <- tryPutMVar break_now ()
return ()
Just _ ->
return ()
go
-- | Connects two IO callbacks so that data received in one is sent to the
-- other.
couple :: IOCallbacks -> IOCallbacks -> IO Coupling
couple a b =
do
break_now_mvar <- newEmptyMVar
_pump_thread1 <- forkIOExc "pump1" $ pump "1to2" break_now_mvar ( a ^. bestEffortPullAction_IOC ) ( b ^. pushAction_IOC)
_pump_thread2 <- forkIOExc "pump2 "$ pump "2to1" break_now_mvar ( b ^. bestEffortPullAction_IOC ) ( a ^. pushAction_IOC)
return Coupling {
_breakNow_Cou = break_now_mvar
}
breakCoupling :: Coupling -> IO ()
breakCoupling coupling = do
_succeeded <- tryPutMVar (coupling ^. breakNow_Cou ) ()
return ()
iocallbacksToSink :: MonadIO m => IOCallbacks -> Sink LB.ByteString m ()
iocallbacksToSink ioc = DCL.mapM_ (
\ s -> do
--putStrLn $ "send: " ++ (show s)
liftIO $ ioc ^. pushAction_IOC $ s
)
-- | Sends the data coming from the source to the IOCallbacks.
-- No exceptions are handled here. This consumes the thread until
-- it finishes. The iocallbacks is not closed.
sendSourceToIO :: MonadIO m => Source m LB.ByteString -> IOCallbacks -> m ()
sendSourceToIO source ioc =
source $$ iocallbacksToSink ioc
data SideDatum = SideDatum {
_dt_SD :: IORef Bu.Builder
, _dt_M :: MVar ()
}
newSideDatum :: IO SideDatum
newSideDatum =
do
r <- newIORef mempty
m <- newEmptyMVar
return SideDatum { _dt_SD = r , _dt_M = m}
makeLenses ''SideDatum
data IOCTransit = IOCTransit {
_aToB_IOCT :: SideDatum
, _bToA_IOCT :: SideDatum
, _closed_IOCT :: MVar ()
}
makeLenses ''IOCTransit
newtype IOCSideA = IOCSideA (IOCTransit, IOCallbacks)
newtype IOCSideB = IOCSideB (IOCTransit, IOCallbacks)
popIOCallbacksIntoExistance :: IO (IOCSideA, IOCSideB)
popIOCallbacksIntoExistance = do
sd1 <- newSideDatum
sd2 <- newSideDatum
closed <- newEmptyMVar
let
t = IOCTransit {
_aToB_IOCT = sd1
, _bToA_IOCT = sd2
, _closed_IOCT = closed
}
let
action_set_a = popActions t aToB_IOCT bToA_IOCT
action_set_b = popActions t bToA_IOCT aToB_IOCT
return
(
IOCSideA (t, action_set_a),
IOCSideB (t, action_set_b)
)
popActions :: IOCTransit -> Lens' IOCTransit SideDatum -> Lens' IOCTransit SideDatum -> IOCallbacks
popActions t pullside pushside =
let
throwIfNeeded :: IO ()
throwIfNeeded = do
m <- tryReadMVar (t ^. closed_IOCT)
case m of
Nothing -> return ()
Just _ -> E.throwIO NoMoreDataException
push_action :: LB.ByteString -> IO ()
push_action datum = do
--putStrLn . show $ "PUSHED" `mappend` datum
throwIfNeeded
atomicModifyIORef' (t ^. pushside . dt_SD) $ \ bu -> (bu `mappend` (Bu.lazyByteString datum), () )
-- Notify the other side that there is more data
_ <- tryPutMVar (t ^. pushside . dt_M) ()
return ()
best_effort_pull_action can_block
| can_block = do
throwIfNeeded
hath_data <- atomicModifyIORef' ( t ^. pullside . dt_SD ) $ \ cnt -> (mempty, cnt)
let
hath_data_bs = Bu.toLazyByteString hath_data
if LB.length hath_data_bs > 0
then
return $ LB.toStrict hath_data_bs
else do
_ <- takeMVar (t ^. pullside . dt_M)
best_effort_pull_action True
| otherwise = do
throwIfNeeded
hath_data <- atomicModifyIORef' ( t ^. pullside . dt_SD ) $ \ cnt -> (mempty, cnt)
let
hath_data_bs = LB.toStrict . Bu.toLazyByteString $ hath_data
return hath_data_bs
pull_action n = (LB.toStrict . Bu.toLazyByteString ) <$> (pull_action' n mempty 0)
pull_action' :: Int -> Bu.Builder -> Int -> IO Bu.Builder
pull_action' asked bu nhath = do
throwIfNeeded
hath_data <- atomicModifyIORef' ( t ^. pullside . dt_SD ) $ \ cnt -> (mempty, cnt)
let
hath_data_bs = Bu.toLazyByteString hath_data
case fromIntegral $ LB.length hath_data_bs of
ln | (ln + nhath) < asked -> do
let
nbu = bu `mappend` hath_data
_ <- takeMVar (t ^. pullside . dt_M)
pull_action' asked nbu (ln + nhath)
| (ln + nhath) == asked ->
return $ bu `mappend` hath_data
| (ln + nhath) > asked -> do
let
complete_thing = bu `mappend` hath_data
(ret, keep) = LB.splitAt (fromIntegral asked) . Bu.toLazyByteString $ complete_thing
atomicModifyIORef' ( t ^. pullside . dt_SD ) $ \ cnt -> ( Bu.lazyByteString keep `mappend` cnt, ())
_ <- tryPutMVar ( t ^. pullside . dt_M ) ()
return . Bu.lazyByteString $ ret
| otherwise -> error "Function supposed to be total"
close_action = do
_ <- tryPutMVar (t ^. closed_IOCT ) ()
return ()
in IOCallbacks {
_pushAction_IOC = push_action
, _pullAction_IOC = pull_action
, _bestEffortPullAction_IOC = best_effort_pull_action
, _closeAction_IOC = close_action
}
instance IOChannels IOCSideA where
handshake (IOCSideA (_t,ioc)) = return ioc
instance IOChannels IOCSideB where
handshake (IOCSideB (_t,ioc)) = return ioc