{-# LANGUAGE Safe #-} -- | A dual lock can be taken in either exclusive or shared mode, and also supports -- switching (atomically) from one mode to the other. module Data.Columbia.DualLock where import System.IO import System.FileLock import Control.Monad data DualLockShared = DualLockShared!FilePath !FileLock !FileLock data DualLock = DualLock!FilePath !FileLock !FileLock dualLockShared :: FilePath -> IO DualLockShared dualLockShared path = liftM2(DualLockShared path) (lockFile(path++".lock") Shared) (lockFile(path++".lock2") Shared) dualLock :: FilePath -> IO DualLock dualLock path = do l1 <- lockFile(path++".lock") Exclusive tryLockFile(path++".lock2") Exclusive>>=maybe (do unlockFile l1 dualLock path) ((return$!).DualLock path l1) unlockShared :: DualLockShared -> IO() unlockShared(DualLockShared _ l1 l2) = unlockFile l1>>unlockFile l2 unlock :: DualLock -> IO() unlock(DualLock _ l1 l2) = unlockFile l1>>unlockFile l2 -- | N.B. That two simultaneous attempts to switch locks will deadlock. Please protect a critical -- section that switches locks with some other lock, to ensure this doesn't happen. For instance, -- the garbage collector protects its use of 'switchLocks' on a dual lock, with an exclusive -- writer lock. switchLocks :: DualLockShared->IO DualLock switchLocks(DualLockShared path l1 l2) = do unlockFile l1 l3 <- lockFile(path++".lock") Exclusive unlockFile l2 tryLockFile(path++".lock2") Exclusive>>=maybe (do l2' <- lockFile(path++".lock2") Shared unlockFile l3 l1' <- lockFile(path++".lock") Shared switchLocks$!DualLockShared path l1' l2') ((return$!).DualLock path l3) -- | Switching from an exclusive to a shared lock may be done at any time. switchLocks2 :: DualLock->IO DualLockShared switchLocks2(DualLock path l1 l2) = do unlockFile l2 l4 <- lockFile(path++".lock2") Shared unlockFile l1 l3 <- lockFile(path++".lock") Shared return$!DualLockShared path l3 l4