如何使文件I / O更具事务性?

时间:2011-08-14 19:39:33

标签: haskell io

我正在Haskell中编写CGI脚本。当用户点击“提交”时,Haskell程序在服务器上运行,更新(即读入,处理,覆盖)状态文件。然后读取覆盖有时会导致延迟IO的问题,因为我们可能在读完输入之前生成一个大的输出前缀。更糟糕的是,用户有时会在提交按钮上弹跳,并且该进程的两个实例同时运行,争夺同一个文件!

实施

的好方法
transactionalUpdate :: FilePath -> (String -> String) -> IO ()

其中函数('update')从旧文件内容计算新文件内容?假设“更新”是严格的是不安全的,但可以假设它是完全的(对部分更新功能的鲁棒性是一种奖励)。可以同时尝试事务,但是如果文件是由其他人写入的,则没有事务应该能够更新。在竞争文件访问的情况下,事务可以中止。我们可以假设系统范围内唯一的临时文件名来源。

我当前的尝试写入临时文件,然后使用系统复制命令覆盖。这似乎解决了懒惰的IO问题,但它并没有让我对比赛安全。是否有一个经过试验和测试的配方,我们可以装瓶?

2 个答案:

答案 0 :(得分:6)

答案 1 :(得分:3)

这是一个粗略的第一个切割,它依赖于底层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

然后我用这个脚本编译并运行了一堆实例:

#!/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)

更新:您实际上想要在此处使用唯一名称;它必须是竞争过程中的一致名称。我已相应更新了代码。