Retry http request with backoff - (Nestjs - axios - rxjs) throws socket hang up - node.js

[second-Update]: i solved the issue by implmentig retry with promises and try & catch
[first-Update]:
I tried the retrial mechanism with HTTP request with content-type: application/json and it works!! but my issue Is with content type form-data.
I guess it's similar to this problem: Axios interceptor to retry sending FormData
Services architecture
I'm trying to make an HTTP request to service-a from NestJS-app.
and I want to implement retry with backoff logic.
to re-produce service-a failure, I'm restarting its docker container and make the HTTP request.
retry logic implemented as 3 retries.
first time as service-a is restarting.. throws 405 service not available error and make retry.
all 3 retries failed with a socket hang up error.
HTTP request code using axios nestjs wrapper lib
retryWithBackOff rxjs operator implementation
the first call throws a 405 Service Unavailable error.
then application starts retries.
first retry fires after service-a started, failed with error socket hang up
first, second, and third retries failed with socket hang up.
3 sockets hang up errors
my expected behavior is:
when service-a started then the first retry fires, it should work with a successful response.
notice that 3 retries don't log to the Nginx server anything!

While your solution probably works, it could be improved in terms of single responsibility, which RxJS can help with. I use an adapted solution of a code snippet I found once on the web (I can't find the original source any more).
interface GenericRetryStrategy {
getAttempt?(): number;
maxRetryAttempts?: number;
scalingDuration?: number;
maxDuration?: number;
retryFormula?: RetryFormula;
excludedStatusCodes?: number[]; // All errors with these codes will circumvent retry and just return the error
}
const genericRetryStrategy$ =
({
getAttempt,
maxRetryAttempts = 3,
scalingDuration = 1000,
maxDuration = 64000,
retryFormula = 'constant', // time-to-retry-count interpolation
excludedStatusCodes = [], // All errors with these codes will circumvent retry and just return the error
}: GenericRetryStrategy = {}) =>
(error$: Observable<unknown>): Observable<number> =>
error$.pipe(
switchMap((error, i) => {
const retryAttempt = getAttempt ? getAttempt() : i + 1;
// if maximum number of retries have been met
// or response is a error code we don't wish to retry, throw error
if (
retryAttempt > maxRetryAttempts ||
excludedStatusCodes.find(e => e === error.code)
) {
return throwError(error);
}
const retryDuration = getRetryCurve(retryFormula, retryAttempt);
const waitDuration = Math.min(
maxDuration,
retryDuration * scalingDuration,
);
// retry after 1000ms, 2000ms, etc …
return timer(waitDuration);
}),
);
You would then call it like this:
const retryThreeTimes$ = genericRetryStrategy$({
maxRetryAttempts: 3,
excludedStatusCodes: [HttpStatus.PayloadTooLarge, HttpStatus.NotFound] // This will throw the error straight away
});
this.setupUploadAttachements(url, clientApiKey, files, toPoTenantId).pipe(retryWhen(retryThreeTimes$))
This function/operator can now be re-used for all kinds of requests. It is very flexible. It also makes your operator logic more readable, since the complex retry logic sits somewhere else and does not “pollute” your pipe.
You might have to do some adjustment, since axios does return a different error payload, it seems (at least judging from your code examples). Also, if I understood your code correctly, you actually don't want to throw and error when the above error codes apply. In that case, you could add another catchError after the retryWhen and filter these codes, while returning of([]).

Related

How to retry a http request until a condition is met using rxjs

I want to retry a http request until some data exists up to 10 times with a delay of 2 seconds between each retry.
const $metrics = from(axios(this.getMetrics(session._id, sessionRequest._id, side)));
const res = $metrics.pipe(
map((val: any) => {
console.log("VALUE:", val.data.metrics.length);
if (val.data.metrics.length === 0) {
throw val;
}
return val;
}),
retryWhen((errors) => errors.pipe(delay(2000), take(10))),
).subscribe();
I am trying to follow the example in the documentation. https://www.learnrxjs.io/operators/error_handling/retry.html
I create $metrics observable from an axios http promise.
I use the map operator to check if the response of the http request matches my condition to retry. val.data.metrics.length === 0. If it does it throws an error.
I retry the http requests up to 10 times with a 10 second delay.
I expect after 3-4 retries for this metrics array to have data, but in my console when i log the response i get the following.
VALUE: 0
Im not sure if this is even making multiple http requests because the console log only returns one output instead of 10.
UPDATE
Ive updated the code to use retryWhen instead of retry, it does a delay of 2 seconds and will only take 10 errors before stopping.
Now i believe the problem is that it only makes 1 http request because the console log only returns a single output.
try use defer()
const $metrics = defer(()=>from(axios(this.getMetrics(session._id, sessionRequest._id, side))))
one thing to point out, you should inspect your network tab and see if the request is made in retry. your console.log is in map() operator which will be skipped when error thrown, could be why u don't see console.log there. You can try out the example below.
import { timer, interval,from } from 'rxjs';
import { map, tap, retryWhen, delayWhen,delay,take } from 'rxjs/operators';
//emit value every 1s
const source = from(fetch('http://kaksfk')).pipe(tap(val => console.log(`fetcching you won't see`)))
const example = source.pipe(
retryWhen(errors =>
errors.pipe(
// log error message
tap(val => console.log(`retrying`)),
// restart in 5 seconds
delay(2000),
take(5),
),
),
)

Serverless Lambda re-run function on error

My lamba function works the way it is supposed to work, however sometimes it gives an error. But with a manual re-run it works fine. So I was thinking to have an automatic retry when ever that error occurs.
Now I am new to Serverless so I was hoping to ask how do I do I invoke retries on failure/error. I have tried searching but was not successful. I'd be happy if someone can guide me with this.
Thanks :)
I think maybe you should look at your lambda maximum execution time.
By default it's 3 seconds, maybe sometimes your function is longer and then fails.
This document explains lambda retries.
Before you proceed, you need to make sure you understand where the error is being generated.
If your lambda function itself makes some kind of socket connection while it runs, and the lambda function gets a "Socket Hang Up" exception during execution, it probably returns a 5xx status code your calling application. You can have your lambda function retry whatever it was doing using any kind of common retry technique.
If your calling application is seeing the Socket Hang Up exception, then the issue is actually in the communication between your application and AWS Lambda, and adding retries inside your lambda function will be useless. You need to add the retries to your calling application.
Here's an example of what that might look like:
function getUsers() {
let retries = 0;
let maxAttempts = 3;
function attempt() {
return lambda.invoke({
ClientContext: "MyApp",
FunctionName: "GetUsers",
Payload: JSON.stringify({ Active: true })
}).promise().catch(error => {
// We don't want to retry on EVERY error, because the error might
// be an application error or an AWS credentials error. So here we
// check specifically for the types of errors to retry. AWS will
// helpfully set the "retryable" property if it's a network issue
// that they caught.
//
// A socket connection might not be an AWS-generated error, so you
// need to check it directly... this is just an example you can
// extend:
if (error.retryable || error.code === 'ECONNRESET') {
retries++;
if (retries < maxAttempts) {
return attempt();
}
}
throw error;
});
}
return attempt();
}

