Skip to content

Commit 954dcc0

Browse files
iohk-bors[bot]coot
andauthored
Merge #1897
1897: Change how IOManager is handling exceptions r=dcoutts a=coot With this patch `withIOManager` will rethrown the exception thrown when `LPOVERLAPPED` was null. This wasn't the case previously which was a mistake. I also documented better (an experiment a bit more) with one particular case: why we are not throwing exceptions when the `LPOVERLAPPED` is `NULL` and `GetQueuedIoCompletionStatus` errored with `ERROR_INVALID_HANDLE`. I can reproduce this scenario when we close the iocp's `HANDLE`, but only in some circumstances e.g. running the same test twice or more. The first time `dequeueCompletionPackets` exists cleanly (i.e. `GetQueuedIoCompletionStatus` errors with `ERROR_ABANDONDED_WAIT_0`) and the next one errors with `ERROR_INVALID_HANDLE`). This behaviour is not documented in `MSDN`. Also label the `IOManager` thread, which is useful for profiling with the eventlog. - withIOManager use IOException - Removed withMaxSuccess from tests - Label the IOManager thread Co-authored-by: Marcin Szamotulski <[email protected]>
2 parents 55b3f5a + c2f1e6b commit 954dcc0

File tree

5 files changed

+66
-24
lines changed

5 files changed

+66
-24
lines changed

