Почему при потоковой проверке строк выдается ошибка «openBinaryFile: ресурс исчерпан (слишком много открытых файлов)»? - PullRequest
2 голосов
/ 12 мая 2019

Библиотека streaming-bytestring выдает ошибку после печати около 512 байт.

Ошибка:

openBinaryFile: resource exhausted (Too many open files)

Код:

import           Control.Monad.Trans (lift, MonadIO)
import           Control.Monad.Trans.Resource (runResourceT, MonadResource, MonadUnliftIO, ResourceT, liftResourceT)
import qualified Data.ByteString.Streaming          as BSS
import qualified Data.ByteString.Streaming.Char8    as BSSC
import           System.TimeIt

main :: IO ()
main = timeIt $ runResourceT $ dump $ BSS.drop 24 $ BSS.readFile "filename"

dump :: MonadIO m => BSS.ByteString m r -> m ()
dump bs = do
    isEmpty <- BSS.null_ bs
    if isEmpty then return ()
    else do
        BSSC.putStr $ BSS.take 1 bs
        dump $ BSS.drop 1 bs

1 Ответ

2 голосов
/ 13 мая 2019

При работе с потоковыми библиотеками обычно плохая идея повторно использовать эффективный поток. Таким образом, вы можете применить функцию типа drop или splitAt к потоку и затем продолжить работу с результирующим потоком, или вы можете использовать поток целиком с помощью функции, подобной сбросьте , что оставляет вас в базовой монаде. Но вы никогда не должны применять одно и то же значение потока к двум разным функциям.

К сожалению, система типов Haskell в ее нынешнем виде не способна применить это ограничение во время компиляции, для нее потребуется некоторая форма линейных типов . Вместо этого это становится ответственностью пользователя.

Функция null_, возможно, является бородавкой в ​​ API-интерфейсе streaming-bytestring *, поскольку она не возвращает новый поток вместе с результатом, создавая впечатление, что поток повторное использование является нормальным явлением во всем API. Было бы лучше, если бы у него была подпись вроде null_ :: ByteString m r -> m (Bool, ByteString m r).

Аналогично, не используйте drop и take с одинаковым значением потока. Вместо этого используйте splitAt или uncons и работайте с разделенным результатом.

dump :: MonadIO m => BSS.ByteString m r -> m ()
dump bs = do
    mc <- BSSC.uncons bs -- bs is only used once
    case mc of
        Left _ -> return ()
        Right (c,rest) -> do liftIO $ putChar c
                             dump rest

Итак, об ошибке. Как отмечает @BobDalgleish в комментариях, происходит то, что файл открывается при вызове null_ (это первый раз, когда мы «требуем» что-то из потока). В рекурсивном вызове мы снова передаем исходное значение bs, поэтому он будет снова открывать файл, один раз для каждой итерации, пока мы не достигнем предела дескриптора файла.


Лично я не фанат использования ResourceT с потоковыми библиотеками. Я предпочитаю открыть файл с withFile, а затем создать и использовать поток с обратным вызовом, если это возможно. Но некоторые вещи сложнее.

...