How to handle errors from parallel web requests using Retrofit + RxJava?

I have a situation like this where I make some web requests in parallel. Sometimes I make these calls and all requests see the same error (e.g. no-network):
void main() {
Observable.just("a", "b", "c")
.flatMap(s -> makeNetworkRequest())
.subscribe(
s -> {
// TODO
},
error -> {
// handle error
});
}
Observable<String> makeNetworkRequest() {
return Observable.error(new NoNetworkException());
}
class NoNetworkException extends Exception {
}
Depending on the timing, if one request emits the NoNetworkException before the others can, Retrofit/RxJava will dispose/interrupt** the others. I'll see one of the following logs (not all three) for each request remaining in progress++:
<-- HTTP FAILED: java.io.IOException: Canceled
<-- HTTP FAILED: java.io.InterruptedIOException
<-- HTTP FAILED: java.io.InterruptedIOException: thread interrupted
I'll be able to handle the NoNetworkException error in the subscriber and everything downstream will get disposed of and all is OK.
However based on timing, if two or more web requests emit NoNetworkException, then the first one will trigger the events above, disposing of everything down stream. The second NoNetworkException will have nowhere to go and I'll get the dreaded UndeliverableException. This is the same as example #1 documented here.
In the above article, the author suggested using an error handler. Obviously retry/retryWhen don't make sense if I expect to hear the same errors again. I don't understand how onErrorResumeNext/onErrorReturn help here, unless I map them to something recoverable to be handled downstream:
Observable.just("a", "b", "c")
.flatMap(s ->
makeNetworkRequest()
.onErrorReturn(error -> {
// eat actual error and return something else
return "recoverable error";
}))
.subscribe(
s -> {
if (s.equals("recoverable error")) {
// handle error
} else {
// TODO
}
},
error -> {
// handle error
});
but this seems wonky.
I know another solution is to set a global error handler with RxJavaPlugins.setErrorHandler(). This doesn't seem like a great solution either. I may want to handle NoNetworkException differently in different parts of my app.
So what other options to I have? What do other people do in this case? This must be pretty common.
** I don't fully understand who is interrupting/disposing of who. Is RxJava disposing of all other requests in flatmap which in turn causes Retrofit to cancel requests? Or does Retrofit cancel requests, resulting in each
request in flatmap emitting one of the above IOExceptions? I guess it doesn't really matter to answer the question, just curious.
++ It's possible that not all a, b, and c requests are in flight depending on thread pool.
Have you tried by using flatMap() with delayErrors=true?

