packages feed

ribosome-0.4.0.0: lib/Ribosome/Data/Conduit.hs

module Ribosome.Data.Conduit where

import Conduit (ConduitT, MonadResource, bracketP, runConduit, yield, (.|))
import Control.Concurrent (forkIO)
import Control.Concurrent.Lifted (fork, killThread)
import Control.Concurrent.STM.TBMChan (TBMChan, closeTBMChan, newTBMChan, readTBMChan, writeTBMChan)
import Control.Exception.Lifted (bracket, finally)
import Control.Monad.Trans.Control (embed)
import qualified Data.Conduit.Combinators as Conduit (mapM_)

import Ribosome.Control.Monad.Ribo (modifyTMVar)

withTBMChan ::
  MonadIO m =>
  MonadBaseControl IO m =>
  Int ->
  (TBMChan a -> m b) ->
  m b
withTBMChan bound =
  bracket acquire release
  where
    acquire =
      atomically (newTBMChan bound)
    release =
      atomically . closeTBMChan

sourceChan ::
  MonadIO m =>
  TBMChan a ->
  ConduitT () a m ()
sourceChan chan =
  loop
  where
    loop =
      traverse_ recurse =<< atomically (readTBMChan chan)
    recurse a =
      yield a *> loop

sourceTerminated ::
  MonadIO m =>
  TMVar Int ->
  TBMChan a ->
  m ()
sourceTerminated var chan = do
  n <- modifyTMVar (subtract 1) var
  when (n == 0) (atomically $ closeTBMChan chan)

mergeSourcesWith ::
  MonadResource m =>
  TMVar Int ->
  TBMChan a ->
  (ConduitT () a m () -> IO (StM m ())) ->
  [ConduitT () a m ()] ->
  ConduitT () a m ()
mergeSourcesWith activeSources chan sourceRunner sources =
  bracketP acquire release (const combinedSource)
  where
    acquire =
      traverse (forkIO . start) sources
    start source = do
      void $ sourceRunner source
      sourceTerminated activeSources chan
    release ids =
      traverse_ killThread ids *>
      atomically (closeTBMChan chan)
    combinedSource =
      sourceChan chan

mergeSources ::
  MonadResource m =>
  MonadBaseControl IO m =>
  Int ->
  [ConduitT () a m ()] ->
  ConduitT () a m ()
mergeSources bound sources = do
  activeSources <- atomically $ newTMVar (length sources)
  chan <- atomically (newTBMChan bound)
  embeddedRunner <- lift $ embed (embedSourceRunner chan)
  mergeSourcesWith activeSources chan embeddedRunner sources
  where
    embedSourceRunner chan source =
      runConduit (source .| Conduit.mapM_ (atomically . writeTBMChan chan))

withSourcesInChanAs ::
  MonadIO m =>
  MonadBaseControl IO m =>
  (ConduitT () a m () -> m b) ->
  [ConduitT () a m ()] ->
  TBMChan a ->
  m b
withSourcesInChanAs executor sources chan = do
  activeSources <- atomically $ newTMVar (length sources)
  threadIds <- traverse (fork . start activeSources) sources
  finally listen (release threadIds)
  where
    release =
      traverse_ killThread
    listen =
      executor $ sourceChan chan
    start activeSources source = do
      runConduit (source .| Conduit.mapM_ (atomically . writeTBMChan chan))
      sourceTerminated activeSources chan

simpleExecutor ::
  Monad m =>
  ConduitT a Void m b ->
  ConduitT () a m () ->
  m b
simpleExecutor consumer s =
  runConduit $ s .| consumer

withSourcesInChan ::
  MonadIO m =>
  MonadBaseControl IO m =>
  ConduitT a Void m b ->
  [ConduitT () a m ()] ->
  TBMChan a ->
  m b
withSourcesInChan =
  withSourcesInChanAs . simpleExecutor

withMergedSourcesAs ::
  MonadIO m =>
  MonadBaseControl IO m =>
  (ConduitT () a m () -> m b) ->
  Int ->
  [ConduitT () a m ()] ->
  m b
withMergedSourcesAs executor bound sources =
  withTBMChan bound (withSourcesInChanAs executor sources)

withMergedSources ::
  MonadIO m =>
  MonadBaseControl IO m =>
  ConduitT a Void m b ->
  Int ->
  [ConduitT () a m ()] ->
  m b
withMergedSources =
  withMergedSourcesAs . simpleExecutor