Skip to content

Commit fc48abb

Browse files
committed
Use async instead of forkIO
1 parent e291574 commit fc48abb

File tree

7 files changed

+51
-54
lines changed

7 files changed

+51
-54
lines changed

library/Neovim/Context.hs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ module Neovim.Context (
1919
FunctionMapEntry,
2020
mkFunctionMap,
2121
runNeovim,
22-
forkNeovim,
2322
err,
2423
errOnInvalidResult,
2524
restart,
@@ -42,8 +41,7 @@ module Neovim.Context (
4241

4342
import Neovim.Classes
4443
import Neovim.Context.Internal (FunctionMap, FunctionMapEntry,
45-
Neovim, forkNeovim,
46-
mkFunctionMap,
44+
Neovim, mkFunctionMap,
4745
newUniqueFunctionName, runNeovim)
4846
import Neovim.Exceptions (NeovimException (..))
4947

library/Neovim/Context/Internal.hs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import Neovim.Plugin.Classes
2626
import Neovim.Plugin.IPC (SomeMessage)
2727

2828
import Control.Applicative
29-
import Control.Concurrent (MVar, ThreadId, forkIO)
3029
import Control.Exception (ArithException, ArrayException,
3130
ErrorCall, PatternMatchFail)
3231
import Control.Monad.Except
@@ -119,21 +118,6 @@ runNeovimInternal f r (Neovim a) =
119118
(Right <$> f res) `catches` exceptionHandlers
120119

121120

122-
-- | Fork a neovim thread with the given custom config value and a custom
123-
-- state. The result of the thread is discarded and only the 'ThreadId' is
124-
-- returend immediately.
125-
-- FIXME This function is pretty much unused and mayhave undesired effects,
126-
-- namely that you cannot register autocmds in the forked thread.
127-
forkNeovim :: NFData a => iEnv -> Neovim iEnv a -> Neovim env ThreadId
128-
forkNeovim r a = do
129-
cfg <- ask'
130-
let threadConfig = cfg
131-
{ pluginSettings = Nothing -- <- slightly problematic
132-
, customConfig = r
133-
}
134-
liftIO . forkIO . void $ runNeovim threadConfig a
135-
136-
137121
-- | Create a new unique function name. To prevent possible name clashes, digits
138122
-- are stripped from the given suffix.
139123
newUniqueFunctionName :: Neovim env FunctionName

library/Neovim/Debug.hs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,15 @@ import Neovim.Main (CommandLineOptions (..),
3333
runPluginProvider)
3434
import Neovim.RPC.Common (RPCConfig)
3535

36-
import Control.Concurrent
37-
import Control.Concurrent.STM
3836
import Control.Monad
3937
import qualified Data.Map as Map
4038
import Foreign.Store
4139
import System.IO (stdout)
4240
import Text.PrettyPrint.ANSI.Leijen hiding ((<$>))
4341
import qualified Text.PrettyPrint.ANSI.Leijen as Pretty
42+
import UnliftIO.Async (Async, async, cancel)
43+
import UnliftIO.STM
44+
import UnliftIO.Concurrent (putMVar, takeMVar)
4445

4546
import Prelude
4647

@@ -68,7 +69,7 @@ debug env a = disableLogger $ do
6869
(cfg { Internal.customConfig = env, Internal.pluginSettings = Nothing })
6970
a
7071

71-
mapM_ killThread tids
72+
mapM_ cancel tids
7273
return res
7374

7475
_ ->
@@ -110,7 +111,7 @@ debug' a = debug () a
110111
--
111112
develMain
112113
:: Maybe NeovimConfig
113-
-> IO (Either Doc [ThreadId])
114+
-> IO (Either Doc [Async ()])
114115
develMain mcfg = lookupStore 0 >>= \case
115116
Nothing -> do
116117
x <- disableLogger $
@@ -126,9 +127,8 @@ develMain mcfg = lookupStore 0 >>= \case
126127
return $ Left e
127128

128129
Internal.InitSuccess -> do
129-
transitionHandlerThread <- forkIO $ do
130-
myTid <- myThreadId
131-
void $ transitionHandler (myTid:tids) cfg
130+
transitionHandlerThread <- async $ do
131+
void $ transitionHandler (tids) cfg
132132
return $ Right (transitionHandlerThread:tids)
133133

134134
Internal.Quit -> do
@@ -139,7 +139,7 @@ develMain mcfg = lookupStore 0 >>= \case
139139
Just x ->
140140
deleteStore x
141141

142-
mapM_ killThread tids
142+
mapM_ cancel tids
143143
return . Left $ text "Quit develMain"
144144

145145
_ ->
@@ -155,7 +155,7 @@ quitDevelMain cfg = putMVar (Internal.transitionTo cfg) Internal.Quit
155155
restartDevelMain
156156
:: Internal.Config RPCConfig
157157
-> Maybe NeovimConfig
158-
-> IO (Either Doc [ThreadId])
158+
-> IO (Either Doc [Async ()])
159159
restartDevelMain cfg mcfg = do
160160
quitDevelMain cfg
161161
develMain mcfg

library/Neovim/Main.hs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import Data.Monoid
3333
import Options.Applicative
3434
import System.IO (stdin, stdout)
3535
import System.SetEnv
36+
import UnliftIO.Async (Async, async, cancel)
3637

3738
import Prelude
3839
import System.Environment
@@ -133,7 +134,7 @@ neovim =
133134
-- 'Internal.Config' with the custom field set to 'RPCConfig'. These information
134135
-- can be used to properly clean up a session and then do something else.
135136
-- The transition handler is first called after the plugin provider has started.
136-
type TransitionHandler a = [ThreadId] -> Internal.Config RPCConfig -> IO a
137+
type TransitionHandler a = [Async ()] -> Internal.Config RPCConfig -> IO a
137138

138139

139140
-- | This main functions can be used to create a custom executable without
@@ -179,11 +180,11 @@ runPluginProvider os mcfg transitionHandler mDyreParams = case (hostPort os, uni
179180

180181
conf <- Internal.newConfig (pure (providerName os)) newRPCConfig
181182

182-
ehTid <- forkIO $ runEventHandler
183+
ehTid <- async $ runEventHandler
183184
evHandlerHandle
184185
conf { Internal.pluginSettings = Nothing }
185186

186-
srTid <- forkIO $ runSocketReader sockreaderHandle conf
187+
srTid <- async $ runSocketReader sockreaderHandle conf
187188

188189
ghcEnv <- forM ["GHC_PACKAGE_PATH","CABAL_SANDBOX_CONFIG"] $ \var -> do
189190
val <- lookupEnv var
@@ -216,7 +217,7 @@ finishDyre threads cfg = takeMVar (Internal.transitionTo cfg) >>= \case
216217

217218
Internal.Restart -> do
218219
debugM logger "Trying to restart nvim-hs"
219-
mapM_ killThread threads
220+
mapM_ cancel threads
220221
Dyre.relaunchMaster Nothing
221222

222223
Internal.Failure e ->

library/Neovim/Plugin.hs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@ import qualified Neovim.Plugin.Startup as Plugin
4141
import Neovim.RPC.FunctionCall
4242

4343
import Control.Applicative
44-
import Control.Concurrent (ThreadId, forkIO)
45-
import Control.Concurrent.STM
4644
import Control.Monad (foldM, void)
4745
import Control.Monad.Trans.Resource hiding (register)
4846
import Data.ByteString (ByteString)
@@ -56,6 +54,8 @@ import Data.Traversable (forM)
5654
import System.Log.Logger
5755
import Text.PrettyPrint.ANSI.Leijen (Doc)
5856
import UnliftIO.Exception (SomeException, try)
57+
import UnliftIO.Async (Async, async)
58+
import UnliftIO.STM
5959

6060
import Prelude
6161

@@ -69,12 +69,12 @@ type StartupConfig = Plugin.StartupConfig NeovimConfig
6969

7070
startPluginThreads :: Internal.Config StartupConfig
7171
-> [Neovim StartupConfig NeovimPlugin]
72-
-> IO (Either Doc ([FunctionMapEntry],[ThreadId]))
72+
-> IO (Either Doc ([FunctionMapEntry],[Async ()]))
7373
startPluginThreads cfg = runNeovimInternal return cfg . foldM go ([], [])
7474
where
75-
go :: ([FunctionMapEntry], [ThreadId])
75+
go :: ([FunctionMapEntry], [Async ()])
7676
-> Neovim StartupConfig NeovimPlugin
77-
-> Neovim StartupConfig ([FunctionMapEntry], [ThreadId])
77+
-> Neovim StartupConfig ([FunctionMapEntry], [Async ()])
7878
go (es, tids) iop = do
7979
NeovimPlugin p <- iop
8080
(es', tid) <- registerStatefulFunctionality p
@@ -262,17 +262,17 @@ addAutocmd event (opts@AutocmdOptions{..}) f = do
262262
-- the corresponding 'TQueue's (i.e. communication channels).
263263
registerStatefulFunctionality
264264
:: Plugin env
265-
-> Neovim anyEnv ([FunctionMapEntry], ThreadId)
265+
-> Neovim anyEnv ([FunctionMapEntry], Async ())
266266
registerStatefulFunctionality (Plugin { environment = env, exports = fs }) = do
267-
q <- liftIO newTQueueIO
267+
messageQueue <- liftIO newTQueueIO
268268
route <- liftIO $ newTVarIO Map.empty
269269

270270
cfg <- Internal.ask'
271271

272272
let startupConfig = cfg
273273
{ Internal.customConfig = env
274274
, Internal.pluginSettings = Just $ Internal.StatefulSettings
275-
(registerPlugin (\_ -> return ())) q route
275+
(registerPlugin (\_ -> return ())) messageQueue route
276276
}
277277
res <- liftIO . runNeovimInternal return startupConfig . forM fs $ \f ->
278278
registerFunctionality (getDescription f) (getFunction f)
@@ -283,11 +283,11 @@ registerStatefulFunctionality (Plugin { environment = env, exports = fs }) = do
283283
let pluginThreadConfig = cfg
284284
{ Internal.customConfig = env
285285
, Internal.pluginSettings = Just $ Internal.StatefulSettings
286-
(registerPlugin registerInGlobalFunctionMap) q route
286+
(registerPlugin registerInGlobalFunctionMap) messageQueue route
287287
}
288288

289-
tid <- liftIO . forkIO . void . runNeovimInternal return pluginThreadConfig $ do
290-
listeningThread q route
289+
tid <- liftIO . async . void . runNeovim pluginThreadConfig $ do
290+
listeningThread messageQueue route
291291

292292
return (map fst es, tid) -- NB: dropping release functions/keys here
293293

library/Neovim/RPC/SocketReader.hs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import Neovim.RPC.Common
3131
import Neovim.RPC.FunctionCall
3232

3333
import Control.Applicative
34-
import Control.Concurrent (forkIO)
3534
import Control.Concurrent.STM
3635
import Control.Monad (void)
3736
import Control.Monad.Trans.Class (lift)
@@ -45,6 +44,8 @@ import Data.Monoid
4544
import qualified Data.Serialize (get)
4645
import System.IO (Handle)
4746
import System.Log.Logger
47+
import UnliftIO.Async (async, race)
48+
import UnliftIO.Concurrent (threadDelay)
4849

4950
import Prelude
5051

@@ -98,6 +99,7 @@ handleResponse i result = do
9899
atomically' . modifyTVar' answerMap $ Map.delete i
99100
atomically' $ putTMVar reply result
100101

102+
101103
-- | Act upon the received request or notification. The main difference between
102104
-- the two is that a notification does not generate a reply. The distinction
103105
-- between those two cases is done via the first paramater which is 'Maybe' the
@@ -106,14 +108,21 @@ handleRequestOrNotification :: Maybe Int64 -> FunctionName -> [Object]
106108
-> ConduitT a Void SocketHandler ()
107109
handleRequestOrNotification mi m params = do
108110
cfg <- lift Internal.ask'
109-
void . liftIO . forkIO $ handle cfg
111+
void . liftIO . async $ race logTimeout (handle cfg)
112+
return ()
110113

111114
where
112115
lookupFunction
113116
:: TMVar Internal.FunctionMap
114117
-> STM (Maybe (FunctionalityDescription, Internal.FunctionType))
115118
lookupFunction funMap = Map.lookup m <$> readTMVar funMap
116119

120+
logTimeout :: IO ()
121+
logTimeout = do
122+
let seconds = 1000 * 1000
123+
threadDelay (10 * seconds)
124+
debugM logger $ "Cancelled another action before it was finished"
125+
117126
handle :: Internal.Config RPCConfig -> IO ()
118127
handle rpc = atomically (lookupFunction (Internal.globalFunctionMap rpc)) >>= \case
119128

library/Neovim/Test.hs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ import Neovim.RPC.Common (newRPCConfig, RPCConfig)
2121
import Neovim.RPC.EventHandler (runEventHandler)
2222
import Neovim.RPC.SocketReader (runSocketReader)
2323

24-
import Control.Concurrent
25-
import Control.Concurrent.STM (atomically, putTMVar)
2624
import Control.Monad.Reader (runReaderT)
2725
import Control.Monad.Trans.Resource (runResourceT)
2826
import GHC.IO.Exception (ioe_filename)
@@ -32,6 +30,9 @@ import System.IO (Handle)
3230
import System.Process
3331
import Text.PrettyPrint.ANSI.Leijen (red, text, putDoc, (<$$>))
3432
import UnliftIO.Exception
33+
import UnliftIO.STM (atomically, putTMVar)
34+
import UnliftIO.Async (async, cancel)
35+
import UnliftIO.Concurrent (threadDelay)
3536

3637

3738
-- | Type synonym for 'Word'.
@@ -55,7 +56,7 @@ testWithEmbeddedNeovim file timeout r (Internal.Neovim a) =
5556
runTest `catch` catchIfNvimIsNotOnPath
5657
where
5758
runTest = do
58-
(_, _, ph, cfg) <- startEmbeddedNvim file timeout
59+
(_, _, ph, cfg, cleanUp) <- startEmbeddedNvim file timeout
5960

6061
let testCfg = Internal.retypeConfig r cfg
6162

@@ -65,14 +66,16 @@ testWithEmbeddedNeovim file timeout r (Internal.Neovim a) =
6566
-- result of the operation since neovim cannot send a result if it
6667
-- has quit.
6768
let Internal.Neovim q = vim_command "qa!"
68-
void . forkIO . void $ runReaderT (runResourceT q) testCfg
69+
testRunner <- async . void $ runReaderT (runResourceT q) testCfg
6970

7071
waitForProcess ph >>= \case
7172
ExitFailure i ->
7273
fail $ "Neovim returned with an exit status of: " ++ show i
7374

7475
ExitSuccess ->
7576
return ()
77+
cancel testRunner
78+
cleanUp
7679

7780

7881
catchIfNvimIsNotOnPath :: IOException -> IO ()
@@ -87,7 +90,7 @@ catchIfNvimIsNotOnPath e = case ioe_filename e of
8790
startEmbeddedNvim
8891
:: Maybe FilePath
8992
-> Seconds
90-
-> IO (Handle, Handle, ProcessHandle, Internal.Config RPCConfig)
93+
-> IO (Handle, Handle, ProcessHandle, Internal.Config RPCConfig, IO ())
9194
startEmbeddedNvim file (Seconds timeout) = do
9295
args <- case file of
9396
Nothing ->
@@ -107,21 +110,23 @@ startEmbeddedNvim file (Seconds timeout) = do
107110

108111
cfg <- Internal.newConfig (pure Nothing) newRPCConfig
109112

110-
void . forkIO $ runSocketReader
113+
socketReader <- async . void $ runSocketReader
111114
hout
112115
(cfg { Internal.pluginSettings = Nothing })
113116

114-
void . forkIO $ runEventHandler
117+
eventHandler <- async . void $ runEventHandler
115118
hin
116119
(cfg { Internal.pluginSettings = Nothing })
117120

118121
atomically $ putTMVar
119122
(Internal.globalFunctionMap cfg)
120123
(Internal.mkFunctionMap [])
121124

122-
void . forkIO $ do
125+
timeoutAsync <- async . void $ do
123126
threadDelay $ (fromIntegral timeout) * 1000 * 1000
124127
getProcessExitCode ph >>= maybe (terminateProcess ph) (\_ -> return ())
125128

126-
return (hin, hout, ph, cfg)
129+
let cleanUp = mapM_ cancel [socketReader, eventHandler, timeoutAsync]
130+
131+
return (hin, hout, ph, cfg, cleanUp)
127132

0 commit comments

Comments
 (0)