Ответ 1
Самый идиоматический способ unixy сделать это с помощью flock:
Я пишу сценарии CGI в Haskell. Когда пользователь нажимает "отправить", программа Haskell запускается на сервере, обновляет (то есть считывает, обрабатывает, перезаписывает) файл состояния. Чтение, а затем перезаписи иногда вызывает проблемы с ленивым IO, так как мы можем сгенерировать большой выходной префикс, прежде чем мы закончим чтение ввода. Хуже того, пользователи иногда отказываются от кнопки отправки и одновременно запускают два экземпляра процесса, борясь за один и тот же файл!
Какой хороший способ реализовать
transactionalUpdate :: FilePath -> (String -> String) -> IO ()
где функция (& lsquo; update & rsquo;) вычисляет новое содержимое файла из старого содержимого файла? Небезопасно предполагать, что & lsquo; update & rsquo; является строгим, но можно предположить, что он является полным (надежность функций частичного обновления является бонусом). Транзакции могут быть предприняты одновременно, но никакая транзакция не должна обновляться, если файл был написан кем-либо еще с момента его чтения. Это нормально для транзакции, чтобы прервать в случае конкуренции за доступ к файлам. Мы можем считать источником уникальных для системы временных имен файлов.
Моя текущая попытка записывается во временный файл, а затем используется команда копирования системы для перезаписывания. Это, похоже, касается ленивых проблем с ИО, но это не ударяет меня так же безопасно от рас. Есть ли проверенная и проверенная формула, которую мы могли бы просто бутылить?
Самый идиоматический способ unixy сделать это с помощью flock:
Вот грубый первый разрез, основанный на атомарности базового mkdir
. Кажется, это соответствует спецификации, но я не уверен, насколько она надежна или быстра:
import Control.DeepSeq
import Control.Exception
import System.Directory
import System.IO
transactionalUpdate :: FilePath -> (String -> String) -> IO ()
transactionalUpdate file upd = bracket acquire release update
where
acquire = do
let lockName = file ++ ".lock"
createDirectory lockName
return lockName
release = removeDirectory
update _ = nonTransactionalUpdate file upd
nonTransactionalUpdate :: FilePath -> (String -> String) -> IO ()
nonTransactionalUpdate file upd = do
h <- openFile file ReadMode
s <- upd `fmap` hGetContents h
s `deepseq` hClose h
h <- openFile file WriteMode
hPutStr h s
hClose h
Я протестировал это, добавив следующее main
и выбрав threadDelay
в середине nonTransactionalUpdate
:
main = do
[n] <- getArgs
transactionalUpdate "foo.txt" ((show n ++ "\n") ++)
putStrLn $ "successfully updated " ++ show n
Затем я скомпилировал и выполнил кучу экземпляров с помощью этого script:
#!/bin/bash
rm foo.txt
touch foo.txt
for i in {1..50}
do
./SO $i &
done
Процесс, который напечатал успешное сообщение об обновлении, если и только если соответствующий номер находился в foo.txt
; все остальные напечатали ожидаемый SO: foo.txt.notveryunique: createDirectory: already exists (File exists)
.
Обновление:. На самом деле вы не хотите использовать уникальные имена; это должно быть последовательное имя в конкурирующих процессах. Я обновил код соответственно.