Skip to content

Commit 4f9f42f

Browse files
authored
Merge pull request #5729 from unisonweb/file-watch-tweak
2 parents cdd2e61 + a69af08 commit 4f9f42f

File tree

2 files changed

+25
-17
lines changed

2 files changed

+25
-17
lines changed

unison-cli/src/Unison/Codebase/Watch.hs

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ module Unison.Codebase.Watch
44
where
55

66
import Control.Concurrent (threadDelay)
7+
import Control.Concurrent.STM (STM)
78
import Control.Concurrent.STM qualified as STM
89
import Control.Exception (MaskingState (..))
910
import Data.IORef (newIORef, readIORef, writeIORef)
1011
import Data.Map qualified as Map
11-
import Data.Time (getCurrentTime)
1212
import Data.Time.Clock (UTCTime, diffUTCTime)
1313
import GHC.Conc (registerDelay)
1414
import GHC.IO (unsafeUnmask)
@@ -21,7 +21,7 @@ import UnliftIO.STM (atomically)
2121

2222
watchDirectory :: Ki.Scope -> FSNotify.WatchManager -> FilePath -> (FilePath -> Bool) -> IO (IO (FilePath, Text))
2323
watchDirectory scope mgr dir allow = do
24-
eventQueue <- forkDirWatcherThread scope mgr dir allow
24+
readLatestEvent <- forkDirWatcherThread scope mgr dir allow
2525

2626
-- Await an event from the event queue with the following simple debounce logic, which is intended to work around the
2727
-- tendency for modern editors to create a flurry of rapid filesystem events when a file is saved:
@@ -40,17 +40,14 @@ watchDirectory scope mgr dir allow = do
4040
var <- registerDelay 50_000
4141
(join . atomically . asum)
4242
[ do
43-
event1 <- STM.readTQueue eventQueue
43+
event1 <- readLatestEvent
4444
pure (go event1),
4545
do
4646
STM.readTVar var >>= STM.check
4747
pure (pure event0)
4848
]
49-
event@(_, eventTime) <- atomically (STM.readTQueue eventQueue)
50-
now <- getCurrentTime
51-
if (now `diffUTCTime` eventTime) <= 1.0
52-
then go event
53-
else awaitEvent0
49+
event <- atomically readLatestEvent
50+
go event
5451

5552
-- Enhance the previous "await event" action with a small file cache that serves as a second debounce implementation.
5653
-- We keep in memory the file contents of previously-saved files, so that we can avoid emitting events for files that
@@ -74,16 +71,21 @@ watchDirectory scope mgr dir allow = do
7471

7572
-- | `forkDirWatcherThread scope mgr dir allow` forks a background thread into `scope` that, using "file watcher
7673
-- manager" `mgr` (just a boilerplate argument the caller is responsible for creating), watches directory `dir` for
77-
-- all "added" and "modified" filesystem events that occur on files that pass the `allow` predicate. It returns a queue
78-
-- of such event that is (obviously) meant to be read or flushed, never written.
79-
forkDirWatcherThread :: Ki.Scope -> FSNotify.WatchManager -> FilePath -> (FilePath -> Bool) -> IO (STM.TQueue (FilePath, UTCTime))
74+
-- all "added" and "modified" filesystem events that occur on files that pass the `allow` predicate. It returns an STM
75+
-- action that reads (and clears) the latest event, blocking if one isn't available.
76+
forkDirWatcherThread ::
77+
Ki.Scope ->
78+
FSNotify.WatchManager ->
79+
FilePath ->
80+
(FilePath -> Bool) ->
81+
IO (STM (FilePath, UTCTime))
8082
forkDirWatcherThread scope mgr dir allow = do
81-
queue <- STM.newTQueueIO
83+
latestEventVar <- STM.newTVarIO Nothing
8284

8385
let handler :: Event -> IO ()
8486
handler = \case
85-
Added fp t FSNotify.IsFile | allow fp -> atomically (STM.writeTQueue queue (fp, t))
86-
Modified fp t FSNotify.IsFile | allow fp -> atomically (STM.writeTQueue queue (fp, t))
87+
Added fp t FSNotify.IsFile | allow fp -> atomically (STM.writeTVar latestEventVar (Just (fp, t)))
88+
Modified fp t FSNotify.IsFile | allow fp -> atomically (STM.writeTVar latestEventVar (Just (fp, t)))
8789
_ -> pure ()
8890

8991
-- A bit of a "one too many threads" situation but there's not much we can easily do about it. The `fsnotify` API
@@ -98,4 +100,11 @@ forkDirWatcherThread scope mgr dir allow = do
98100
stopListening <- unsafeUnmask (FSNotify.watchDir mgr dir (const True) handler) <|> pure (pure ())
99101
unsafeUnmask (forever (threadDelay maxBound)) `finally` stopListening
100102

101-
pure queue
103+
let readLatestEvent =
104+
STM.readTVar latestEventVar >>= \case
105+
Nothing -> STM.retry
106+
Just event -> do
107+
STM.writeTVar latestEventVar Nothing
108+
pure event
109+
110+
pure readLatestEvent

unison-cli/src/Unison/CommandLine/Main.hs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,7 @@ main dir welcome ppIds initialInputs runtime sbRuntime nRuntime codebase serverB
157157
_ <- Ki.fork scope (Codebase.expectProjectBranchRoot codebase ppIds.project ppIds.branch)
158158
-- IOSource takes a while to compile, we should start compiling it on startup
159159
_ <- Ki.fork scope (IO.evaluate IOSource.typecheckedFile)
160-
-- Fork the file watcher thread, which returns an IO action we can call to get one filesystem event (automatically
161-
-- first tossing all that have accumulated since the last call)
160+
-- Fork the file watcher thread, which returns an IO action we can call to get one filesystem event.
162161
awaitFileEvent <- do
163162
(fmap . fmap)
164163
(\(file, contents) -> UnisonFileChanged (Text.pack file) contents)

0 commit comments

Comments
 (0)