@@ -7,6 +7,9 @@ const fs = require('fs')
77const path = require ( 'path' )
88require ( 'dotenv' ) . config ( ) ;
99
10+ const DB_CONNECT_RETRY_MAX_ATTEMPTS = Number . parseInt ( process . env . DB_CONNECT_RETRY_MAX_ATTEMPTS || '24' , 10 )
11+ const DB_CONNECT_RETRY_DELAY_MS = Number . parseInt ( process . env . DB_CONNECT_RETRY_DELAY_MS || '5000' , 10 )
12+
1013// the env AWS_ENDPOINT_URL is automatically injected and available
1114const endpoint = process . env . AWS_ENDPOINT_URL ;
1215const url = new URL ( endpoint ) ;
@@ -21,19 +24,10 @@ const secrets = new AWS.SecretsManager({
2124} )
2225
2326exports . handler = async ( e ) => {
27+ let connection
2428 try {
2529 const { config } = e . params
26- const { password, username, dbname, port } = await getSecretValue ( config . credsSecretName )
27- const connection = mysql . createConnection ( {
28- host : hostname ,
29- user : username ,
30- database : dbname ,
31- port,
32- password,
33- multipleStatements : true
34- } )
35-
36- connection . connect ( )
30+ connection = await createConnectionWithRetry ( config . credsSecretName )
3731
3832 const sqlScript = fs . readFileSync ( path . join ( __dirname , 'script.sql' ) ) . toString ( )
3933 const res = await query ( connection , sqlScript )
@@ -48,6 +42,10 @@ exports.handler = async (e) => {
4842 err,
4943 message : err . message
5044 }
45+ } finally {
46+ if ( connection ) {
47+ await closeConnection ( connection )
48+ }
5149 }
5250}
5351
@@ -61,6 +59,61 @@ function query (connection, sql) {
6159 } )
6260}
6361
62+ async function createConnectionWithRetry ( connectionConfig ) {
63+ let lastError
64+ for ( let attempt = 1 ; attempt <= DB_CONNECT_RETRY_MAX_ATTEMPTS ; attempt += 1 ) {
65+ const { password, username, dbname, port } = await getSecretValue ( connectionConfig )
66+ const connection = mysql . createConnection ( {
67+ host : hostname ,
68+ user : username ,
69+ database : dbname ,
70+ port,
71+ password,
72+ multipleStatements : true
73+ } )
74+ try {
75+ await connect ( connection )
76+ return connection
77+ } catch ( error ) {
78+ connection . destroy ( )
79+ lastError = error
80+ if ( ! shouldRetryConnectionError ( error ) || attempt === DB_CONNECT_RETRY_MAX_ATTEMPTS ) {
81+ break
82+ }
83+
84+ const retryInSeconds = DB_CONNECT_RETRY_DELAY_MS / 1000
85+ console . log ( `Database connection attempt ${ attempt } /${ DB_CONNECT_RETRY_MAX_ATTEMPTS } failed (port=${ port } ) with '${ error . code || error . message } '. Retrying in ${ retryInSeconds } s...` )
86+ await sleep ( DB_CONNECT_RETRY_DELAY_MS )
87+ }
88+ }
89+
90+ throw lastError
91+ }
92+
93+ function connect ( connection ) {
94+ return new Promise ( ( resolve , reject ) => {
95+ connection . connect ( ( error ) => {
96+ if ( error ) return reject ( error )
97+
98+ return resolve ( )
99+ } )
100+ } )
101+ }
102+
103+ function shouldRetryConnectionError ( error ) {
104+ return [ 'ECONNREFUSED' , 'ETIMEDOUT' , 'EHOSTUNREACH' , 'ENOTFOUND' , 'PROTOCOL_CONNECTION_LOST' ] . includes ( error ?. code )
105+ }
106+
107+ function sleep ( delayMs ) {
108+ return new Promise ( ( resolve ) => setTimeout ( resolve , delayMs ) )
109+ }
110+
111+ function closeConnection ( connection ) {
112+ return new Promise ( ( resolve ) => {
113+ connection . end ( ( ) => resolve ( ) )
114+ } )
115+ }
116+
64117function getSecretValue ( secretId ) {
65118 return new Promise ( ( resolve , reject ) => {
66119 secrets . getSecretValue ( { SecretId : secretId } , ( err , data ) => {
0 commit comments