summaryrefslogtreecommitdiff
path: root/libraries/base/GHC/IO
diff options
context:
space:
mode:
authorAndrzej Rybczak <electricityispower@gmail.com>2017-02-26 16:25:17 -0500
committerBen Gamari <ben@smart-cactus.org>2017-02-26 16:34:19 -0500
commit0d86aa5904e5a06c93632357122e57e4e118fd2a (patch)
treeb2ee94d50b66f2cf4f241e8594b0c4b4acb14c36 /libraries/base/GHC/IO
parentb494689c30fd0394423f264792530d1f352e1ee7 (diff)
downloadhaskell-0d86aa5904e5a06c93632357122e57e4e118fd2a.tar.gz
Add support for concurrent package db access and updates
Trac issues: #13194 Reviewers: austin, hvr, erikd, bgamari, dfeuer, duncan Subscribers: DemiMarie, dfeuer, thomie Differential Revision: https://phabricator.haskell.org/D3090
Diffstat (limited to 'libraries/base/GHC/IO')
-rw-r--r--libraries/base/GHC/IO/Handle.hs15
-rw-r--r--libraries/base/GHC/IO/Handle/FD.hs31
-rw-r--r--libraries/base/GHC/IO/Handle/Lock.hsc162
3 files changed, 194 insertions, 14 deletions
diff --git a/libraries/base/GHC/IO/Handle.hs b/libraries/base/GHC/IO/Handle.hs
index ec376cb8d4..256f802c3d 100644
--- a/libraries/base/GHC/IO/Handle.hs
+++ b/libraries/base/GHC/IO/Handle.hs
@@ -26,12 +26,14 @@ module GHC.IO.Handle (
mkFileHandle, mkDuplexHandle,
- hFileSize, hSetFileSize, hIsEOF, hLookAhead,
+ hFileSize, hSetFileSize, hIsEOF, isEOF, hLookAhead,
hSetBuffering, hSetBinaryMode, hSetEncoding, hGetEncoding,
hFlush, hFlushAll, hDuplicate, hDuplicateTo,
hClose, hClose_help,
+ LockMode(..), hLock, hTryLock,
+
HandlePosition, HandlePosn(..), hGetPosn, hSetPosn,
SeekMode(..), hSeek, hTell,
@@ -54,6 +56,8 @@ import GHC.IO.Encoding
import GHC.IO.Buffer
import GHC.IO.BufferedIO ( BufferedIO )
import GHC.IO.Device as IODevice
+import GHC.IO.Handle.FD
+import GHC.IO.Handle.Lock
import GHC.IO.Handle.Types
import GHC.IO.Handle.Internals
import GHC.IO.Handle.Text
@@ -162,6 +166,15 @@ hIsEOF handle = wantReadableHandle_ "hIsEOF" handle $ \Handle__{..} -> do
return False
-- ---------------------------------------------------------------------------
+-- isEOF
+
+-- | The computation 'isEOF' is identical to 'hIsEOF',
+-- except that it works only on 'stdin'.
+
+isEOF :: IO Bool
+isEOF = hIsEOF stdin
+
+-- ---------------------------------------------------------------------------
-- Looking ahead
-- | Computation 'hLookAhead' returns the next character from the handle
diff --git a/libraries/base/GHC/IO/Handle/FD.hs b/libraries/base/GHC/IO/Handle/FD.hs
index b2c971c908..e988b25c9f 100644
--- a/libraries/base/GHC/IO/Handle/FD.hs
+++ b/libraries/base/GHC/IO/Handle/FD.hs
@@ -18,13 +18,13 @@
module GHC.IO.Handle.FD (
stdin, stdout, stderr,
openFile, openBinaryFile, openFileBlocking,
- mkHandleFromFD, fdToHandle, fdToHandle',
- isEOF
+ mkHandleFromFD, fdToHandle, fdToHandle', handleToFd
) where
import GHC.Base
import GHC.Show
import Data.Maybe
+import Data.Typeable
import Foreign.C.Types
import GHC.MVar
import GHC.IO
@@ -32,7 +32,6 @@ import GHC.IO.Encoding
import GHC.IO.Device as IODevice
import GHC.IO.Exception
import GHC.IO.IOMode
-import GHC.IO.Handle
import GHC.IO.Handle.Types
import GHC.IO.Handle.Internals
import qualified GHC.IO.FD as FD
@@ -105,15 +104,6 @@ foreign import ccall unsafe "__hscore_setmode"
#endif
-- ---------------------------------------------------------------------------
--- isEOF
-
--- | The computation 'isEOF' is identical to 'hIsEOF',
--- except that it works only on 'stdin'.
-
-isEOF :: IO Bool
-isEOF = hIsEOF stdin
-
--- ---------------------------------------------------------------------------
-- Opening and Closing Files
addFilePathToIOError :: String -> FilePath -> IOException -> IOException
@@ -199,7 +189,7 @@ openFile' filepath iomode binary non_blocking = do
-- ---------------------------------------------------------------------------
--- Converting file descriptors to Handles
+-- Converting file descriptors from/to Handles
mkHandleFromFD
:: FD.FD
@@ -283,6 +273,21 @@ fdToHandle fdint = do
mkHandleFromFD fd fd_type fd_str iomode False{-non-block-}
Nothing -- bin mode
+-- | Turn an existing Handle into a file descriptor. This function throws an
+-- IOError if the Handle does not reference a file descriptor.
+handleToFd :: Handle -> IO FD.FD
+handleToFd h = case h of
+ FileHandle _ mv -> do
+ Handle__{haDevice = dev} <- readMVar mv
+ case cast dev of
+ Just fd -> return fd
+ Nothing -> throwErr "not a file descriptor"
+ DuplexHandle{} -> throwErr "not a file handle"
+ where
+ throwErr msg = ioException $ IOError (Just h)
+ InappropriateType "handleToFd" msg Nothing Nothing
+
+
-- ---------------------------------------------------------------------------
-- Are files opened by default in text or binary mode, if the user doesn't
-- specify?
diff --git a/libraries/base/GHC/IO/Handle/Lock.hsc b/libraries/base/GHC/IO/Handle/Lock.hsc
new file mode 100644
index 0000000000..1da0308cdf
--- /dev/null
+++ b/libraries/base/GHC/IO/Handle/Lock.hsc
@@ -0,0 +1,162 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE InterruptibleFFI #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE MultiWayIf #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+module GHC.IO.Handle.Lock (
+ FileLockingNotSupported(..)
+ , LockMode(..)
+ , hLock
+ , hTryLock
+ ) where
+
+#include "HsBaseConfig.h"
+
+#if HAVE_FLOCK
+
+#include <sys/file.h>
+
+import Data.Bits
+import Data.Function
+import Foreign.C.Error
+import Foreign.C.Types
+import GHC.IO.Exception
+import GHC.IO.FD
+import GHC.IO.Handle.FD
+
+#elif defined(mingw32_HOST_OS)
+
+#if defined(i386_HOST_ARCH)
+## define WINDOWS_CCONV stdcall
+#elif defined(x86_64_HOST_ARCH)
+## define WINDOWS_CCONV ccall
+#else
+# error Unknown mingw32 arch
+#endif
+
+#include <windows.h>
+
+import Data.Bits
+import Data.Function
+import Foreign.C.Error
+import Foreign.C.Types
+import Foreign.Marshal.Alloc
+import Foreign.Marshal.Utils
+import GHC.IO.Exception
+import GHC.IO.FD
+import GHC.IO.Handle.FD
+import GHC.Ptr
+import GHC.Real
+import GHC.Windows
+
+#endif
+
+import Data.Functor
+import GHC.Base
+import GHC.Exception
+import GHC.IO.Handle.Types
+import GHC.Show
+
+-- | Exception thrown by 'hLock' on non-Windows platforms that don't support
+-- 'flock'.
+data FileLockingNotSupported = FileLockingNotSupported
+ deriving Show
+
+instance Exception FileLockingNotSupported
+
+-- | Indicates a mode in which a file should be locked.
+data LockMode = SharedLock | ExclusiveLock
+
+-- | If a 'Handle' references a file descriptor, attempt to lock contents of the
+-- underlying file in appropriate mode. If the file is already locked in
+-- incompatible mode, this function blocks until the lock is established. The
+-- lock is automatically released upon closing a 'Handle'.
+--
+-- Things to be aware of:
+--
+-- 1) This function may block inside a C call. If it does, in order to be able
+-- to interrupt it with asynchronous exceptions and/or for other threads to
+-- continue working, you MUST use threaded version of the runtime system.
+--
+-- 2) The implementation uses 'LockFileEx' on Windows and 'flock' otherwise,
+-- hence all of their caveats also apply here.
+--
+-- 3) On non-Windows plaftorms that don't support 'flock' (e.g. Solaris) this
+-- function throws 'FileLockingNotImplemented'. We deliberately choose to not
+-- provide fcntl based locking instead because of its broken semantics.
+--
+-- @since 4.10.0.0
+hLock :: Handle -> LockMode -> IO ()
+hLock h mode = void $ lockImpl h "hLock" mode True
+
+-- | Non-blocking version of 'hLock'.
+--
+-- @since 4.10.0.0
+hTryLock :: Handle -> LockMode -> IO Bool
+hTryLock h mode = lockImpl h "hTryLock" mode False
+
+----------------------------------------
+
+#if HAVE_FLOCK
+
+lockImpl :: Handle -> String -> LockMode -> Bool -> IO Bool
+lockImpl h ctx mode block = do
+ FD{fdFD = fd} <- handleToFd h
+ let flags = cmode .|. (if block then 0 else #{const LOCK_NB})
+ fix $ \retry -> c_flock fd flags >>= \case
+ 0 -> return True
+ _ -> getErrno >>= \errno -> if
+ | not block && errno == eWOULDBLOCK -> return False
+ | errno == eINTR -> retry
+ | otherwise -> ioException $ errnoToIOError ctx errno (Just h) Nothing
+ where
+ cmode = case mode of
+ SharedLock -> #{const LOCK_SH}
+ ExclusiveLock -> #{const LOCK_EX}
+
+foreign import ccall interruptible "flock"
+ c_flock :: CInt -> CInt -> IO CInt
+
+#elif defined(mingw32_HOST_OS)
+
+lockImpl :: Handle -> String -> LockMode -> Bool -> IO Bool
+lockImpl h ctx mode block = do
+ FD{fdFD = fd} <- handleToFd h
+ wh <- throwErrnoIf (== iNVALID_HANDLE_VALUE) ctx $ c_get_osfhandle fd
+ allocaBytes sizeof_OVERLAPPED $ \ovrlpd -> do
+ fillBytes ovrlpd (fromIntegral sizeof_OVERLAPPED) 0
+ let flags = cmode .|. (if block then 0 else #{const LOCKFILE_FAIL_IMMEDIATELY})
+ -- We want to lock the whole file without looking up its size to be
+ -- consistent with what flock does. According to documentation of LockFileEx
+ -- "locking a region that goes beyond the current end-of-file position is
+ -- not an error", however e.g. Windows 10 doesn't accept maximum possible
+ -- value (a pair of MAXDWORDs) for mysterious reasons. Work around that by
+ -- leaving the highest bit set to 0.
+ fix $ \retry -> c_LockFileEx wh flags 0 0xffffffff 0x7fffffff ovrlpd >>= \case
+ True -> return True
+ False -> getLastError >>= \err -> if
+ | not block && err == #{const ERROR_LOCK_VIOLATION} -> return False
+ | err == #{const ERROR_OPERATION_ABORTED} -> retry
+ | otherwise -> failWith ctx err
+ where
+ sizeof_OVERLAPPED = #{size OVERLAPPED}
+
+ cmode = case mode of
+ SharedLock -> 0
+ ExclusiveLock -> #{const LOCKFILE_EXCLUSIVE_LOCK}
+
+-- https://msdn.microsoft.com/en-us/library/aa297958.aspx
+foreign import ccall unsafe "_get_osfhandle"
+ c_get_osfhandle :: CInt -> IO HANDLE
+
+-- https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203.aspx
+foreign import WINDOWS_CCONV interruptible "LockFileEx"
+ c_LockFileEx :: HANDLE -> DWORD -> DWORD -> DWORD -> DWORD -> Ptr () -> IO BOOL
+
+#else
+
+-- | No-op implementation.
+lockImpl :: Handle -> String -> LockMode -> Bool -> IO Bool
+lockImpl _ _ _ _ = throwIO FileLockingNotSupported
+
+#endif