有没有办法将 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

最佳答案

ResourceT 仅用于在特殊情况下清理资源。它不旨在提供快速定稿,仅保证定稿。为了迅速起见,conduit 提供了自己的处理清理的工具。在您的情况下,您正在寻找两者:您希望尽可能早地进行清理,并且即使在抛出异常的情况下也会发生。为此,您应该使用 bracketP 。例如:

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

这会导致所需的 alloc 和 free 输出交错。

10-07 18:27