File: Flock.hsc

package info (click to toggle)
haskell-filelock 0.1.1.8-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 88 kB
  • sloc: haskell: 148; makefile: 2
file content (98 lines) | stat: -rw-r--r-- 2,993 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
{-# LANGUAGE InterruptibleFFI #-}

module System.FileLock.Internal.Flock
#ifndef USE_FLOCK
  () where
#else
  (Lock, lock, tryLock, unlock) where

#include <sys/file.h>

import Control.Applicative
import Control.Concurrent (yield)
import qualified Control.Exception as E
import Data.Bits
import Foreign.C.Error
import Foreign.C.Types
import System.Posix.Files
import System.Posix.IO
  ( openFd, closeFd, defaultFileFlags, OpenMode(..)
#if MIN_VERSION_unix(2,8,0)
  , OpenFileFlags(cloexec, creat)
#else
  , setFdOption, FdOption(..)
#endif
  )
import System.Posix.Types
import Prelude

type Lock = Fd

lock :: FilePath -> Bool -> IO Lock
lock path exclusive = do
  fd <- open path
  (`E.onException` closeFd fd) $ do
    True <- flock fd exclusive True
    return fd

tryLock :: FilePath -> Bool -> IO (Maybe Lock)
tryLock path exclusive = do
  fd <- open path
  (`E.onException` closeFd fd) $ do
    success <- flock fd exclusive False
    if success
      then return $ Just $ fd
      else Nothing <$ closeFd fd

unlock :: Lock -> IO ()
unlock fd = closeFd fd

open :: FilePath -> IO Fd
open path = do
#if MIN_VERSION_unix(2,8,0)
  fd <- openFd path WriteOnly defaultFileFlags{ cloexec = True, creat = Just stdFileMode }
    -- Field cloexec only available from unix-2.8
#else
  fd <- openFd path WriteOnly (Just stdFileMode) defaultFileFlags
  setFdOption fd CloseOnExec True
    -- Ideally, we would open the file descriptor with CLOEXEC enabled, but this
    -- is not available in unix < 2.9.
    -- So we set CLOEXEC after opening the file descriptor.  This
    -- may seem like a race condition at first. However, since the lock is always
    -- taken after CLOEXEC is set, the worst that can happen is that a child
    -- process inherits the open FD in an unlocked state. While non-ideal from a
    -- performance standpoint, it doesn't introduce any locking bugs.
#endif
  return fd

flock :: Fd -> Bool -> Bool -> IO Bool
flock (Fd fd) exclusive block = do
  r <- c_flock fd $ modeOp .|. blockOp
  if r == 0
    then return True -- success
    else do
      errno <- getErrno
      case () of
        _ | errno == eWOULDBLOCK
            -> return False -- already taken
          | errno == eINTR -> do
              -- If InterruptibleFFI interrupted the syscall with EINTR,
              -- we need to give the accompanying Haskell exception a chance to bubble.
              -- See also https://gitlab.haskell.org/ghc/ghc/issues/8684#note_142404.
              E.interruptible yield
              flock (Fd fd) exclusive block
          | otherwise -> throwErrno "flock"
  where
    modeOp = case exclusive of
      False -> #{const LOCK_SH}
      True -> #{const LOCK_EX}
    blockOp = case block of
      True -> 0
      False -> #{const LOCK_NB}

-- `interruptible` so that async exceptions like `timeout` can stop it
-- when used in blocking mode (without `LOCK_NB`).
foreign import ccall interruptible "flock"
  c_flock :: CInt -> CInt -> IO CInt

#endif /* USE_FLOCK */