@@ -4,11 +4,11 @@ module Unison.Codebase.Watch
4
4
where
5
5
6
6
import Control.Concurrent (threadDelay )
7
+ import Control.Concurrent.STM (STM )
7
8
import Control.Concurrent.STM qualified as STM
8
9
import Control.Exception (MaskingState (.. ))
9
10
import Data.IORef (newIORef , readIORef , writeIORef )
10
11
import Data.Map qualified as Map
11
- import Data.Time (getCurrentTime )
12
12
import Data.Time.Clock (UTCTime , diffUTCTime )
13
13
import GHC.Conc (registerDelay )
14
14
import GHC.IO (unsafeUnmask )
@@ -21,7 +21,7 @@ import UnliftIO.STM (atomically)
21
21
22
22
watchDirectory :: Ki. Scope -> FSNotify. WatchManager -> FilePath -> (FilePath -> Bool ) -> IO (IO (FilePath , Text ))
23
23
watchDirectory scope mgr dir allow = do
24
- eventQueue <- forkDirWatcherThread scope mgr dir allow
24
+ readLatestEvent <- forkDirWatcherThread scope mgr dir allow
25
25
26
26
-- Await an event from the event queue with the following simple debounce logic, which is intended to work around the
27
27
-- 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
40
40
var <- registerDelay 50_000
41
41
(join . atomically . asum)
42
42
[ do
43
- event1 <- STM. readTQueue eventQueue
43
+ event1 <- readLatestEvent
44
44
pure (go event1),
45
45
do
46
46
STM. readTVar var >>= STM. check
47
47
pure (pure event0)
48
48
]
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
54
51
55
52
-- Enhance the previous "await event" action with a small file cache that serves as a second debounce implementation.
56
53
-- 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
74
71
75
72
-- | `forkDirWatcherThread scope mgr dir allow` forks a background thread into `scope` that, using "file watcher
76
73
-- 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 ))
80
82
forkDirWatcherThread scope mgr dir allow = do
81
- queue <- STM. newTQueueIO
83
+ latestEventVar <- STM. newTVarIO Nothing
82
84
83
85
let handler :: Event -> IO ()
84
86
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) ))
87
89
_ -> pure ()
88
90
89
91
-- 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
98
100
stopListening <- unsafeUnmask (FSNotify. watchDir mgr dir (const True ) handler) <|> pure (pure () )
99
101
unsafeUnmask (forever (threadDelay maxBound )) `finally` stopListening
100
102
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
0 commit comments