@@ -14,7 +14,7 @@ const hanaKeywords = keywords.reduce((prev, curr) => {
1414 return prev
1515} , { } )
1616
17- const DEBUG = cds . debug ( 'sql|db' )
17+ const LOG = cds . log ( 'sql|db' ) , DEBUG = cds . debug ( 'sql|db' )
1818const SYSTEM_VERSIONED = '@hana.systemversioned'
1919
2020/**
@@ -50,47 +50,68 @@ class HANAService extends SQLService {
5050 }
5151 const isMultitenant = ! ! service . options . credentials . sm_url || ! ! service . options . credentials . baseurl || ( 'multiTenant' in this . options ? this . options . multiTenant : cds . env . requires . multitenancy )
5252 const acquireTimeoutMillis = this . options . pool ?. acquireTimeoutMillis || ( cds . env . profiles . includes ( 'production' ) ? 1000 : 10000 )
53+ const maxRetries = 3
54+
55+ async function createSingleTenant ( tenant , attempt = 1 ) {
56+ try {
57+ const dbc = new driver ( { ...service . options . credentials , ...clientOptions } )
58+ await dbc . connect ( )
59+ service . server . major = dbc . server . major || service . server . major
60+ return dbc
61+ } catch ( err ) {
62+ if ( err . code === 10 ) return // authentication error, see error handler below
63+ if ( attempt < maxRetries ) {
64+ LOG . debug ( 'connection failed:' , err , '- retrying attempt' , attempt , 'of' , maxRetries )
65+ return createSingleTenant ( tenant , attempt + 1 )
66+ }
67+ throw err
68+ }
69+ }
70+
71+ async function createMultiTenant ( tenant , start = Date . now ( ) , attempt = 1 ) {
72+ let credentials
73+ try {
74+ const smTenant = await require ( '@sap/cds-mtxs/lib' ) . xt . serviceManager . get ( tenant , { disableCache : false } )
75+ credentials = smTenant . credentials
76+ const dbc = new driver ( { ...credentials , ...clientOptions } )
77+ await dbc . connect ( )
78+ service . server . major = dbc . server . major || service . server . major
79+ return dbc
80+ } catch ( err ) {
81+ if ( cds . requires . db ?. pool ?. builtin !== false && cds . env . features . pool !== 'generic-pool' ) {
82+ if ( err . status === 404 || err . status === 429 ) {
83+ throw new Error ( `Pool failed connecting to '${ tenant } '` , { cause : err } )
84+ }
85+ const deadline = start + acquireTimeoutMillis
86+ if ( attempt <= maxRetries && Date . now ( ) < deadline ) {
87+ // Retry transient connection failures before invalidating credentials
88+ LOG . debug ( 'connection failed:' , err , '- retrying attempt' , attempt , 'of' , maxRetries )
89+ return createMultiTenant ( tenant , start , attempt + 1 )
90+ }
91+ try {
92+ await require ( '@sap/cds-mtxs/lib' ) . xt . serviceManager . get ( tenant , { invalidCredentials : credentials , retryUntil : deadline } )
93+ } catch ( smErr ) {
94+ smErr . cause = err
95+ throw new Error ( `Failed connecting to pool - could not get valid credentials from Service Manager` , { cause : smErr } )
96+ }
97+ if ( Date . now ( ) < deadline ) return createMultiTenant ( tenant , start )
98+ else throw new Error ( `Pool exceeded for '${ tenant } ' within ${ acquireTimeoutMillis } ms` , { cause : err } )
99+ } else {
100+ // Stop trying when the tenant does not exist or is rate limited
101+ if ( err . status == 404 || err . status == 429 ) {
102+ return new Promise ( function ( _ , reject ) { // break retry loop for generic-pool
103+ setTimeout ( ( ) => reject ( err ) , acquireTimeoutMillis )
104+ } )
105+ }
106+ await require ( '@sap/cds-mtxs/lib' ) . xt . serviceManager . get ( tenant , { disableCache : true } )
107+ throw new Error ( `Pool failed connecting to'${ tenant } '` , { cause : err } ) // generic-pool will retry on errors
108+ }
109+ }
110+ }
111+
53112 return {
54113 options : this . options . pool || { } ,
55- create : async function create ( tenant , start = Date . now ( ) ) {
56- let credentials
57- try {
58- const smTenant = isMultitenant
59- ? await require ( '@sap/cds-mtxs/lib' ) . xt . serviceManager . get ( tenant , { disableCache : false } )
60- : service . options
61- credentials = smTenant . credentials
62- const dbc = new driver ( { ...credentials , ...clientOptions } )
63- await dbc . connect ( )
64- service . server . major = dbc . server . major || service . server . major
65- return dbc
66- } catch ( err ) {
67- if ( isMultitenant ) {
68- if ( cds . requires . db ?. pool ?. builtin !== false && cds . env . features . pool !== 'generic-pool' ) {
69- if ( err . status === 404 || err . status === 429 ) {
70- throw new Error ( `Pool failed connecting to '${ tenant } '` , { cause : err } )
71- }
72- const deadline = start + acquireTimeoutMillis
73- try {
74- await require ( '@sap/cds-mtxs/lib' ) . xt . serviceManager . get ( tenant , { disableCache : true , invalidCredentials : credentials , retryUntil : deadline } )
75- } catch ( smErr ) {
76- smErr . cause = err
77- throw new Error ( `Failed connecting to pool - could not get valid credentials from Service Manager` , { cause : smErr } )
78- }
79- if ( Date . now ( ) < deadline ) return create ( tenant , start )
80- else throw new Error ( `Pool exceeded for '${ tenant } ' within ${ acquireTimeoutMillis } ms` , { cause : err } )
81- } else {
82- // Stop trying when the tenant does not exist or is rate limited
83- if ( err . status == 404 || err . status == 429 ) {
84- return new Promise ( function ( _ , reject ) { // break retry loop for generic-pool
85- setTimeout ( ( ) => reject ( err ) , acquireTimeoutMillis )
86- } )
87- }
88- await require ( '@sap/cds-mtxs/lib' ) . xt . serviceManager . get ( tenant , { disableCache : true } )
89- throw new Error ( `Pool failed connecting to'${ tenant } '` , { cause : err } ) // generic-pool will retry on errors
90- }
91- } else if ( err . code !== 10 ) throw err
92- }
93- } ,
114+ create : isMultitenant ? createMultiTenant : createSingleTenant ,
94115 error : ( err /*, tenant*/ ) => {
95116 // Check whether the connection error was an authentication error
96117 if ( err . code === 10 ) {
0 commit comments