| ... |
... |
@@ -70,8 +70,13 @@ data Pipe = Pipe |
|
70
|
70
|
, pipeLeftovers :: !(IORef (Maybe ByteString))
|
|
71
|
71
|
, pipeLock :: !(MVar ()) -- ^ Lock to prevent concurrent writes to the stream
|
|
72
|
72
|
, pipeAsyncMngr :: !AsyncManager
|
|
73
|
|
- , pipeIsClosed :: !(TVar Bool)
|
|
74
|
|
- -- ^ After the pipe is closed, requests can no longer be received nor sent
|
|
|
73
|
+ , pipeCloseRead :: !(IO ())
|
|
|
74
|
+ -- ^ closes read end of the pipe
|
|
|
75
|
+ , pipeCloseWrite :: !(IO ())
|
|
|
76
|
+ -- ^ closes write end of the pipe
|
|
|
77
|
+ , pipeReadClosed :: !(TVar Bool)
|
|
|
78
|
+ -- ^ After the pipe is closed, messages can no longer be received from the
|
|
|
79
|
+ -- other end, but the 'AsyncManager' queues might not be empty yet.
|
|
75
|
80
|
}
|
|
76
|
81
|
|
|
77
|
82
|
-- | Make a 'Pipe' from a 'Handle' to read and a 'Handle' to write.
|
| ... |
... |
@@ -82,43 +87,49 @@ mkPipeFromHandles pipeRead pipeWrite = do |
|
82
|
87
|
putAll b = do
|
|
83
|
88
|
B.hPutBuilder pipeWrite b
|
|
84
|
89
|
hFlush pipeWrite
|
|
85
|
|
- mkPipeFromContinuations getSome putAll
|
|
|
90
|
+ mkPipeFromContinuations getSome putAll (hClose pipeRead) (hClose pipeWrite)
|
|
86
|
91
|
|
|
87
|
92
|
-- | Make a 'Pipe' from a reader function and a writer function.
|
|
88
|
|
--- The pipe should be closed with 'closePipe'.
|
|
89
|
|
-mkPipeFromContinuations :: IO ByteString -> (B.Builder -> IO ()) -> IO Pipe
|
|
90
|
|
-mkPipeFromContinuations getSome putAll = mdo
|
|
|
93
|
+-- The pipe should be closed with 'closePipe', which will call 'pipeCloseRead' and 'pipeCloseWrite'.
|
|
|
94
|
+mkPipeFromContinuations :: IO ByteString -> (B.Builder -> IO ())
|
|
|
95
|
+ -> IO ()
|
|
|
96
|
+ -- ^ pipeCloseRead
|
|
|
97
|
+ -> IO ()
|
|
|
98
|
+ -- ^ pipeCloseWrite
|
|
|
99
|
+ -> IO Pipe
|
|
|
100
|
+mkPipeFromContinuations getSome putAll pipeCloseRead pipeCloseWrite = mdo
|
|
91
|
101
|
pipeLeftovers <- newIORef Nothing
|
|
92
|
102
|
pipeLock <- newMVar ()
|
|
93
|
|
- pipeIsClosed <- newTVarIO False
|
|
|
103
|
+ pipeReadClosed <- newTVarIO False
|
|
94
|
104
|
mdo
|
|
95
|
105
|
pipeAsyncMngr <- newAsyncManager p
|
|
96
|
106
|
let p = Pipe { .. }
|
|
97
|
107
|
pure p
|
|
98
|
108
|
|
|
99
|
|
--- | Cleanly close our end of the Pipe, terminating the async manager's
|
|
100
|
|
--- drain-read-end thread. Calling 'closePipe' on an already closed pipe is a no-op.
|
|
|
109
|
+-- | Cleanly close our ends of the Pipe, terminating the async manager's
|
|
|
110
|
+-- drain-read-end thread. Also calls 'pipeCloseRead' and 'pipeCloseWrite', in that order.
|
|
101
|
111
|
--
|
|
102
|
|
--- If the pipe was created using 'mkPipeFromHandles', the handles must only by
|
|
103
|
|
--- closed after calling 'closePipe'.
|
|
|
112
|
+-- If the pipe was created using 'mkPipeFromHandles', the handles are also closed.
|
|
104
|
113
|
--
|
|
105
|
114
|
-- The requests we've sent that are pending are all canceled with
|
|
106
|
115
|
-- 'PipeClosedException' by ourselves.
|
|
107
|
116
|
--
|
|
108
|
|
--- The remote process does not necessarily detect that this end of the pipe was
|
|
109
|
|
--- closed, but it must handle cleanly and clean up after exceptions caused from
|
|
110
|
|
--- it such as a handle being closed.
|
|
|
117
|
+-- No special notification is sent to the remote process, a message to that
|
|
|
118
|
+-- effect should be sent ahead of time if one is needed (see 'Shutdown').
|
|
111
|
119
|
closePipe :: Pipe -> IO ()
|
|
112
|
120
|
closePipe Pipe{..} = do
|
|
113
|
121
|
|
|
114
|
|
- isAlreadyClosed <- atomically $ swapTVar pipeIsClosed True
|
|
|
122
|
+ readAlreadyClosed <- atomically $ swapTVar pipeReadClosed True
|
|
115
|
123
|
|
|
116
|
|
- unless isAlreadyClosed $ do
|
|
|
124
|
+ -- this triggers pipeCloseRead
|
|
|
125
|
+ unless readAlreadyClosed $ do
|
|
117
|
126
|
|
|
118
|
127
|
-- releases handle lock taken by reading, and cleans up by replying to every
|
|
119
|
128
|
-- pending request with 'PipeClosedException'.
|
|
120
|
129
|
throwTo (asyncDrainTid pipeAsyncMngr) PipeClosedException
|
|
121
|
130
|
|
|
|
131
|
+ pipeCloseWrite
|
|
|
132
|
+
|
|
122
|
133
|
-- | We've closed the pipe on our end and we terminate all pending requests with this.
|
|
123
|
134
|
data PipeClosedException = PipeClosedException deriving Show
|
|
124
|
135
|
instance Exception PipeClosedException
|
| ... |
... |
@@ -186,7 +197,8 @@ remoteAsyncReq pipe msg ii = do |
|
186
|
197
|
wv <- newEmptyTMVarIO
|
|
187
|
198
|
|
|
188
|
199
|
isClosed <- atomically $ do
|
|
189
|
|
- isClosed <- readTVar (pipeIsClosed pipe)
|
|
|
200
|
+ isClosed <- readTVar (pipeReadClosed pipe)
|
|
|
201
|
+ -- don't send the req if we won't get a reply.
|
|
190
|
202
|
if isClosed
|
|
191
|
203
|
then pure True
|
|
192
|
204
|
else do
|
| ... |
... |
@@ -223,15 +235,17 @@ handleAsyncReq pipe getit mii k = mask $ \restore -> do |
|
223
|
235
|
|
|
224
|
236
|
-- Block waiting for request
|
|
225
|
237
|
mbreq <- atomically $ do
|
|
226
|
|
- isClosed <- readTVar (pipeIsClosed pipe)
|
|
227
|
|
- if isClosed
|
|
228
|
|
- then return Nothing
|
|
229
|
|
- else Just <$> case mii of
|
|
230
|
|
- Nothing -> readTQueue $ requestsQueue am
|
|
231
|
|
- Just ii -> readTQueue =<< getMailbox am ii
|
|
|
238
|
+ isClosed <- readTVar (pipeReadClosed pipe)
|
|
|
239
|
+ taken <- case mii of
|
|
|
240
|
+ Nothing -> tryReadTQueue $ requestsQueue am
|
|
|
241
|
+ Just ii -> tryReadTQueue =<< getMailbox am ii
|
|
|
242
|
+ case taken of
|
|
|
243
|
+ Just req -> pure (Just req)
|
|
|
244
|
+ Nothing | isClosed -> pure Nothing -- empty and closed
|
|
|
245
|
+ | otherwise -> retry -- wait for msg
|
|
232
|
246
|
|
|
233
|
247
|
case mbreq of
|
|
234
|
|
- Nothing -> throwIO PipeClosedException
|
|
|
248
|
+ Nothing -> throwIO PipeClosedException -- can't expect reqs
|
|
235
|
249
|
Just (uq, bs) -> do
|
|
236
|
250
|
let reply p = do
|
|
237
|
251
|
writePipe pipe (Reply uq (bimap toSerializableException runPut p))
|
| ... |
... |
@@ -368,8 +382,10 @@ newAsyncManager pipe = do |
|
368
|
382
|
-- In pratice, we'll have both a thread draining the read end of the host's
|
|
369
|
383
|
-- 'Pipe' and a thread draining the read end of the interpreter's 'Pipe', since
|
|
370
|
384
|
-- both processes construct a 'Pipe' and send and receive messages
|
|
|
385
|
+--
|
|
|
386
|
+-- Closes the Pipe read end when done reading
|
|
371
|
387
|
drainPipe :: Pipe -> (forall x. IO x -> IO x) -> IO ()
|
|
372
|
|
-drainPipe p unmask = catch (unmask loop) replyToPendings where
|
|
|
388
|
+drainPipe p unmask = catch (unmask $ loop `finally` pipeCloseRead p) replyToPendings where
|
|
373
|
389
|
loop = do
|
|
374
|
390
|
readPipe p >>= \case
|
|
375
|
391
|
-- Receive a reply to a pending notification
|
| ... |
... |
@@ -404,7 +420,9 @@ drainPipe p unmask = catch (unmask loop) replyToPendings where |
|
404
|
420
|
pure ()
|
|
405
|
421
|
|
|
406
|
422
|
replyToPendings e = atomically $ do
|
|
407
|
|
- writeTVar (pipeIsClosed p) True
|
|
|
423
|
+ -- reqs are only added if pipe not closed;
|
|
|
424
|
+ -- thus we close and take all pending atomically.
|
|
|
425
|
+ writeTVar (pipeReadClosed p) True
|
|
408
|
426
|
pendings <- swapTVar (pendingReply am) IntMap.empty -- take pending
|
|
409
|
427
|
mapM_ (\p -> putTMVar p (Left e {-host exception-})) pendings
|
|
410
|
428
|
|