Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix overly long mtime change delay calibration with HFS+. #4231

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cabal/Cabal.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ test-suite unit-tests
containers,
directory,
filepath,
time,
tasty,
tasty-hunit,
tasty-quickcheck,
Expand Down
26 changes: 20 additions & 6 deletions Cabal/Distribution/Compat/Time.hs
Original file line number Diff line number Diff line change
Expand Up @@ -184,19 +184,33 @@ getCurTime = posixTimeToModTime `fmap` getPOSIXTime -- Uses 'gettimeofday'.
calibrateMtimeChangeDelay :: IO (Int, Int)
calibrateMtimeChangeDelay =
withTempDirectory silent "." "calibration-" $ \dir -> do
let fileName = dir </> "probe"
mtimes <- for [1..25] $ \(i::Int) -> time $ do
mtimes <- sampleMtimes (dir </> "probe")
let mtimeChange = maximum mtimes
mtimeChange' = min 1000000 $ (max 10000 mtimeChange) * 2
return (mtimeChange, mtimeChange')
where

sampleMtimes :: FilePath -> IO [Int]
sampleMtimes fileName = do
mtime0 <- sampleMtime 1 fileName
-- Some filesystems (e.g. HFS+ on Mac) store mtimes with 1
-- second resolution. Bail out directly instead of spinning in
-- that case.
if (mtime0 >= 1000000) || (1000000 - mtime0 <= 10000)
then return [mtime0]
else do mtimes <- for [2..25] $ \(i::Int) -> sampleMtime i fileName
return (mtime0:mtimes)

sampleMtime :: Int -> FilePath -> IO Int
sampleMtime i fileName = time $ do
writeFile fileName $ show i
t0 <- getModTime fileName
let spin j = do
writeFile fileName $ show (i,j)
t1 <- getModTime fileName
unless (t0 < t1) (spin $ j + 1)
spin (0::Int)
let mtimeChange = maximum mtimes
mtimeChange' = min 1000000 $ (max 10000 mtimeChange) * 2
return (mtimeChange, mtimeChange')
where

time :: IO () -> IO Int
time act = do
t0 <- getCurrentTime
Expand Down
17 changes: 15 additions & 2 deletions Cabal/tests/UnitTests/Distribution/Compat/Time.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{-# LANGUAGE BangPatterns #-}
module UnitTests.Distribution.Compat.Time (tests) where

import Control.Concurrent (threadDelay)
import Data.Time (diffUTCTime, getCurrentTime)
import System.FilePath

import Distribution.Simple.Utils (withTempDirectory)
Expand All @@ -13,8 +15,10 @@ import Test.Tasty.HUnit

tests :: Int -> [TestTree]
tests mtimeChange =
[ testCase "getModTime has sub-second resolution" $ getModTimeTest mtimeChange
, testCase "getCurTime works as expected" $ getCurTimeTest mtimeChange
[ testCase "getModTime has expected resolution" $ getModTimeTest mtimeChange
, testCase "getCurTime works as expected" $ getCurTimeTest mtimeChange
, testCase "calibrateMtimeChangeDelay is reasonably fast"
$ calibrateMtimeChangeDelayTest
]

getModTimeTest :: Int -> Assertion
Expand Down Expand Up @@ -47,3 +51,12 @@ getCurTimeTest mtimeChange =
assertBool ("expected current time (" ++ show t1
++ ") to be earlier than file mtime (" ++ show t2 ++ ")")
(t1 < t2)

-- See #4230.
calibrateMtimeChangeDelayTest :: Assertion
calibrateMtimeChangeDelayTest = do
t0 <- getCurrentTime
(!_maxDelay, !_recDelay) <- calibrateMtimeChangeDelay
t1 <- getCurrentTime
assertBool "expected calibrateMtimeChangeDelay to take less than 2 seconds" $
(t1 `diffUTCTime` t0) < 2