Невозможно прочитать из временного файла

Я пытаюсь вызвать внешний процесс, который выполняет запись во временный файл, который я получаю с помощью withSystemTempFile. После завершения процесса я могу cat прочитать содержимое этого файла, но само приложение завершается с ошибкой openFile: resource busy (file is locked) при попытке прочитать этот файл с помощью readFile. Я следовал предложениям в ответах на этот вопрос и использовал ленивую версию readFile. Вот пример кода:

module Main where

import Prelude hiding (readFile)
import System.Process (createProcess, shell)
import System.IO (Handle, hShow)
import System.IO.Temp (withSystemTempFile)
import System.IO.Strict (readFile)

main :: IO ()
main = withSystemTempFile "temp.txt" doSomeStuff
    where
        doSomeStuff :: FilePath -> Handle -> IO ()
        doSomeStuff tempFilePath tempFilePathHandle = do
            putStrLn tempFilePath
            createProcess $ shell $ "echo \"test\" >> " ++ tempFilePath
            getLine -- It's here just to stop program while I check that I can actually "cat" the temp file
            -- here I'm able to cat the temp file
            contents <- readFile tempFilePath -- here the command fails with /tmp/temp23900-0.txt: openFile: resource busy (file is locked)
            putStrLn contents

Я все еще разбираюсь в Haskell, поэтому надеюсь, что это не что-то очевидное, так как у меня закончились идеи. Что дает?


person mjarosie    schedule 12.04.2020    source источник


Ответы (2)


Эта ошибка связана с тем, что withSystemTempFile блокирует файл при входе. Взамен он дает вам свой дескриптор (называемый tempFilePathHandle в вашем коде), поэтому вы можете прочитать файл, используя дескриптор, например:

contents <- hGetContents tempFilePathHandle

ИЗМЕНИТЬ

Это связано с тем, что GHC внутри реализует блокировку readers-writer, чтобы отслеживать, какие файлы, которые он открыл, и какие разрешения требуются. Блокировка «читатели-писатели» позволяет либо только одному писателю (монопольный доступ), либо множеству считывателей (общий доступ).

В этом случае withSystemTempFile получает блокировку записи для временного файла, и, следовательно, readFile не может получить необходимую ему блокировку чтения (поскольку, опять же, блокировка записи не позволяет GHC получить какие-либо блокировки чтения для этого файла).

Вот ссылка на Код C, реализующий блокировку. Как предполагает @luqui в комментарии ниже, это, возможно, не оптимальное решение, тем более что GHC не запрашивает никаких блокировок на уровне ОС, поэтому такие процессы, как cat, по-прежнему могут получать доступ к файлам и изменять их. Это может иметь больше смысла в ленивом контексте, когда чтение и запись в файл приведет к труднопредсказуемым результатам.

person MikaelF    schedule 12.04.2020
comment
@luqui Я ценю ваше редактирование, но мне интересно, почему тогда ОС разрешает другим процессам (например, cat) доступ к файлу, но не Haskell. - person MikaelF; 13.04.2020
comment
@luqui Глядя на код, отвечающий за получение lock в GHC, я вижу, что он реализован с помощью блокировки чтения-записи, но похоже, что эта блокировка используется только внутри, и не похоже, что Haskell запрашивает блокировку у ОС, по крайней мере, нет из того, что я вижу, глядя на исходный код и вывод lslocks. - person MikaelF; 13.04.2020
comment
Извините, это было интуитивное исправление, потому что мне было трудно поверить, что GHC ведет учет уже открытых файлов (особенно потому, что вы можете открыть два разных файла, которые являются жесткими ссылками друг на друга и т. д.). Но я ценю, что вы копаетесь в кишках, и полагаюсь на ваши более доказательные рассуждения. - person luqui; 13.04.2020

withSystemTempFile открывает созданный для вас временный файл, поэтому вам не нужно открывать его снова. readFile берет имя файла, а не его дескриптор, поэтому вы знаете, что он должен пытаться открыть его сам. Эквивалент readFile, но для уже открытого файла — hGetContents, поэтому, чтобы решить проблему, замените readFile tempFilePath на hGetContents tempFilePathHandle (и соответственно обновите import).

person Joseph Sible-Reinstate Monica    schedule 12.04.2020