Skip to content
This repository was archived by the owner on Oct 7, 2020. It is now read-only.

Commit f99b6f5

Browse files
committed
Merge pull request #72 from alanz/async-dispatch-1
Async dispatch
2 parents dcf8746 + 75c6ed7 commit f99b6f5

File tree

15 files changed

+262
-134
lines changed

15 files changed

+262
-134
lines changed

app/MainHie.hs

+6-4
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44

55
module Main where
66

7-
import Control.Monad
8-
import Control.Monad.Trans.Maybe
97
import Control.Concurrent
8+
import Control.Concurrent.STM.TChan
109
import Control.Exception
1110
import Control.Logging
11+
import Control.Monad
12+
import Control.Monad.STM
13+
import Control.Monad.Trans.Maybe
1214
import qualified Data.Map as Map
1315
import Data.Version (showVersion)
1416
import Development.GitRev (gitCommitCount)
@@ -104,7 +106,7 @@ run opts = do
104106
getUserHomeDirectory >>= mapM_ setCurrentDirectory
105107

106108
logm $ "run entered for HIE " ++ version
107-
cin <- newChan :: IO (Chan ChannelRequest)
109+
cin <- atomically newTChan :: IO (TChan ChannelRequest)
108110

109111
-- log $ T.pack $ "replPluginInfo:" ++ show replPluginInfo
110112

@@ -138,6 +140,6 @@ getUserHomeDirectory = do
138140
-- pass the request through to the main event dispatcher, and listen on the
139141
-- reply channel for the response, which should go back to the IDE, using
140142
-- whatever it takes.
141-
listener :: Chan ChannelRequest -> IO ()
143+
listener :: TChan ChannelRequest -> IO ()
142144
listener = assert False undefined
143145

haskell-ide-engine.cabal

+5
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ library
4949
, pipes-attoparsec >= 0.5
5050
, pipes-bytestring
5151
, pipes-parse
52+
, stm
5253
, servant-server
5354
, text
5455
, time
@@ -79,6 +80,7 @@ executable hie
7980
, logging
8081
, optparse-applicative
8182
, optparse-simple
83+
, stm
8284
, text
8385
, time
8486
, transformers
@@ -105,7 +107,10 @@ test-suite haskell-ide-test
105107
, haskell-ide-plugin-ghcmod
106108
, hspec
107109
, logging
110+
, stm
108111
, text
112+
, transformers
113+
, unordered-containers
109114
ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall
110115
default-language: Haskell2010
111116

haskell-ide-example-plugin2/Haskell/Ide/ExamplePlugin2.hs

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ example2Descriptor = PluginDescriptor
4545
-- ---------------------------------------------------------------------
4646

4747
sayHelloCmd :: CommandFunc T.Text
48-
sayHelloCmd _ _ = return (IdeResponseOk sayHello)
48+
sayHelloCmd = CmdSync $ \_ _ -> return (IdeResponseOk sayHello)
4949

5050
sayHelloToCmd :: CommandFunc T.Text
51-
sayHelloToCmd _ req = do
51+
sayHelloToCmd = CmdSync $ \_ req -> do
5252
case Map.lookup "name" (ideParams req) of
5353
Nothing -> return $ missingParameter "name"
5454
Just (ParamTextP n) -> do

haskell-ide-plugin-api/Haskell/Ide/Engine/PluginDescriptor.hs

+9-1
Original file line numberDiff line numberDiff line change
@@ -256,9 +256,17 @@ class (Monad m) => HasIdeState m where
256256
-- descriptor, and has all the required parameters. Where a command has only one
257257
-- allowed context the supplied context list does not add much value, but allows
258258
-- easy case checking when multiple contexts are supported.
259-
type CommandFunc resp = forall m. (MonadIO m,GHC.GhcMonad m,HasIdeState m)
259+
data CommandFunc resp = CmdSync (SyncCommandFunc resp)
260+
| CmdAsync (AsyncCommandFunc resp)
261+
-- ^ Note: does not forkIO, the command must decide when
262+
-- to do this.
263+
264+
type SyncCommandFunc resp = forall m. (MonadIO m,GHC.GhcMonad m,HasIdeState m)
260265
=> [AcceptedContext] -> IdeRequest -> m (IdeResponse resp)
261266

