on master help sign in
Avoid non-termination in runAction (#1938)

* Add more comments to shakeRun * Delete the multiple versions of runActions, since they weren't used and parallel is good enough * Delete runActionsSync entirely * Make sure runAction returns even if shakeRun throws an exception * Remove the callback from shakeRun - it was never used * Fix one last use * More comments

commit04576f086de643388fa2d832fb36cdc5f2a2b118 authorNeil Mitchell <35463327+neil-da@users.noreply.github.com> dateFri 28 Jun 2019, 14:56 (7 years ago) parent16873ed
5 files changed+29−40
src/Development/IDE/Core/FileStore.hs+2−2 src/Development/IDE/Core/OfInterest.hs+1−1 src/Development/IDE/Core/Rules.hs+1−1 src/Development/IDE/Core/Service.hs+20−23 src/Development/IDE/Core/Shake.hs+5−13
src/Development/IDE/Core/FileStore.hs+2−2
@@ -167,7 +167,7 @@ setBufferModified state absFile contents = do
167167 VFSHandle{..} <- getIdeGlobalState state
168168 whenJust setVirtualFileContents $ \set ->
169169 set (filePathToUri' absFile) contents
170− void $ shakeRun state [] (const $ pure ())
170+ void $ shakeRun state []
171171
172172-- | Note that some buffer somewhere has been modified, but don't say what.
173173-- Only valid if the virtual file system was initialised by LSP, as that
@@ -177,4 +177,4 @@ setSomethingModified state = do
177177 VFSHandle{..} <- getIdeGlobalState state
178178 when (isJust setVirtualFileContents) $
179179 fail "setSomethingModified can't be called on this type of VFSHandle"
180− void $ shakeRun state [] (const $ pure ())
180+ void $ shakeRun state []
src/Development/IDE/Core/OfInterest.hs+1−1
@@ -78,4 +78,4 @@ modifyFilesOfInterest state f = do
7878 OfInterestVar var <- getIdeGlobalState state
7979 files <- modifyVar var $ pure . dupe . f
8080 logDebug (ideLogger state) $ "Set files of interest to: " <> T.pack (show $ Set.toList files)
81− void $ shakeRun state [] (const $ pure ())
81+ void $ shakeRun state []
src/Development/IDE/Core/Rules.hs+1−1
@@ -12,7 +12,7 @@
1212module Development.IDE.Core.Rules(
1313 IdeState, GetDependencies(..), GetParsedModule(..), TransitiveDependencies(..),
1414 Priority(..),
15− runAction, runActions, useE, usesE,
15+ runAction, useE, usesE,
1616 toIdeResult, defineNoFile,
1717 mainRule,
1818 getGhcCore,
src/Development/IDE/Core/Service.hs+20−23
@@ -11,20 +11,22 @@
1111module Development.IDE.Core.Service(
1212 getIdeOptions,
1313 IdeState, initialise, shutdown,
14− runAction, runActions,
15− runActionSync, runActionsSync,
14+ runAction,
15+ runActionSync,
1616 writeProfile,
1717 getDiagnostics, unsafeClearDiagnostics,
1818 ideLogger
1919 ) where
2020
2121import Control.Concurrent.Extra
22+import Control.Concurrent.Async
2223import Control.Monad.Except
2324import Development.IDE.Types.Options (IdeOptions(..))
2425import Development.IDE.Core.FileStore
2526import Development.IDE.Core.OfInterest
2627import Development.IDE.Types.Logger
2728import Development.Shake hiding (Diagnostic, Env, newCache)
29+import Data.Either.Extra
2830import qualified Language.Haskell.LSP.Messages as LSP
2931
3032import Development.IDE.Core.Shake
@@ -68,32 +70,27 @@ setProfiling opts shakeOpts =
6870shutdown :: IdeState -> IO ()
6971shutdown = shakeShut
7072
71−-- | Run a single action using the supplied service. See `runActions`
72−-- for more details.
73−runAction :: IdeState -> Action a -> IO a
74−runAction service action = head <$> runActions service [action]
75−
76−-- | Run a list of actions in parallel using the supplied service.
77−-- This will return as soon as the results of the actions are
73+-- This will return as soon as the result of the action is
7874-- available. There might still be other rules running at this point,
7975-- e.g., the ofInterestRule.
80−runActions :: IdeState -> [Action a] -> IO [a]
81−runActions x acts = do
82− var <- newBarrier
83− _ <- shakeRun x acts (signalBarrier var)
84− waitBarrier var
85−
86−-- | This is a synchronous variant of `runAction`. See
87−-- `runActionsSync` of more details.
88−runActionSync :: IdeState -> Action a -> IO a
89−runActionSync s a = head <$> runActionsSync s [a]
90−
91−-- | `runActionsSync` is similar to `runActions` but it will
76+runAction :: IdeState -> Action a -> IO a
77+runAction ide action = do
78+ bar <- newBarrier
79+ res <- shakeRun ide [do v <- action; liftIO $ signalBarrier bar v; return v]
80+ -- shakeRun might throw an exception (either through action or a default rule),
81+ -- in which case action may not complete successfully, and signalBarrier might not be called.
82+ -- Therefore we wait for either res (which propagates the exception) or the barrier.
83+ -- Importantly, if the barrier does finish, cancelling res only kills waiting for the result,
84+ -- it doesn't kill the actual work
85+ fmap fromEither $ race (head <$> res) $ waitBarrier bar
86+
87+
88+-- | `runActionSync` is similar to `runAction` but it will
9289-- wait for all rules (so in particular the `ofInterestRule`) to
9390-- finish running. This is mainly useful in tests, where you want
9491-- to wait for all rules to fire so you can check diagnostics.
95−runActionsSync :: IdeState -> [Action a] -> IO [a]
96−runActionsSync s acts = join $ shakeRun s acts (const $ pure ())
92+runActionSync :: IdeState -> Action a -> IO a
93+runActionSync s act = fmap head $ join $ shakeRun s [act]
9794
9895getIdeOptions :: Action IdeOptions
9996getIdeOptions = do
src/Development/IDE/Core/Shake.hs+5−13
@@ -237,25 +237,17 @@ shakeShut IdeState{..} = withVar shakeAbort $ \stop -> do
237237 stop
238238 shakeClose
239239
240−-- | Spawn immediately, add an action to collect the results syncronously.
241−-- If you are already inside a call to shakeRun that will be aborted with an exception.
242−-- The callback will be fired as soon as the results are available
243−-- even if there are still other rules running while the IO action that is
244−-- being returned will wait for all rules to finish.
245−shakeRun :: IdeState -> [Action a] -> ([a] -> IO ()) -> IO (IO [a])
240+-- | Spawn immediately. If you are already inside a call to shakeRun that will be aborted with an exception.
241+shakeRun :: IdeState -> [Action a] -> IO (IO [a])
246242-- FIXME: If there is already a shakeRun queued up and waiting to send me a kill, I should probably
247243-- not even start, which would make issues with async exceptions less problematic.
248−shakeRun IdeState{shakeExtras=ShakeExtras{..}, ..} acts callback = modifyVar shakeAbort $ \stop -> do
244+shakeRun IdeState{shakeExtras=ShakeExtras{..}, ..} acts = modifyVar shakeAbort $ \stop -> do
249245 (stopTime,_) <- duration stop
250246 logDebug logger $ T.pack $ "Starting shakeRun (aborting the previous one took " ++ showDuration stopTime ++ ")"
251247 bar <- newBarrier
252248 start <- offsetTime
253− let act = do
254− res <- parallel acts
255− liftIO $ callback res
256− pure res
257− thread <- forkFinally (shakeRunDatabaseProfile shakeDb [act]) $ \res -> do
258− signalBarrier bar (mapRight head res)
249+ thread <- forkFinally (shakeRunDatabaseProfile shakeDb acts) $ \res -> do
250+ signalBarrier bar res
259251 runTime <- start
260252 logDebug logger $ T.pack $
261253 "Finishing shakeRun (took " ++ showDuration runTime ++ ", " ++ (if isLeft res then "exception" else "completed") ++ ")"