Skip to content

Commit 79bed74

Browse files
committed
Added 429 handling and retries with jitter
1 parent 2b35d7a commit 79bed74

File tree

3 files changed

+135
-22
lines changed

3 files changed

+135
-22
lines changed

package-lock.json

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"test": "mocha --reporter spec"
88
},
99
"dependencies": {
10+
"async-await-retry": "^1.2.4",
1011
"chai": "^4.3.6",
1112
"cookie-parser": "~1.4.4",
1213
"cors": "2.8.5",

routes/proxypoc.js

Lines changed: 117 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,53 @@
11
var express = require('express');
2+
const retry = require('async-await-retry');
3+
const request = require('request');
24
var client = require("twilio")(process.env.TWIL_FLEX_ACCOUNT_SID, process.env.TWIL_FLEX_ACCOUNT_KEY);
35

46
const router = express.Router();
57
const numberPool = JSON.parse(process.env.NUMBER_POOL).sort();
68

9+
async function timeout(data) {
10+
const interval = data.exponential ? data.interval * data.factor : data.interval;
11+
// if interval is set to zero, do not use setTimeout, gain 1 event loop tick
12+
if (interval) await new Promise(r => setTimeout(r, interval + data.jitter));
13+
}
14+
15+
async function doCleanupConversation(conversationSid) {
16+
await new Promise((a) => {setTimeout(a, Math.random(10000) + 100)});
17+
if ( !conversationSid) {
18+
console.error('Underfined conversationSid');
19+
}
20+
const conversationInst = client.conversations.conversations(conversationSid);
21+
const res = await retry( async () => {
22+
return conversationInst.remove();
23+
},
24+
null,
25+
{
26+
onAttemptFail: async (data) => {
27+
console.warn(`Got error response while cleaning up convo ${conversationSid}: ${JSON.stringify(data)}`);
28+
if ( data.error.status === 429) {
29+
await timeout(data);
30+
return true;
31+
} else {
32+
console.error(`Got error response while cleaning up convo, not retrying ${conversationSid}: ${JSON.stringify(data)}`);
33+
return false; // dont retry
34+
}
35+
},
36+
retriesMax: 4,
37+
interval: 1000,
38+
exponential: true,
39+
factor: 3,
40+
jitter: 10000
41+
});
42+
return res;
43+
}
44+
745
async function cleanupConversation(conversationSid) {
846
try {
9-
await client.conversations.conversations(conversationSid).remove();
47+
await doCleanupConversation(conversationSid);
1048
console.log(`Removed new conversation successfully: ${conversationSid}`)
1149
} catch (removeError) {
12-
console.log(`Error occurred while removing ${conversationSid}: ${removeError}`);
50+
console.error(`Error occurred while removing ${conversationSid}: ${removeError}`);
1351
}
1452
}
1553

@@ -74,17 +112,64 @@ async function fetchProxyAddressesInOpenConversationsForAddress(address) {
74112
return proxyAddresses;
75113
}
76114

77-
// Helper function for create /Conversations endpoint
115+
116+
117+
118+
// Helper function for create /Conversations endpoint with retry handling
78119
async function createConversation(sessionOpts) {
79-
return client.conversations.conversations.create(sessionOpts);
120+
const res = await retry( async () => {
121+
return client.conversations.conversations.create(sessionOpts);
122+
},
123+
null,
124+
{
125+
onAttemptFail: async (data) => {
126+
console.warn(`Got error response when creating conversation ${JSON.stringify(data)}`);
127+
if ( data.error.status === 429) {
128+
await timeout(data);
129+
return true;
130+
} else {
131+
return false; // dont retry
132+
}
133+
},
134+
retriesMax: 4,
135+
interval: 1000,
136+
exponential: true,
137+
factor: 3,
138+
jitter: 1000
139+
});
140+
return res;
141+
}
142+
143+
async function doAddParticipantToConversation(conversationSid, address, proxyAddress) {
144+
const res = await retry( async () => {
145+
return client.conversations.conversations(conversationSid).participants.create({
146+
'messagingBinding.address': address,
147+
'messagingBinding.proxyAddress': proxyAddress
148+
});
149+
},
150+
null,
151+
{
152+
onAttemptFail: async (data) => {
153+
console.warn(`Got error response when adding participant ${address}:${proxyAddress} to ${conversationSid}: ${JSON.stringify(data)}`);
154+
if ( data.error.status === 429) {
155+
timeout(data);
156+
return true;
157+
} else {
158+
return false; // dont retry
159+
}
160+
},
161+
retriesMax: 4,
162+
interval: 1000,
163+
exponential: true,
164+
factor: 3,
165+
jitter: 1000
166+
});
167+
return res;
80168
}
81169

82170
// Helper function for Conversations create /Participants endpoint
83171
async function addParticipantToConversation(conversationSid, address, proxyAddress) {
84-
return client.conversations.conversations(conversationSid).participants.create({
85-
'messagingBinding.address': address,
86-
'messagingBinding.proxyAddress': proxyAddress
87-
})
172+
return doAddParticipantToConversation(conversationSid, address, proxyAddress);
88173
}
89174

90175
// Get the numbers in the numberPool that are not in activeSessionNumbers
@@ -102,31 +187,36 @@ function getSetOfAvailableNumbers(numberPool, activeSessionNumbers) {
102187
}
103188

104189
async function handleAddParticipant(conversationSid, address) {
105-
190+
console.log(`handleAddParticipant called with ${conversationSid} and ${address}`);
191+
106192
const openConversationsProxyAddresses = await fetchProxyAddressesInOpenConversationsForAddress(address);
107193
const availableNumbers = getSetOfAvailableNumbers(numberPool, openConversationsProxyAddresses);
108-
console.log(`Found proxy number candidates for ${address}: ${availableNumbers}`);
109194

110195
if ( availableNumbers.length === 0) {
196+
console.error(`No Proxy numbers to add ${address} to ${conversationSid}`);
111197
throw 'No proxy numbers available';
112-
}
198+
} else {
199+
console.log(`Found proxy number candidates for ${address}: ${availableNumbers}`);
200+
}
113201

114202
// We need to iterate over the list of available numbers until we add the participant successfully
115203
// This is needed since we may issue more than one request for a given number to add to a session
116204
// for e.g. a driver has multiple deliveries to make
205+
let lastError;
117206
for ( let i = 0; i < availableNumbers.length; ++i) {
118207
try {
119208
console.log(`Try add ${address} with proxy_address ${availableNumbers[i]} to conversation ${conversationSid}`);
120209
const participant = await addParticipantToConversation(conversationSid, address, availableNumbers[i]);
121210
console.log(`Added participant ${address} successfully: ${participant.sid} to ${conversationSid}`);
122211
return participant;
123212
} catch (e) {
124-
console.log(`Failed to add participant ${address} with proxy_address ${availableNumbers[i]}: ${JSON.stringify(e)}`)
213+
console.error(`Failed to add participant ${address} with proxy_address ${availableNumbers[i]}: ${JSON.stringify(e)}`)
214+
lastError = e;
125215
}
126216
}
127217

128-
// if we get here, it means we couldnt find a suitable number
129-
throw 'No proxy numbers available';
218+
// if we get here, it means we couldnt find a suitable number
219+
throw lastError;
130220
}
131221