Win32-network/src/System/IOManager.hs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
{-# LANGUAGE CPP #-}
22
{-# LANGUAGE RankNTypes #-}
33

4+
-- we are exporting `Void` with constructors
5+
{-# OPTIONS_GHC -Wno-dodgy-exports #-}
6+
47
-- | A shim layer for `Win32-network`'s `IOManager`
58
--
69
module System.IOManager
710
( WithIOManager
811
, IOManager (..)
12+
, IOManagerError (..)
913
, withIOManager
1014

1115
-- * Deprecated API
@@ -18,6 +22,14 @@ module System.IOManager
1822
import System.Win32.Types (HANDLE)
1923
import qualified System.Win32.Async.IOManager as Win32.Async
2024
import Network.Socket (Socket)
25+
#else
26+
import Data.Void (Void)
27+
#endif
28+
29+
#if defined(mingw32_HOST_OS)
30+
type IOManagerError = Win32.Async.IOManagerError
31+
#else
32+
type IOManagerError = Void
2133
#endif
2234

2335
-- | This is public api to interact with the io manager; On Windows 'IOManager'
@@ -50,9 +62,17 @@ type AssociateWithIOCP = IOManager
5062
type WithIOManager = forall a. (IOManager -> IO a) -> IO a
5163

5264

53-
-- | 'withIOManger' must be called only once at the top level. We wrap the
54-
-- 'associateWithIOCompletionPort' in a newtype wrapper since it will be
55-
-- carried arround through the application.
65+
-- | 'withIOManager' allows to do asynchronous io on Windows hiding the
66+
-- differences between posix and Win32.
67+
--
68+
-- It starts an io manger thread, which should be only one running at a time, so
69+
-- the best place to call it is very close to the 'main' function and last for
70+
-- duration of the application.
71+
--
72+
-- Async 'IO' operatitions which are using the 'iocp' port should not leak
73+
-- out-side of 'withIOManager'. They will be silently cancelled when
74+
-- 'withIOManager' exists. In particular one should not return 'IOManager'
75+
-- from 'withIOManager'.
5676
--
5777
withIOManager :: WithIOManager
5878
#if defined(mingw32_HOST_OS)

Win32-network/src/System/Win32/Async/IOManager.hsc

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ module System.Win32.Async.IOManager
1515
IOCompletionPort
1616
, associateWithIOCompletionPort
1717
, withIOManager
18-
, IOManagerError (..)
18+
, IOManagerError
1919

2020
-- * Low level IOCP interface
2121
, dequeueCompletionPackets
@@ -39,6 +39,7 @@ import Foreign.Ptr (Ptr, intPtrToPtr, nullPtr)
3939
import Foreign.StablePtr (deRefStablePtr, freeStablePtr)
4040
import Foreign.Marshal (alloca, free)
4141
import Foreign.Storable (Storable (..))
42+
import GHC.Conc (labelThread)
4243

4344
import Network.Socket (Socket)
4445
import qualified Network.Socket as Socket
@@ -140,23 +141,17 @@ withIOManager k =
140141
-- But note that 'closeIOCopletionPort' will terminate the io-manager
141142
-- thread (we cover this scenario in the 'test_closeIOCP' test).
142143
_ <-
143-
forkOS
144-
$ void $ dequeueCompletionPackets iocp
145-
`catch`
146-
\(e :: IOException) -> do
147-
-- throw IOExceptoin's back to the thread which started 'IOManager'
148-
throwTo tid (IOManagerError e)
149-
throwIO e
144+
forkOS $
145+
do
146+
myThreadId >>= flip labelThread "IOManager"
147+
void $ dequeueCompletionPackets iocp
148+
`catch` \(e :: IOException) -> do
149+
-- throw IOExceptoin's back to the thread which started 'IOManager'
150+
throwTo tid (IOManagerError e)
151+
throwIO e
150152
k iocp
151153

152154

153-
data IOCompletionException
154-
= NullPointerException
155-
| NullOverlappedPointer
156-
| IOCompoletionError !Win32.ErrCode
157-
deriving Show
158-
159-
160155
data IOManagerNotification
161156
= IOManagerSkip
162157
-- ^ io manger loop will skip and continue
@@ -167,7 +162,6 @@ data IOManagerNotification
167162
| IOManagerOperationSuccess !Int !LPOVERLAPPED
168163
-- ^ io manager loop received a notification of an erronous IO operation
169164

170-
instance Exception IOCompletionException
171165

172166
-- | I/O manager which handles completions of I/O operations. It should run on
173167
-- a bound thread. It dereferences the stable pointer which was allocated by
@@ -214,6 +208,13 @@ dequeueCompletionPackets (IOCompletionPort port) = ioManagerLoop
214208
-- <https://docs.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-getqueuedcompletionstatus#remarks>)
215209
pure IOManagerStop
216210
| gqcsOverlappedIsNull && errorCode == eRROR_INVALID_HANDLE ->
211+
-- This path is not documented on 'MSDN'; I was only able to
212+
-- reproduce it in some scenarios when closing the iocp
213+
-- handle (which is done by 'withIOManager'). We are not
214+
-- throwing an exception, since this exception would be
215+
-- re-thrown to the application thread when 'withIOManager'
216+
-- exits.
217+
--
217218
-- If we don't terminate the dequeueing thread, some of the tests
218219
-- will take 10x-100x more time to complete:
219220
--
@@ -243,7 +244,7 @@ dequeueCompletionPackets (IOCompletionPort port) = ioManagerLoop
243244
-- 'GetLastCompletionStatus' has not dequeued any completion packet
244245
-- from the completion port. Must be the first clause, since if
245246
-- this is not true we cannot trust other arguments.
246-
throwIO NullOverlappedPointer
247+
Win32.failWith "dequeueCompletionPackets" errorCode
247248
| completionKey /= magicCompletionKey ->
248249
-- The completion key does not agree with what we expect, we ignore
249250
-- and carry on. The completion key is set when one calls

Win32-network/test/Test/Async/Socket.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,14 @@ tests =
5050
, testProperty "sendTo and recvFrom"
5151
(ioProperty . prop_sendTo_recvFrom)
5252
, testProperty "PingPong test"
53-
$ withMaxSuccess 100 prop_PingPong
53+
prop_PingPong
5454
, testProperty "PingPongPipelined test"
55-
$ withMaxSuccess 100 prop_PingPongPipelined
55+
prop_PingPongPipelined
5656
, testGroup "vectored io"
5757
[ testProperty "PingPong test"
58-
$ withMaxSuccess 100 prop_PingPongLazy
58+
prop_PingPongLazy
5959
, testProperty "PingPongPipelined test"
60-
$ withMaxSuccess 100 prop_PingPongPipelinedLazy
60+
prop_PingPongPipelinedLazy
6161
]
6262
]
6363

ouroboros-network/src/Ouroboros/Network/NodeToClient.hs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,13 +439,23 @@ networkErrorPolicies = ErrorPolicies
439439
MuxBearerClosed -> Just (SuspendPeer shortDelay shortDelay)
440440
MuxIOException{} -> Just (SuspendPeer shortDelay shortDelay)
441441
MuxSDUReadTimeout -> Just (SuspendPeer shortDelay shortDelay)
442+
443+
-- Error thrown by 'IOManager', this is fatal on Windows, and it will
444+
-- never fire on other platofrms.
445+
, ErrorPolicy
446+
$ \(_ :: IOManagerError)
447+
-> Just Throw
442448
]
443449
, epConErrorPolicies = [
444450
-- If an 'IOException' is thrown by the 'connect' call we suspend the
445451
-- peer for 'shortDelay' and we will try to re-connect to it after that
446452
-- period.
447453
ErrorPolicy $ \(_ :: IOException) -> Just $
448454
SuspendPeer shortDelay shortDelay
455+
456+
, ErrorPolicy
457+
$ \(_ :: IOManagerError)
458+
-> Just Throw
449459
]
450460
}
451461
where

ouroboros-network/src/Ouroboros/Network/NodeToNode.hs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ import qualified Network.Socket as Socket
100100

101101
import Ouroboros.Network.Driver (TraceSendRecv(..))
102102
import Ouroboros.Network.Driver.Limits (ProtocolLimitFailure)
103+
import Ouroboros.Network.IOManager
103104
import Ouroboros.Network.Mux
104105
import Ouroboros.Network.Magic
105106
import Ouroboros.Network.ErrorPolicy
@@ -735,13 +736,23 @@ remoteNetworkErrorPolicy = ErrorPolicies {
735736
, ErrorPolicy
736737
$ \(_ :: BlockFetchProtocolFailure)
737738
-> Just theyBuggyOrEvil
739+
740+
-- Error thrown by 'IOManager', this is fatal on Windows, and it will
741+
-- never fire on other platofrms.
742+
, ErrorPolicy
743+
$ \(_ :: IOManagerError)
744+
-> Just Throw
738745
],
739746

740747
-- Exception raised during connect; suspend connecting to that peer for
741748
-- a 'shortDelay'
742749
epConErrorPolicies = [
743750
ErrorPolicy $ \(_ :: IOException) -> Just $
744751
SuspendConsumer shortDelay
752+
753+
, ErrorPolicy
754+
$ \(_ :: IOManagerError)
755+
-> Just Throw
745756
]
746757
}
747758
where

0 commit comments

Comments
 (0)