@@ -35,6 +35,32 @@ const headers = {
3535 'Access-Control-Allow-Origin' : '*'
3636} ;
3737
38+ const sleep = ( ms : number ) => new Promise ( resolve => setTimeout ( resolve , ms ) ) ;
39+
40+ async function sendCommandWithRetry ( command : any , maxRetries = 8 , initialDelay = 100 , maxDelay = 5000 ) {
41+ let retries = 0 ;
42+ while ( retries < maxRetries ) {
43+ try {
44+ // @ts -ignore
45+ return await ddbDocClient . send ( command ) ;
46+ } catch ( err : any ) {
47+ if ( [ 'ThrottlingException' , 'ProvisionedThroughputExceededException' , 'InternalServerError' , 'ServiceUnavailable' ] . includes ( err . name ) ) {
48+ retries ++ ;
49+ if ( retries >= maxRetries ) {
50+ console . error ( `Command failed after ${ maxRetries } retries.` ) ;
51+ throw err ;
52+ }
53+ const delay = Math . min ( initialDelay * Math . pow ( 2 , retries - 1 ) , maxDelay ) ;
54+ const jitter = delay * 0.1 * Math . random ( ) ;
55+ console . log ( `Retryable error (${ err . name } ) caught. Retrying in ${ Math . round ( delay + jitter ) } ms...` ) ;
56+ await sleep ( delay + jitter ) ;
57+ } else {
58+ throw err ;
59+ }
60+ }
61+ }
62+ }
63+
3864// Types
3965export type UserSettings = {
4066 [ k : string ] : any ;
@@ -234,7 +260,7 @@ async function getPlayersSlowly(playerIDs: string[]) {
234260 const players : FullUser [ ] = [ ] ;
235261 for ( const id of playerIDs ) {
236262 try {
237- const playerData = await ddbDocClient . send (
263+ const playerData = await sendCommandWithRetry (
238264 new GetCommand ( {
239265 TableName : process . env . ABSTRACT_PLAY_TABLE ,
240266 Key : {
@@ -255,29 +281,29 @@ function addToGameLists(type: string, game: Game, now: number, keepgame: boolean
255281 const work : Promise < any > [ ] = [ ] ;
256282 const sk = now + "#" + game . id ;
257283 if ( type === "COMPLETEDGAMES" && keepgame ) {
258- work . push ( ddbDocClient . send ( new PutCommand ( {
284+ work . push ( sendCommandWithRetry ( new PutCommand ( {
259285 TableName : process . env . ABSTRACT_PLAY_TABLE ,
260286 Item : {
261287 "pk" : type ,
262288 "sk" : sk ,
263289 ...game }
264290 } ) ) ) ;
265- work . push ( ddbDocClient . send ( new PutCommand ( {
291+ work . push ( sendCommandWithRetry ( new PutCommand ( {
266292 TableName : process . env . ABSTRACT_PLAY_TABLE ,
267293 Item : {
268294 "pk" : type + "#" + game . metaGame ,
269295 "sk" : sk ,
270296 ...game }
271297 } ) ) ) ;
272298 game . players . forEach ( ( player : { id : string ; } ) => {
273- work . push ( ddbDocClient . send ( new PutCommand ( {
299+ work . push ( sendCommandWithRetry ( new PutCommand ( {
274300 TableName : process . env . ABSTRACT_PLAY_TABLE ,
275301 Item : {
276302 "pk" : type + "#" + player . id ,
277303 "sk" : sk ,
278304 ...game }
279305 } ) ) ) ;
280- work . push ( ddbDocClient . send ( new PutCommand ( {
306+ work . push ( sendCommandWithRetry ( new PutCommand ( {
281307 TableName : process . env . ABSTRACT_PLAY_TABLE ,
282308 Item : {
283309 "pk" : type + "#" + game . metaGame + "#" + player . id ,
@@ -287,7 +313,7 @@ function addToGameLists(type: string, game: Game, now: number, keepgame: boolean
287313 } ) ;
288314 }
289315 if ( type === "CURRENTGAMES" ) {
290- work . push ( ddbDocClient . send ( new UpdateCommand ( {
316+ work . push ( sendCommandWithRetry ( new UpdateCommand ( {
291317 TableName : process . env . ABSTRACT_PLAY_TABLE ,
292318 Key : { "pk" : "METAGAMES" , "sk" : "COUNTS" } ,
293319 ExpressionAttributeNames : { "#g" : game . metaGame } ,
@@ -301,7 +327,7 @@ function addToGameLists(type: string, game: Game, now: number, keepgame: boolean
301327 update += ", #g.completedgames :n" ;
302328 eavObj [ ":n" ] = 1
303329 }
304- work . push ( ddbDocClient . send ( new UpdateCommand ( {
330+ work . push ( sendCommandWithRetry ( new UpdateCommand ( {
305331 TableName : process . env . ABSTRACT_PLAY_TABLE ,
306332 Key : { "pk" : "METAGAMES" , "sk" : "COUNTS" } ,
307333 ExpressionAttributeNames : { "#g" : game . metaGame } ,
@@ -352,7 +378,7 @@ async function startTournament(users: UserLastSeen[], tournament: Tournament) {
352378 // Cancel tournament. Everyone is gone.
353379 try {
354380 console . log ( `Deleting tournament ${ tournament . id } ` ) ;
355- await ddbDocClient . send (
381+ await sendCommandWithRetry (
356382 new DeleteCommand ( {
357383 TableName : process . env . ABSTRACT_PLAY_TABLE ,
358384 Key : {
@@ -361,7 +387,7 @@ async function startTournament(users: UserLastSeen[], tournament: Tournament) {
361387 } ,
362388 } ) ) ;
363389 const sk = tournament . metaGame + "#" + tournament . variants . sort ( ) . join ( "|" ) ;
364- await ddbDocClient . send (
390+ await sendCommandWithRetry (
365391 new UpdateCommand ( {
366392 TableName : process . env . ABSTRACT_PLAY_TABLE ,
367393 Key : { "pk" : "TOURNAMENTSCOUNTER" , "sk" : sk } ,
@@ -417,7 +443,7 @@ async function startTournament(users: UserLastSeen[], tournament: Tournament) {
417443 if ( tournament . waiting !== true ) {
418444 try {
419445 console . log ( `Updating tournament ${ tournament . id } to waiting` ) ;
420- await ddbDocClient . send ( new UpdateCommand ( {
446+ await sendCommandWithRetry ( new UpdateCommand ( {
421447 TableName : process . env . ABSTRACT_PLAY_TABLE ,
422448 Key : { "pk" : "TOURNAMENT" , "sk" : tournament . id } ,
423449 ExpressionAttributeValues : { ":t" : true } ,
@@ -463,7 +489,7 @@ async function startTournament(users: UserLastSeen[], tournament: Tournament) {
463489 player . sk = tournament . id + "#" + division . toString ( ) + '#' + player . playerid ;
464490 try {
465491 console . log ( `Adding player ${ player . playerid } to tournament ${ tournament . id } in division ${ division } ` ) ;
466- await ddbDocClient . send ( new PutCommand ( {
492+ await sendCommandWithRetry ( new PutCommand ( {
467493 TableName : process . env . ABSTRACT_PLAY_TABLE ,
468494 Item : player
469495 } ) ) ;
@@ -476,7 +502,7 @@ async function startTournament(users: UserLastSeen[], tournament: Tournament) {
476502 if ( division > 1 ) {
477503 try {
478504 console . log ( `Deleting player ${ player . playerid } from tournament ${ tournament . id } with division 1 (so they can be put in the right division)` ) ;
479- await ddbDocClient . send ( new DeleteCommand ( {
505+ await sendCommandWithRetry ( new DeleteCommand ( {
480506 TableName : process . env . ABSTRACT_PLAY_TABLE ,
481507 Key : {
482508 "pk" : "TOURNAMENTPLAYER" , "sk" : tournament . id + "#1#" + player . playerid
@@ -537,7 +563,7 @@ async function startTournament(users: UserLastSeen[], tournament: Tournament) {
537563 const state = engine . serialize ( ) ;
538564 try {
539565 console . log ( `Creating game ${ gameId } for tournament ${ tournament . id } with division ${ division } ` ) ;
540- await ddbDocClient . send ( new PutCommand ( {
566+ await sendCommandWithRetry ( new PutCommand ( {
541567 TableName : process . env . ABSTRACT_PLAY_TABLE ,
542568 Item : {
543569 "pk" : "GAME" ,
@@ -588,7 +614,7 @@ async function startTournament(users: UserLastSeen[], tournament: Tournament) {
588614 "player2" : gamePlayers [ 1 ] . id
589615 } ;
590616 console . log ( `Adding game ${ gameId } to TOURNAMENTGAME list` ) ;
591- await ddbDocClient . send ( new PutCommand ( {
617+ await sendCommandWithRetry ( new PutCommand ( {
592618 TableName : process . env . ABSTRACT_PLAY_TABLE ,
593619 Item : tournamentGame
594620 } ) ) ;
@@ -607,7 +633,7 @@ async function startTournament(users: UserLastSeen[], tournament: Tournament) {
607633 }
608634 const newTournamentid = uuid ( ) ;
609635 console . log ( `Updating tournament ${ tournament . id } to started` ) ;
610- await ddbDocClient . send ( new UpdateCommand ( {
636+ await sendCommandWithRetry ( new UpdateCommand ( {
611637 TableName : process . env . ABSTRACT_PLAY_TABLE ,
612638 Key : { "pk" : "TOURNAMENT" , "sk" : tournament . id } ,
613639 ExpressionAttributeValues : { ":dt" : now , ":t" : true , ":nextid" : newTournamentid , ":ds" : divisions } ,
@@ -616,7 +642,7 @@ async function startTournament(users: UserLastSeen[], tournament: Tournament) {
616642 // open next tournament for sign-up.
617643 console . log ( `Opening next tournament ${ newTournamentid } for sign-up. Update TOURNAMENTSCOUNTER for '${ tournament . metaGame } #${ tournament . variants . sort ( ) . join ( "|" ) } '` ) ;
618644 try {
619- await ddbDocClient . send ( new UpdateCommand ( {
645+ await sendCommandWithRetry ( new UpdateCommand ( {
620646 TableName : process . env . ABSTRACT_PLAY_TABLE ,
621647 Key : { "pk" : "TOURNAMENTSCOUNTER" , "sk" : tournament . metaGame + "#" + tournament . variants . sort ( ) . join ( "|" ) } ,
622648 ExpressionAttributeValues : { ":inc" : 1 , ":f" : false } ,
@@ -641,7 +667,7 @@ async function startTournament(users: UserLastSeen[], tournament: Tournament) {
641667 } ;
642668 console . log ( `Creating new tournament ${ newTournamentid } ` ) ;
643669 try {
644- await ddbDocClient . send ( new PutCommand ( {
670+ await sendCommandWithRetry ( new PutCommand ( {
645671 TableName : process . env . ABSTRACT_PLAY_TABLE ,
646672 Item : data
647673 } ) ) ;
@@ -667,7 +693,7 @@ async function startTournament(users: UserLastSeen[], tournament: Tournament) {
667693 } ;
668694 try {
669695 console . log ( `Adding player ${ player . playerid } to new tournament ${ newTournamentid } ` ) ;
670- await ddbDocClient . send ( new PutCommand ( {
696+ await sendCommandWithRetry ( new PutCommand ( {
671697 TableName : process . env . ABSTRACT_PLAY_TABLE ,
672698 Item : playerdata
673699 } ) ) ;
@@ -704,7 +730,7 @@ async function startTournament(users: UserLastSeen[], tournament: Tournament) {
704730 if ( remove . length > 0 ) {
705731 for ( const player of remove ) {
706732 console . log ( `Deleting tournament player record for ${ player . playerid } from tournament ${ tournament . id } ` ) ;
707- await ddbDocClient . send (
733+ await sendCommandWithRetry (
708734 new DeleteCommand ( {
709735 TableName : process . env . ABSTRACT_PLAY_TABLE ,
710736 Key : {
@@ -754,7 +780,7 @@ async function updateUserGames(userId: string, gamesUpdate: undefined | number,
754780 gameIDsChanged . length = 0 ;
755781 if ( gamesUpdate === undefined ) {
756782 // Update "old" users. This is a one-time update.
757- return ddbDocClient . send ( new UpdateCommand ( {
783+ return sendCommandWithRetry ( new UpdateCommand ( {
758784 TableName : process . env . ABSTRACT_PLAY_TABLE ,
759785 Key : { "pk" : "USER" , "sk" : userId } ,
760786 ExpressionAttributeValues : { ":val" : 1 , ":gs" : games } ,
@@ -763,7 +789,7 @@ async function updateUserGames(userId: string, gamesUpdate: undefined | number,
763789 } else {
764790 console . log ( `updateUserGames: optimistically updating games for ${ userId } ` ) ;
765791 try {
766- await ddbDocClient . send ( new UpdateCommand ( {
792+ await sendCommandWithRetry ( new UpdateCommand ( {
767793 TableName : process . env . ABSTRACT_PLAY_TABLE ,
768794 Key : { "pk" : "USER" , "sk" : userId } ,
769795 ExpressionAttributeValues : { ":val" : gamesUpdate , ":inc" : 1 , ":gs" : games } ,
@@ -777,7 +803,7 @@ async function updateUserGames(userId: string, gamesUpdate: undefined | number,
777803 console . log ( `updateUserGames: games has been modified by another process for ${ userId } ` ) ;
778804 let count = 0 ;
779805 while ( count < 3 ) {
780- const userData = await ddbDocClient . send (
806+ const userData = await sendCommandWithRetry (
781807 new GetCommand ( {
782808 TableName : process . env . ABSTRACT_PLAY_TABLE ,
783809 Key : {
@@ -801,7 +827,7 @@ async function updateUserGames(userId: string, gamesUpdate: undefined | number,
801827 }
802828 try {
803829 console . log ( `updateUserGames: Update ${ count } of games for user` , userId , newgames ) ;
804- await ddbDocClient . send ( new UpdateCommand ( {
830+ await sendCommandWithRetry ( new UpdateCommand ( {
805831 TableName : process . env . ABSTRACT_PLAY_TABLE ,
806832 Key : { "pk" : "USER" , "sk" : userId } ,
807833 ExpressionAttributeValues : { ":val" : gamesUpdate , ":inc" : 1 , ":gs" : newgames } ,
@@ -810,7 +836,11 @@ async function updateUserGames(userId: string, gamesUpdate: undefined | number,
810836 } ) ) ;
811837 return ;
812838 } catch ( err : any ) {
813- count ++ ;
839+ if ( err . name === 'ConditionalCheckFailedException' ) {
840+ count ++ ;
841+ } else {
842+ throw err ;
843+ }
814844 }
815845 }
816846 new Error ( `updateUserGames: Unable to update games for user ${ userId } after 3 retries` ) ;
0 commit comments