@@ -32,16 +32,39 @@ module Network.Wait (
32
32
33
33
-------------------------------------------------------------------------------
34
34
35
+ import Control.Exception (throwIO )
35
36
import Control.Monad.Catch
36
37
import Control.Monad.IO.Class
37
38
import Control.Retry
38
39
-- Only needed for base < 4.11, redundant otherwise
39
40
import Data.Semigroup
41
+ import System.IO.Error
42
+ import System.Timeout
40
43
41
44
import Network.Socket
42
45
43
46
-------------------------------------------------------------------------------
44
47
48
+ -- | Each individual connect attempt needs a timeout to prevent it from hanging
49
+ -- indefinitely. This policy allows us to make that timeout length adaptive,
50
+ -- based on the 'RetryStatus' of the outer retry policy.
51
+ --
52
+ -- Thus, the first attempt to connect will have a short timeout (currently 100ms),
53
+ -- and then successive attempts will get longer timeouts via "FullJitter" backoff.
54
+ -- The goals of this are twofold:
55
+ --
56
+ -- 1) If a connect call hangs during the first few attempts, it is timed out quickly
57
+ -- and re-attempted, so on a healthy network you aren't penalized too much by the hang.
58
+ -- The outer retry policy can control the time between attempts, so the user can set
59
+ -- it high enough to make this be the case.
60
+ --
61
+ -- 2) If the network is slow, we will eventually reach the maximum timeout of 3 seconds,
62
+ -- which should be long enough. Note that the popular wait-for script uses 1 second
63
+ -- timeouts, so this is extra conservative:
64
+ -- https://github.com/eficode/wait-for/blob/7586b3622f010808bb2027c19aaf367221b4ad54/wait-for#L72
65
+ connectRetryPolicy :: MonadIO m => RetryPolicyM m
66
+ connectRetryPolicy = capDelay (3000000 ) (fullJitterBackoff 100000 )
67
+
45
68
-- | `waitTcp` @retryPolicy hostName serviceName@ is a variant of `waitTcpWith`
46
69
-- which does not install any additional handlers.
47
70
--
@@ -141,13 +164,21 @@ waitSocketWith
141
164
=> [RetryStatus -> Handler m Bool ] -> RetryPolicyM m -> AddrInfo
142
165
-> m Socket
143
166
waitSocketWith hs policy addr =
144
- recoveringWith hs policy $
167
+ recoveringWith hs policy $ \ retryStatus ->
145
168
-- all of the networking code runs in IO
146
169
liftIO $
147
170
-- we want to make sure that we close the socket after every attempt;
148
171
-- `bracket` will re-throw any error afterwards
149
- bracket initSocket close $
150
- \ sock -> connect sock (addrAddress addr) >> pure sock
172
+ bracket initSocket close $ \ sock -> do
173
+ maybeConnectTimeoutUs <- (getRetryPolicyM connectRetryPolicy) retryStatus
174
+ connectTimeoutUs <- case maybeConnectTimeoutUs of
175
+ Nothing -> throwIO $ userError " Timeout in connect attempt"
176
+ Just us -> pure us
177
+
178
+ maybeResult <- timeout connectTimeoutUs (connect sock (addrAddress addr))
179
+ case maybeResult of
180
+ Nothing -> throwIO $ userError " Timeout in connect attempt"
181
+ Just () -> pure sock
151
182
where
152
183
initSocket =
153
184
socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)
@@ -163,15 +194,13 @@ waitSocketWith hs policy addr =
163
194
-- the standard output or a logger.
164
195
recoveringWith
165
196
:: (MonadIO m , MonadMask m )
166
- => [RetryStatus -> Handler m Bool ] -> RetryPolicyM m -> m a -> m a
197
+ => [RetryStatus -> Handler m Bool ] -> RetryPolicyM m -> ( RetryStatus -> m a ) -> m a
167
198
recoveringWith hs policy action =
168
199
-- apply the retry policy to the following code, with the combinations of
169
200
-- the `skipAsyncExceptions`, given, and default handlers. The order of
170
201
-- the handlers matters as they are checked in order.
171
202
recovering policy (skipAsyncExceptions <> hs <> [defHandler]) $
172
- -- we want to make sure that we close the socket after every attempt;
173
- -- `bracket` will re-throw any error afterwards
174
- const action
203
+ action
175
204
where
176
205
-- our default handler, which works with any exception derived from
177
206
-- `SomeException`, and signals that we should retry if allowed by
0 commit comments