How to specify HTTP timeout for DownloadURL() in Akavache?

I am developing an application targetting mobile devices, so I have to consider bad network connectivity. In one use case, I need to reduce the timeout for a request, because if no network is available, that's okay, and I'd fall back to default data immediately, without having the user wait for the HTTP response.
I found that HttpMixin.MakeWebRequest() has a timeout parameter (with default=null) but DownloadUrl() never makes use of it, so the forementioned function always waits for up to 15 seconds:
request.Timeout(timeout ?? TimeSpan.FromSeconds(15),
BlobCache.TaskpoolScheduler).Retry(retries);
So actually I do not have the option to use a different timeout, or am I missing something?
Thanks for considering a helpful response.
So after looking at the signature for DownloadUrl in
HttpMixin.cs
I saw what you are talking about and am not sure why it is there but, it looks like the timeout is related to building the request and not a timeout for the request itself.
That being said, in order to set a timeout with a download, you have a couple options that should work.
Via TPL aka Async Await
var timeout = 1000;
var task = BlobCache.LocalMachine.DownloadUrl("http://stackoverflow.com").FirstAsync().ToTask();
if (await Task.WhenAny(task, Task.Delay(timeout)) == task) {
// task completed within timeout
//Do Stuff with your byte data here
//var result = task.Result;
} else {
// timeout logic
}
Via Rx Observables
var obs = BlobCache.LocalMachine
.DownloadUrl("http://stackoverflow.com")
.Timeout(TimeSpan.FromSeconds(5))
.Retry(retryCount: 2);
var result = obs.Subscribe((byteData) =>
{
//Do Stuff with your byte data here
Debug.WriteLine("Byte Data Length " + byteData.Length);
}, (ex) => {
Debug.WriteLine("Handle your exceptions here." + ex.Message);
});

Express Node Request For Loop Issue [duplicate]

With node.js I want to http.get a number of remote urls in a way that only 10 (or n) runs at a time.
I also want to retry a request if an exception occures locally (m times), but when the status code returns an error (5XX, 4XX, etc) the request counts as valid.
This is really hard for me to wrap my head around.
Problems:
Cannot try-catch http.get as it is async.
Need a way to retry a request on failure.
I need some kind of semaphore that keeps track of the currently active request count.
When all requests finished I want to get the list of all request urls and response status codes in a list which I want to sort/group/manipulate, so I need to wait for all requests to finish.
Seems like for every async problem using promises are recommended, but I end up nesting too many promises and it quickly becomes uncypherable.
There are lots of ways to approach the 10 requests running at a time.
Async Library - Use the async library with the .parallelLimit() method where you can specify the number of requests you want running at one time.
Bluebird Promise Library - Use the Bluebird promise library and the request library to wrap your http.get() into something that can return a promise and then use Promise.map() with a concurrency option set to 10.
Manually coded - Code your requests manually to start up 10 and then each time one completes, start another one.
In all cases, you will have to manually write some retry code and as with all retry code, you will have to very carefully decide which types of errors you retry, how soon you retry them, how much you backoff between retry attempts and when you eventually give up (all things you have not specified).
Other related answers:
How to make millions of parallel http requests from nodejs app?
Million requests, 10 at a time - manually coded example
My preferred method is with Bluebird and promises. Including retry and result collection in order, that could look something like this:
const request = require('request');
const Promise = require('bluebird');
const get = Promise.promisify(request.get);
let remoteUrls = [...]; // large array of URLs
const maxRetryCnt = 3;
const retryDelay = 500;
Promise.map(remoteUrls, function(url) {
let retryCnt = 0;
function run() {
return get(url).then(function(result) {
// do whatever you want with the result here
return result;
}).catch(function(err) {
// decide what your retry strategy is here
// catch all errors here so other URLs continue to execute
if (err is of retry type && retryCnt < maxRetryCnt) {
++retryCnt;
// try again after a short delay
// chain onto previous promise so Promise.map() is still
// respecting our concurrency value
return Promise.delay(retryDelay).then(run);
}
// make value be null if no retries succeeded
return null;
});
}
return run();
}, {concurrency: 10}).then(function(allResults) {
// everything done here and allResults contains results with null for err URLs
});
The simple way is to use async library, it has a .parallelLimit method that does exactly what you need.

Resources