Haskell 如何使文件I/O更具事务性?
我正在用Haskell编写CGI脚本。当用户点击“提交”时,服务器上会运行一个Haskell程序,更新(即读取、处理、覆盖)状态文件。读取然后覆盖有时会导致惰性IO出现问题,因为在读取完输入之前,我们可能会生成一个较大的输出前缀。更糟糕的是,用户有时会在提交按钮上弹跳,两个进程实例同时运行,争夺同一个文件 实施的好方法是什么Haskell 如何使文件I/O更具事务性?,haskell,io,Haskell,Io,我正在用Haskell编写CGI脚本。当用户点击“提交”时,服务器上会运行一个Haskell程序,更新(即读取、处理、覆盖)状态文件。读取然后覆盖有时会导致惰性IO出现问题,因为在读取完输入之前,我们可能会生成一个较大的输出前缀。更糟糕的是,用户有时会在提交按钮上弹跳,两个进程实例同时运行,争夺同一个文件 实施的好方法是什么 transactionalUpdate :: FilePath -> (String -> String) -> IO () 函数(“更新”)从旧文件内
transactionalUpdate :: FilePath -> (String -> String) -> IO ()
函数(“更新”)从旧文件内容计算新文件内容的位置?假定“更新”是严格的是不安全的,但可以假定它是完全的(对部分更新函数的健壮性是一个额外的优点)。可以同时尝试事务,但如果文件读取后已被其他人写入,则任何事务都不能更新。在竞争文件访问权限的情况下,事务可以中止。我们可以假定系统范围内唯一临时文件名的来源
我当前尝试写入临时文件,然后使用系统复制命令覆盖。这似乎可以解决懒惰IO的问题,但我觉得它在比赛中并不安全。是否有一个经过试验和测试的公式,我们可以只装在瓶子里?这里是一个粗略的第一部分,它依赖于底层
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
然后,我用这个脚本编译并运行了一组实例:
#!/bin/bash
rm foo.txt
touch foo.txt
for i in {1..50}
do
./SO $i &
done
当且仅当相应的编号在foo.txt
中时,打印成功更新消息的过程;所有其他人都打印了预期的SO:foo.txt.notveryunique:createDirectory:已存在(文件存在)
更新:您实际上不想在此处使用唯一的名称;它必须是竞争过程中的一致名称。我已经相应地更新了代码。最惯用的unixy方法是使用flock:
acquire
和release
定义中。听起来像是羊群是一条路要走,但是谢谢你的帮助。哦,已经有人装了吗?看来这就解决了锁定问题,只剩下追尾回避问题了。也就是说,使用这种锁定机制不利于对后者使用基于重命名的方法。我认为对后者使用重命名方法是可以的。羊群是建议性的选择锁。抓取“file.ext”上的锁。然后写入“file.ext.out”,然后重命名为“file.ext”,最后释放锁?是的,只要我确保在锁定主文件的同时没有竞争临时文件名,就可以了。谢谢
#!/bin/bash
rm foo.txt
touch foo.txt
for i in {1..50}
do
./SO $i &
done