267+
type AsyncCommandFunc resp = forall m. (MonadIO m,GHC.GhcMonad m,HasIdeState m)
268+
=> (IdeResponse resp -> IO ()) -> [AcceptedContext] -> IdeRequest -> m ()
269+
262270
-- ---------------------------------------------------------------------
263271
-- ValidResponse instances
264272

haskell-ide-plugin-ghcmod/Haskell/Ide/GhcModPlugin.hs

+5-5
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ ghcmodDescriptor = PluginDescriptor
9898
-- ---------------------------------------------------------------------
9999

100100
checkCmd :: CommandFunc String
101-
checkCmd _ctxs req = do
101+
checkCmd = CmdSync $ \_ctxs req -> do
102102
case getParams (IdFile "file" :& RNil) req of
103103
Left err -> return err
104104
Right (ParamFile fileName :& RNil) -> do
@@ -111,7 +111,7 @@ checkCmd _ctxs req = do
111111
-- TODO: Must define a directory to base the search from, to be able to resolve
112112
-- the project root.
113113
findCmd :: CommandFunc String
114-
findCmd _ctxs req = do
114+
findCmd = CmdSync $ \_ctxs req -> do
115115
case getParams (IdText "symbol" :& RNil) req of
116116
Left err -> return err
117117
Right (ParamText _symbol :& RNil) -> do
@@ -126,7 +126,7 @@ findCmd _ctxs req = do
126126
-- ---------------------------------------------------------------------
127127

128128
lintCmd :: CommandFunc String
129-
lintCmd _ctxs req = do
129+
lintCmd = CmdSync $ \_ctxs req -> do
130130
case getParams (IdFile "file" :& RNil) req of
131131
Left err -> return err
132132
Right (ParamFile fileName :& RNil) -> do
@@ -137,7 +137,7 @@ lintCmd _ctxs req = do
137137
-- ---------------------------------------------------------------------
138138

139139
infoCmd :: CommandFunc String
140-
infoCmd _ctxs req = do
140+
infoCmd = CmdSync $ \_ctxs req -> do
141141
case getParams (IdFile "file" :& IdText "expr" :& RNil) req of
142142
Left err -> return err
143143
Right (ParamFile fileName :& ParamText expr :& RNil) -> do
@@ -148,7 +148,7 @@ infoCmd _ctxs req = do
148148
-- ---------------------------------------------------------------------
149149

150150
typeCmd :: CommandFunc String
151-
typeCmd _ctxs req = do
151+
typeCmd = CmdSync $ \_ctxs req -> do
152152
case getParams (IdFile "file" :& IdPos "start_pos" :& RNil) req of
153153
Left err -> return err
154154
Right (ParamFile fileName :& ParamPos (r,c) :& RNil) -> do

haskell-ide-plugin-hare/Haskell/Ide/HaRePlugin.hs

+6-6
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ hareDescriptor = PluginDescriptor
8787
-- ---------------------------------------------------------------------
8888

8989
demoteCmd :: CommandFunc [FilePath]
90-
demoteCmd _ctxs req = do
90+
demoteCmd = CmdSync $ \_ctxs req -> do
9191
case getParams (IdFile "file" :& IdPos "start_pos" :& RNil) req of
9292
Left err -> return err
9393
Right (ParamFile fileName :& ParamPos pos :& RNil) -> do
@@ -106,7 +106,7 @@ demoteCmd _ctxs req = do
106106
-- ---------------------------------------------------------------------
107107

108108
dupdefCmd :: CommandFunc [FilePath]
109-
dupdefCmd _ctxs req = do
109+
dupdefCmd = CmdSync $ \_ctxs req -> do
110110
case getParams (IdFile "file" :& IdPos "start_pos" :& IdText "name" :& RNil) req of
111111
Left err -> return err
112112
Right (ParamFile fileName :& ParamPos pos :& ParamText name :& RNil) -> do
@@ -125,7 +125,7 @@ dupdefCmd _ctxs req = do
125125
-- ---------------------------------------------------------------------
126126

