Skip to content

Commit 5091b24

Browse files
committed
[retry] Retry upon exceptions, and bubble them up if necessary
Per pull request feed back.
1 parent 35a8051 commit 5091b24

File tree

2 files changed

+25
-17
lines changed

2 files changed

+25
-17
lines changed

docs/Network.HTTP.Affjax.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ Makes a `DELETE` request to the specified URL and ignores the response.
150150
retry :: forall e a b. (Requestable a) => Maybe Int -> (AffjaxRequest a -> Affjax (avar :: AVAR | e) b) -> AffjaxRequest a -> Affjax (avar :: AVAR | e) b
151151
```
152152

153-
Retry a request with exponential backoff, timing out optionally after a specified number of milliseconds.
153+
Retry a request with exponential backoff, timing out optionally after a specified number of milliseconds. After the timeout, the last received response is returned; if it was not possible to communicate with the server due to an error, then this is bubbled up.
154154

155155
#### `affjax'`
156156

src/Network/HTTP/Affjax.purs

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ import Control.Monad.Aff.Par (Par(..), runPar)
2121
import Control.Monad.Aff.AVar (AVAR(), makeVar, takeVar, putVar)
2222
import Control.Monad.Eff (Eff())
2323
import Control.Monad.Eff.Exception (Error(), error)
24-
import Data.Either (Either(..))
24+
import Control.Monad.Error.Class (throwError)
25+
import Data.Either (Either(..), either)
2526
import Data.Foreign (Foreign(..), F(), parseJSON, readString)
2627
import Data.Function (Fn5(), runFn5, Fn4(), runFn4)
2728
import Data.Int (toNumber, round)
@@ -123,9 +124,13 @@ delete u = affjax $ defaultRequest { method = DELETE, url = u }
123124
delete_ :: forall e. URL -> Affjax e Unit
124125
delete_ = delete
125126

126-
-- | Retry a request with exponential backoff, timing out optionally after a specified number of milliseconds.
127+
-- | Either we have a failure (which may be an exception or a failed response), or we have a successful response.
128+
type RetryState e a = Either (Either e a) a
129+
130+
-- | Retry a request with exponential backoff, timing out optionally after a specified number of milliseconds. After the timeout, the last received response is returned; if it was not possible to communicate with the server due to an error, then this is bubbled up.
127131
retry :: forall e a b. (Requestable a) => Maybe Int -> (AffjaxRequest a -> Affjax (avar :: AVAR | e) b) -> (AffjaxRequest a -> Affjax (avar :: AVAR | e) b)
128132
retry milliseconds run req = do
133+
-- failureVar is either an exception or a failed request
129134
failureVar <- makeVar
130135
let loop = go failureVar
131136
case milliseconds of
@@ -139,26 +144,29 @@ retry milliseconds run req = do
139144
loopHandle `cancel` error "Cancel"
140145
result <- takeVar respVar
141146
case result of
142-
Nothing ->
143-
takeVar failureVar
147+
Nothing -> takeVar failureVar >>= either throwError pure
144148
Just resp -> pure resp
145149
where
146-
assert200 resp =
150+
-- delay at attempt #n with exponential backoff
151+
delay n = round $ max maxDelay $ 100.0 * (pow 2.0 $ toNumber (n - 1))
152+
where
153+
-- maximum delay in milliseconds
154+
maxDelay = 30.0 * 1000.0
155+
156+
retryState :: Either _ _ -> RetryState _ _
157+
retryState (Left exn) = Left $ Left exn
158+
retryState (Right resp) =
147159
case resp.status of
148160
StatusCode 200 -> Right resp
149-
_ -> Left resp
150-
151-
-- maximum delay in milliseconds
152-
maxDelay = 30.0 * 1000.0
161+
_ -> Left (Right resp)
153162

154163
go failureVar n = do
155-
result <- run req
156-
case assert200 result of
157-
Right b -> pure b
158-
Left resp -> do
159-
putVar failureVar resp
160-
let delay = round $ max maxDelay $ 100.0 * (pow 2.0 $ toNumber (n - 1))
161-
later' delay $ go failureVar (n + 1)
164+
result <- retryState <$> attempt (run req)
165+
case result of
166+
Left err -> do
167+
putVar failureVar err
168+
later' (delay n) $ go failureVar (n + 1)
169+
Right resp -> pure resp
162170

163171
-- | Run a request directly without using `Aff`.
164172
affjax' :: forall e a b. (Requestable a, Respondable b) =>

0 commit comments

Comments
 (0)