Skip to content

Commit f77ceff

Browse files
committed
Fix race condition when deleting an entry from the map
1 parent 5bcc205 commit f77ceff

File tree

1 file changed

+36
-9
lines changed

1 file changed

+36
-9
lines changed

ghcide/src/Development/IDE/Core/Debouncer.hs

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,23 +34,50 @@ newAsyncDebouncer = Debouncer . asyncRegisterEvent <$> STM.newIO
3434

3535
-- | Register an event that will fire after the given delay if no other event
3636
-- for the same key gets registered until then.
37-
asyncRegisterEvent :: (Eq k, Hashable k) => STM.Map k (TVar (Seconds, IO())) -> Seconds -> k -> IO () -> IO ()
37+
asyncRegisterEvent
38+
:: (Eq k, Hashable k)
39+
=> STM.Map k (TVar (Maybe (Seconds, IO())))
40+
-> Seconds
41+
-> k
42+
-> IO ()
43+
-> IO ()
3844
asyncRegisterEvent d delay k fire = join $ atomicallyNamed "debouncer - register" $ do
45+
-- The previous TVar for this key, if any
3946
prev <- STM.lookup k d
4047
case prev of
41-
Just v -> writeTVar v (delay, fire) >> return (pure ())
48+
Just v -> do
49+
current <- readTVar v
50+
case current of
51+
-- Not empty, means that there is a thread running the actions
52+
Just _ -> writeTVar v (Just (delay, fire)) >> return (pure ())
53+
-- Empty = no thread. We need to start one for running the action
54+
Nothing -> writeTVar v (Just (delay, fire)) >> return (restart v)
55+
56+
-- No previous TVar, we need to insert one and restart a thread for running the action
4257
Nothing
4358
| delay == 0 -> return fire
4459
| otherwise -> do
45-
var <- newTVar (delay, fire)
60+
var <- newTVar (Just (delay, fire))
4661
STM.insert var k d
47-
return $ void $ async $
62+
return (restart var)
63+
where
64+
-- | Restart a thread to run the action stored in the given TVar
65+
-- Once the action is done, the thread dies.
66+
-- Assumes the Tvar is not empty
67+
restart var =
68+
void $ async $
4869
join $ atomicallyNamed "debouncer - sleep" $ do
49-
(s,act) <- readTVar var
50-
unsafeIOToSTM $ sleep s
51-
return $ do
52-
atomically (STM.delete k d)
53-
act
70+
contents <- readTVar var
71+
case contents of
72+
Nothing -> error "impossible"
73+
Just (s,act) -> do
74+
-- sleep for the given delay
75+
-- If the TVar is written while sleeping,
76+
-- the transaction will restart
77+
unsafeIOToSTM $ sleep s
78+
-- we are done - empty the TVar before exiting
79+
writeTVar var Nothing
80+
return act
5481

5582
-- | Debouncer used in the DAML CLI compiler that emits events immediately.
5683
noopDebouncer :: Debouncer k

0 commit comments

Comments
 (0)