127127
iftocaseCmd :: CommandFunc [FilePath]
128-
iftocaseCmd _ctxs req = do
128+
iftocaseCmd = CmdSync $ \_ctxs req -> do
129129
case getParams (IdFile "file" :& IdPos "start_pos" :& IdPos "end_pos" :& RNil) req of
130130
Left err -> return err
131131
Right (ParamFile fileName :& ParamPos start :& ParamPos end :& RNil) -> do
@@ -144,7 +144,7 @@ iftocaseCmd _ctxs req = do
144144
-- ---------------------------------------------------------------------
145145

146146
liftonelevelCmd :: CommandFunc [FilePath]
147-
liftonelevelCmd _ctxs req = do
147+
liftonelevelCmd = CmdSync $ \_ctxs req -> do
148148
case getParams (IdFile "file" :& IdPos "start_pos" :& RNil) req of
149149
Left err -> return err
150150
Right (ParamFile fileName :& ParamPos pos :& RNil) -> do
@@ -163,7 +163,7 @@ liftonelevelCmd _ctxs req = do
163163
-- ---------------------------------------------------------------------
164164

165165
lifttotoplevelCmd :: CommandFunc [FilePath]
166-
lifttotoplevelCmd _ctxs req = do
166+
lifttotoplevelCmd = CmdSync $ \_ctxs req -> do
167167
case getParams (IdFile "file" :& IdPos "start_pos" :& RNil) req of
168168
Left err -> return err
169169
Right (ParamFile fileName :& ParamPos pos :& RNil) -> do
@@ -182,7 +182,7 @@ lifttotoplevelCmd _ctxs req = do
182182
-- ---------------------------------------------------------------------
183183

184184
renameCmd :: CommandFunc [FilePath]
185-
renameCmd _ctxs req = do
185+
renameCmd = CmdSync $ \_ctxs req -> do
186186
case getParams (IdFile "file" :& IdPos "start_pos" :& IdText "name" :& RNil) req of
187187
Left err -> return err
188188
Right (ParamFile fileName :& ParamPos pos :& ParamText name :& RNil) -> do

src/Haskell/Ide/Engine/BasePlugin.hs

+4-4
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,15 @@ baseDescriptor = PluginDescriptor
7676
-- ---------------------------------------------------------------------
7777

7878
versionCmd :: CommandFunc String
79-
versionCmd _ _ = return (IdeResponseOk version)
79+
versionCmd = CmdSync $ \_ _ -> return (IdeResponseOk version)
8080

8181
pluginsCmd :: CommandFunc Plugins
82-
pluginsCmd _ _ = do
82+
pluginsCmd = CmdSync $ \_ _ -> do
8383
plugins <- getPlugins
8484
return (IdeResponseOk plugins)
8585

8686
commandsCmd :: CommandFunc [CommandName]
87-
commandsCmd _ req = do
87+
commandsCmd = CmdSync $ \_ req -> do
8888
plugins <- getPlugins
8989
-- TODO: Use Maybe Monad. What abut error reporting?
9090
case Map.lookup "plugin" (ideParams req) of
@@ -97,7 +97,7 @@ commandsCmd _ req = do
9797
Just x -> return $ incorrectParameter "plugin" ("ParamText"::String) x
9898

9999
commandDetailCmd :: CommandFunc CommandDescriptor
100-
commandDetailCmd _ req = do
100+
commandDetailCmd = CmdSync $ \_ req -> do
101101
plugins <- getPlugins
102102
case getParams (IdText "plugin" :& IdText "command" :& RNil) req of
103103
Left err -> return err

src/Haskell/Ide/Engine/Console.hs