132222
/*
@@ -190,14 +280,14 @@ router.use('/inboundCall', async function(req, res, next) {
190280
res.set('Content-Type', 'text/xml');
191281
res.send(twiml.toString())
192282
} catch(e) {
193-
console.log(`Something went wrong when handling inbound call ${e}`)
283+
console.error(`Something went wrong when handling inbound call ${e}`)
194284
res.send(500, e);
195285
}
196286
});
197287

198288
// Returns the Conversations reverse chrono order
199289
router.get('/sessions', async function(req, res, next) {
200-
const conversations = await client.conversations.conversations.list({limit: req.query.limit?parseInt(req.query.limit):20});
290+
const conversations = await client.conversations.conversations.list({limit: req.query.limit?parseInt(req.query.limit):200});
201291
conversations.sort((a, b) => {
202292
if ( a.state === b.state) {
203293
return b.dateCreated - a.dateCreated;
@@ -243,7 +333,7 @@ router.get('/participantsessions', async function(req, res, next) {
243333
return res.render('participantConversations', { title: 'Conversations', conversations });
244334
}
245335

246-
const conversations = await client.conversations.conversations.list({limit: 10});
336+
const conversations = await client.conversations.conversations.list({limit: 100});
247337
conversations.sort((a, b) => {
248338
return b.dateCreated - a.dateCreated;
249339
});
@@ -289,7 +379,7 @@ async function handleCreateSession(sessionOpts, addresses) {
289379
newConversation = await createConversation(sessionOpts);
290380
console.log(`Created new conversation successfully: ${newConversation.sid}`)
291381
} catch (e) {
292-
console.log(`Couldnt create a new session for ${JSON.stringify(addresses)}: ${e}`)
382+
console.error(`Couldnt create a new session for ${JSON.stringify(addresses)}: ${JSON.stringify(e)}`)
293383
const error = {
294384
message: 'Could not create session',
295385
raw_message: JSON.stringify(e),
@@ -303,7 +393,7 @@ async function handleCreateSession(sessionOpts, addresses) {
303393
for ( let i = 0; i < addresses.length; ++i) {
304394
participants[i] = await handleAddParticipant(newConversation.sid, addresses[i]);
305395
}
306-
396+
307397
const result = {
308398
sid: newConversation.sid,
309399
participants,
@@ -312,11 +402,16 @@ async function handleCreateSession(sessionOpts, addresses) {
312402
return result;
313403

314404
} catch(e) {
315-
console.log(`Couldnt add participants to a new session for ${JSON.stringify(addresses)}: ${e}`)
405+
console.error(`Couldnt add participants to a new session for ${JSON.stringify(addresses)}: ${e}`)
316406
if ( newConversation) {
317-
cleanupConversation(newConversation.sid);
407+
try {
408+
await cleanupConversation(newConversation.sid);
409+
} catch (ce) {
410+
console.log(`Couldnt clean up conversation ${newConversation.sid}: ${JSON.stringify(ce)}`);
411+
}
318412
}
319413
const error = {
414+
sid: newConversation.sid,
320415
message: 'Could not add participants session',
321416
raw_message: JSON.stringify(e),
322417
}
@@ -354,7 +449,7 @@ router.post('/sessions', async function(req, res, next) {
354449
return res.status(200).send(`${JSON.stringify(result)}`);
355450

356451
} catch(e) {
357-
console.log(`Couldnt create a new session for ${JSON.stringify(addresses)}: ${JSON.stringify(e)}`)
452+
console.error(`Couldnt create a new session for ${JSON.stringify(addresses)}: ${JSON.stringify(e)}`)
358453
return res.send(500, JSON.stringify(e));
359454
} finally {
360455
console.timeEnd('sessionCreate');

0 commit comments

Comments
 (0)