Вложенные области ResourceT в канале Sink

Есть ли способ ограничить runResourceT сроком службы одного Sink?

Я пытаюсь построить Sink, который обертывает потенциально бесконечное количество Sinks. Это отлично работает с потоками, но я пытаюсь сделать это без потоков. Кажется, это должно быть возможно. Я столкнулся с препятствием из-за области действия runResourceT: я получаю либо слишком крупнозернистое (но функциональное), либо слишком мелкозернистое (полностью сломанное) управление ресурсами.

{-# LANGUAGE FlexibleContexts #-}

import Control.Monad.Trans (lift)
import Control.Monad.Trans.Resource
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as BC8 (pack)
import Data.Conduit
import qualified Data.Conduit.Binary as Cb
import qualified Data.Conduit.List as Cl
import System.FilePath ((<.>))

test :: IO ()
test =
  runResourceT
      $ Cl.sourceList (fmap (BC8.pack . show) [(1 :: Int)..1000])
     $$ rotateResourceHog "/tmp/foo"

-- |
-- files are allocated on demand but handles are released at the same time
rotateResourceHog
  :: MonadResource m
  => FilePath -> Sink ByteString m ()
rotateResourceHog filePath = step 0 where
  step i = do
    x <- Cl.peek
    case x of
      Just _  -> do
        chunkWriter $ filePath <.> show (i :: Integer)
        -- loop
        step $ i+1

      Nothing -> return ()

-- |
-- files are allocated on demand but handles are released immediately
rotateUsingClosedHandles
  :: (MonadBaseControl IO m, MonadResource m)
  => FilePath -> Sink ByteString m ()
rotateUsingClosedHandles filePath = step 0 where
  step i = do
    x <- Cl.peek
    case x of
      Just _  -> do
        transPipe runResourceT . chunkWriter $ filePath <.> show (i :: Integer)
        -- loop
        step $ i+1

      Nothing -> return ()

chunkWriter
  :: MonadResource m
  => FilePath -> Sink ByteString m ()
chunkWriter filePath = do
  _ <- lift $ allocate (putStrLn "alloc") (\ _ -> putStrLn "free")

  -- the actual conduit chain is more complicated
  Cl.isolate 100 =$= Cb.sinkFile filePath

person Nathan Howell    schedule 15.08.2012    source источник


Ответы (1)


ResourceT предназначен только для очистки ресурсов в исключительных случаях. Он не предназначен для обеспечения быстрой финализации, а только для гарантированной финализации. Для оперативности conduit предоставляет собственные средства для обработки очистки. В вашем случае вы ищете оба: вы хотите, чтобы очистка происходила как можно раньше и происходила даже в случае возникновения исключения. Для этого вы должны использовать bracketP. Например:

chunkWriter
  :: MonadResource m
  => FilePath -> Sink ByteString m ()
chunkWriter filePath = bracketP
    (putStrLn "alloc")
    (\() -> putStrLn "free")
    (\() -> Cl.isolate 100 =$= Cb.sinkFile filePath)

Это приводит к желаемому чередованию распределенных и свободных выходов.

person Michael Snoyman    schedule 16.08.2012
comment
Что мне нужно, так это быстрая очистка дескриптора приемника (и другого состояния из каналов gzip/lzma), что могло быть неясно в вопросе. Я использовал allocate, чтобы отслеживать это. bracketP здесь не помогает, поскольку ресурсы распределяются внутри самого приемника. - person Nathan Howell; 16.08.2012
comment
sinkFile должен отпустить дескриптор, как только ввод будет недоступен. У вас есть доказательства того, что этого не происходит? Если это так, то это ошибка, и ее нужно исправить. - person Michael Snoyman; 16.08.2012
comment
Нет, существует другой код, который использует ResourceT для очистки, и кажется, что это проблема. - person Nathan Howell; 16.08.2012