Skip to content

Commit

Permalink
make request timeout errors eligible for retry (#1104)
Browse files Browse the repository at this point in the history
  • Loading branch information
richardm-stripe authored Jan 15, 2021
1 parent 337ff6c commit e89cc76
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 17 deletions.
36 changes: 19 additions & 17 deletions lib/StripeResource.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,17 +98,7 @@ StripeResource.prototype = {
const timeoutErr = new TypeError('ETIMEDOUT');
timeoutErr.code = 'ETIMEDOUT';

req._isAborted = true;
req.abort();

callback.call(
this,
new StripeConnectionError({
message: `Request aborted due to timeout being reached (${timeout}ms)`,
detail: timeoutErr,
}),
null
);
req.destroy(timeoutErr);
};
},

Expand Down Expand Up @@ -224,11 +214,7 @@ StripeResource.prototype = {
},

_errorHandler(req, requestRetries, callback) {
return (error) => {
if (req._isAborted) {
// already handled
return;
}
return (message, detail) => {
callback.call(
this,
new StripeConnectionError({
Expand Down Expand Up @@ -486,7 +472,23 @@ StripeResource.prototype = {
null
);
} else {
return this._errorHandler(req, requestRetries, callback)(error);
if (error.code === 'ETIMEDOUT') {
return callback.call(
this,
new StripeConnectionError({
message: `Request aborted due to timeout being reached (${timeout}ms)`,
detail: error,
})
);
}
return callback.call(
this,
new StripeConnectionError({
message: this._generateConnectionErrorMessage(requestRetries),
detail: error,
}),
null
);
}
});

Expand Down
40 changes: 40 additions & 0 deletions test/StripeResource.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,46 @@ describe('StripeResource', () => {
});
});

it('throws an error on connection timeout', (done) => {
return utils.getTestServerStripe(
{timeout: 10},
(req, res) => {
// Do nothing. This will trigger a timeout.
},
(err, stripe) => {
if (err) {
return done(err);
}
stripe.charges.create(options.data, (err, result) => {
expect(err.detail.message).to.deep.equal('ETIMEDOUT');
done();
});
}
);
});

it('retries connection timeout errors', (done) => {
let nRequestsReceived = 0;
return utils.getTestServerStripe(
{timeout: 10, maxNetworkRetries: 2},
(req, res) => {
nRequestsReceived += 1;
// Do nothing. This will trigger a timeout.
return {shouldStayOpen: nRequestsReceived < 3};
},
(err, stripe) => {
if (err) {
return done(err);
}
stripe.charges.create(options.data, (err, result) => {
expect(err.detail.message).to.deep.equal('ETIMEDOUT');
expect(nRequestsReceived).to.equal(3);
done();
});
}
);
});

it('should retry the request if max retries are set', (done) => {
nock(`https://${options.host}`)
.post(options.path, options.params)
Expand Down
24 changes: 24 additions & 0 deletions testUtils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,33 @@ require('mocha');
// Ensure we are using the 'as promised' libs before any tests are run:
require('chai').use(require('chai-as-promised'));

const http = require('http');

const ResourceNamespace = require('../lib/ResourceNamespace').ResourceNamespace;

const utils = (module.exports = {
getTestServerStripe: (clientOptions, handler, callback) => {
const server = http.createServer((req, res) => {
const {shouldStayOpen} = handler(req, res) || {};
if (!shouldStayOpen) {
res.on('close', () => server.close());
}
});
server.listen(0, () => {
const {port} = server.address();
const stripe = require('../lib/stripe')(
module.exports.getUserStripeKey(),
{
host: 'localhost',
port,
protocol: 'http',
...clientOptions,
}
);
return callback(null, stripe);
});
},

getUserStripeKey: () => {
const key =
process.env.STRIPE_TEST_API_KEY || 'tGN0bIwXnHdwOa85VABjPdSn8nWY7G7I';
Expand Down

0 comments on commit e89cc76

Please sign in to comment.