+6-5
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
{-# LANGUAGE ScopedTypeVariables #-}
33
module Haskell.Ide.Engine.Console where
44

5-
import Control.Concurrent
5+
import Control.Concurrent.STM.TChan
66
import Control.Monad.IO.Class
7+
import Control.Monad.STM
78
import Data.Attoparsec.Text
89
import qualified Data.Map as Map
910
import Data.Monoid
@@ -24,9 +25,9 @@ emptyEnv = ReplEnv
2425

2526
-- ---------------------------------------------------------------------
2627

27-
consoleListener :: Plugins -> Chan ChannelRequest -> IO ()
28+
consoleListener :: Plugins -> TChan ChannelRequest -> IO ()
2829
consoleListener plugins cin = do
29-
cout <- newChan :: IO (Chan ChannelResponse)
30+
cout <- atomically newTChan :: IO (TChan ChannelResponse)
3031
let
3132
startLoop :: ReplEnv -> Int -> InputT IO ()
3233
startLoop env cid = do
@@ -51,8 +52,8 @@ consoleListener plugins cin = do
5152
case req of
5253
Left err -> outputStrLn (T.unpack err)
5354
Right (plugin,reqVal) -> do
54-
liftIO $ writeChan cin (CReq plugin cid reqVal cout)
55-
rsp <- liftIO $ readChan cout
55+
liftIO $ atomically $ writeTChan cin (CReq plugin cid reqVal cout)
56+
rsp <- liftIO $ atomically $ readTChan cout
5657
outputStrLn $ show (coutResp rsp)
5758
loop env (cid + 1)
5859

src/Haskell/Ide/Engine/Dispatcher.hs

+29-12
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
{-# LANGUAGE GADTs #-}
44
module Haskell.Ide.Engine.Dispatcher where
55

6-
import Control.Concurrent
6+
import Control.Concurrent.STM.TChan
77
import Control.Monad
88
import Control.Monad.IO.Class
9+
import Control.Monad.STM
910
import Data.Aeson
1011
import Data.Either
1112
import Data.Monoid
@@ -21,26 +22,35 @@ import qualified Data.Map as Map
2122

2223
-- |Listen on a Chan for ChannelRequest from the assorted listeners, and route
2324
-- them through to the appropriate plugin for processing.
24-
dispatcher :: Chan ChannelRequest -> IdeM ()
25+
dispatcher :: TChan ChannelRequest -> IdeM ()
2526
dispatcher cin = do
2627
plugins <- getPlugins
2728
forever $ do
2829
debugm "run:top of loop"
29-
req <- liftIO $ readChan cin
30+
req <- liftIO $ atomically $ readTChan cin
3031
debugm $ "main loop:got:" ++ show req
31-
resp <- doDispatch plugins req
32-
let cr = CResp (cinPlugin req) (cinReqId req) resp
33-
liftIO $ writeChan (cinReplyChan req) cr
32+
mresp <- doDispatch plugins req
33+
case mresp of
34+
Nothing -> return ()
35+
Just resp -> liftIO $ sendResponse req resp
36+
37+
-- ---------------------------------------------------------------------
38+
39+
-- | Send a response from the plugin to the designated reply channel
40+
sendResponse :: (ValidResponse a) => ChannelRequest -> IdeResponse a -> IO ()
41+
sendResponse req resp = do
42+
let cr = CResp (cinPlugin req) (cinReqId req) (fmap jsWrite resp)
43+
liftIO $ atomically $ writeTChan (cinReplyChan req) cr
3444

3545
-- ---------------------------------------------------------------------
3646

3747
-- | Manage the process of looking up the request in the known plugins,
3848
-- validating the parameters passed and handing off to the appropriate
3949
-- 'CommandFunc'
40-
doDispatch :: Plugins -> ChannelRequest -> IdeM (IdeResponse Object)
50+
doDispatch :: Plugins -> ChannelRequest -> IdeM (Maybe (IdeResponse Object))
4151
doDispatch plugins creq = do
4252
case Map.lookup (cinPlugin creq) plugins of
43-
Nothing -> return (IdeResponseError (IdeError
53+
Nothing -> return $ Just (IdeResponseError (IdeError
4454
UnknownPlugin ("No plugin found for:" <> cinPlugin creq )
4555
(Just $ toJSON $ cinPlugin creq)))
4656
Just desc -> do
@@ -49,13 +59,20 @@ doDispatch plugins creq = do
4959
debugm $ "doDispatch:desc=" ++ show desc
5060
debugm $ "doDispatch:req=" ++ show req
5161
case Map.lookup (pn,ideCommand req) (pluginCache plugins) of
52-
Nothing -> return (IdeResponseError (IdeError
62+
Nothing -> return $ Just (IdeResponseError (IdeError
5363
UnknownCommand ("No such command:" <> ideCommand req )
5464
(Just $ toJSON $ ideCommand req)))
55-
Just (Command cdesc cfunc) ->
65+
Just (Command cdesc cfunc) -> do
5666
case validateContexts cdesc req of
57-
Left err -> return err
58-
Right ctxs -> fmap jsWrite <$> cfunc ctxs req
67+
Left err -> return (Just err)
68+
Right ctxs -> case cfunc of
69+
CmdSync f -> do
70+
r <- f ctxs req
71+
let r2 = fmap jsWrite r
72+
return (Just r2)
73+
CmdAsync f -> do
74+
f (sendResponse creq) ctxs req
75+
return Nothing
5976

6077
-- ---------------------------------------------------------------------
6178

src/Haskell/Ide/Engine/Transport/JsonHttp.hs

+9-8
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ module Haskell.Ide.Engine.Transport.JsonHttp
1111
)
1212
where
1313

14-
import Control.Concurrent
14+
import Control.Concurrent.STM.TChan
1515
import Control.Monad.IO.Class
16+
import Control.Monad.STM
1617
import Data.Aeson
1718
import qualified Data.Map as Map
1819
import Data.Maybe
@@ -54,33 +55,33 @@ testApi = Proxy
5455
-- that represents the API, are glued together using :<|>.
5556
--
5657
-- Each handler runs in the 'ExceptT ServantErr IO' monad.
57-
server :: Chan ChannelRequest -> Chan ChannelResponse -> Server HieApi
58+
server :: TChan ChannelRequest -> TChan ChannelResponse -> Server HieApi
5859
server cin cout = hieH
5960
:<|> egH
6061

6162
where
6263
hieH plugin mrid reqVal = do
6364
let rid = fromMaybe 1 mrid
64-
liftIO $ writeChan cin (CReq plugin rid reqVal cout)
65-
rsp <- liftIO $ readChan cout
65+
liftIO $ atomically $ writeTChan cin (CReq plugin rid reqVal cout)
66+
rsp <- liftIO $ atomically $ readTChan cout
6667
return (coutResp rsp)
6768
-- return (IdeResponseOk (String $ pack $ show r))
6869

6970
egH = return (IdeRequest ("version"::Text) Map.empty)
7071

7172
-- Turn the server into a WAI app. 'serve' is provided by servant,
7273
-- more precisely by the Servant.Server module.
73-
test :: Chan ChannelRequest -> Chan ChannelResponse -> Application
74+
test :: TChan ChannelRequest -> TChan ChannelResponse -> Application
7475
test cin cout = serve testApi (server cin cout)
7576

7677
-- Run the server.
7778
--
7879
-- 'run' comes from Network.Wai.Handler.Warp
79-
runTestServer :: Chan ChannelRequest -> Port -> IO ()
80+
runTestServer :: TChan ChannelRequest -> Port -> IO ()
8081
runTestServer cin port = do
81-
cout <- newChan :: IO (Chan ChannelResponse)
82+
cout <- atomically newTChan :: IO (TChan ChannelResponse)
8283
run port (test cin cout)
8384

8485
-- Put this all to work!
85-
jsonHttpListener :: Chan ChannelRequest -> Port -> IO ()
86+
jsonHttpListener :: TChan ChannelRequest -> Port -> IO ()
8687
jsonHttpListener cin port = runTestServer cin port

0 commit comments

Comments
 (0)