@@ -22,7 +22,6 @@ import {IpAddressTypes} from './ip-addresses';
2222import { AuthTypes } from './auth-types' ;
2323import { SQLAdminFetcher } from './sqladmin-fetcher' ;
2424import { CloudSQLConnectorError } from './errors' ;
25- import { isSameInstance , resolveInstanceName } from './parse-instance-connection-name' ;
2625
2726// These Socket types are subsets from nodejs definitely typed repo, ref:
2827// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/ae0fe42ff0e6e820e8ae324acf4f8e944aa1b2b7/types/node/v18/net.d.ts#L437
@@ -45,6 +44,7 @@ export declare interface ConnectionOptions {
4544 ipType ?: IpAddressTypes ;
4645 instanceConnectionName : string ;
4746 domainName ?: string ;
47+ limitRateInterval ?: number ;
4848}
4949
5050export declare interface SocketConnectionOptions extends ConnectionOptions {
@@ -74,11 +74,24 @@ export declare interface TediousDriverOptions {
7474 connector : PromisedStreamFunction ;
7575 encrypt : boolean ;
7676}
77+ class CacheEntry {
78+ promise : Promise < CloudSQLInstance > ;
79+ instance ?: CloudSQLInstance ;
80+
81+ constructor ( promise : Promise < CloudSQLInstance > ) {
82+ this . promise = promise ;
83+ this . promise . then ( inst => ( this . instance = inst ) ) ;
84+ }
85+
86+ isResolved ( ) : boolean {
87+ return Boolean ( this . instance ) ;
88+ }
89+ }
7790
7891// Internal mapping of the CloudSQLInstances that
7992// adds extra logic to async initialize items.
80- class CloudSQLInstanceMap extends Map < string , CloudSQLInstance > {
81- private readonly sqlAdminFetcher : SQLAdminFetcher
93+ class CloudSQLInstanceMap extends Map < string , CacheEntry > {
94+ private readonly sqlAdminFetcher : SQLAdminFetcher ;
8295
8396 constructor ( sqlAdminFetcher : SQLAdminFetcher ) {
8497 super ( ) ;
@@ -91,53 +104,59 @@ class CloudSQLInstanceMap extends Map<string,CloudSQLInstance> {
91104 // https://github.com/GoogleCloudPlatform/cloud-sql-nodejs-connector/pull/426
92105 // then the cache key should contain both the domain name
93106 // and the resolved instance name.
94- return ( opts . instanceConnectionName || opts . domainName ) + "-" +
95- opts . authType + "-" + opts . ipType ;
107+ return (
108+ ( opts . instanceConnectionName || opts . domainName ) +
109+ '-' +
110+ opts . authType +
111+ '-' +
112+ opts . ipType
113+ ) ;
96114 }
97115
98116 async loadInstance ( opts : ConnectionOptions ) : Promise < void > {
99117 // in case an instance to that connection name has already
100118 // been setup there's no need to set it up again
101- if ( this . has ( this . cacheKey ( opts ) ) ) {
102- const instance = this . get ( this . cacheKey ( opts ) ) ;
103- let oldInfo = instance ?. instanceInfo
104- if ( oldInfo && oldInfo . domainName ) {
105- // configured with domain name
106- let newInfo = await resolveInstanceName ( undefined , oldInfo . domainName ) ;
107- if ( ! isSameInstance ( oldInfo , newInfo ) ) {
108- // Domain name changed. Close and remove, then create a new map entry.
109- instance ?. close ( ) ;
110- this . delete ( this . cacheKey ( opts ) )
111- } else {
112- // Domain name resolves to the same instance, do nothing.
113- return
114- }
115- } else {
116- // Configured with instance name. Existing map entry is OK.
119+ const entry = this . get ( this . cacheKey ( opts ) ) ;
120+ if ( entry ) {
121+ if ( ! entry . isResolved ( ) ) {
122+ // A cache entry request is in progress.
123+ await entry . promise ;
117124 return ;
125+ } else {
126+ // Check the domain name, then if the instance is still open
127+ // return it
128+ if ( ! entry . instance ?. isClosed ( ) ) {
129+ // The instance is open and the domain has not changed.
130+ // use the cached instance.
131+ return ;
132+ }
118133 }
119134 }
120135
121- const connectionInstance = await CloudSQLInstance . getCloudSQLInstance ( {
136+ // Start the refresh and add a cache entry immediately.
137+ const promise = CloudSQLInstance . getCloudSQLInstance ( {
122138 instanceConnectionName : opts . instanceConnectionName ,
123139 domainName : opts . domainName ,
124140 authType : opts . authType || AuthTypes . PASSWORD ,
125141 ipType : opts . ipType || IpAddressTypes . PUBLIC ,
126- limitRateInterval : 30 * 1000 , // 30 sec
142+ limitRateInterval : opts . limitRateInterval || 30 * 1000 , // 30 sec
127143 sqlAdminFetcher : this . sqlAdminFetcher ,
128144 } ) ;
129- this . set ( this . cacheKey ( opts ) , connectionInstance ) ;
145+ this . set ( this . cacheKey ( opts ) , new CacheEntry ( promise ) ) ;
146+
147+ // Wait for the cache entry to resolve.
148+ await promise ;
130149 }
131150
132151 getInstance ( opts : ConnectionOptions ) : CloudSQLInstance {
133152 const connectionInstance = this . get ( this . cacheKey ( opts ) ) ;
134- if ( ! connectionInstance ) {
153+ if ( ! connectionInstance || ! connectionInstance . instance ) {
135154 throw new CloudSQLConnectorError ( {
136155 message : `Cannot find info for instance: ${ opts . instanceConnectionName } ` ,
137156 code : 'ENOINSTANCEINFO' ,
138157 } ) ;
139158 }
140- return connectionInstance ;
159+ return connectionInstance . instance ;
141160 }
142161}
143162
@@ -183,28 +202,13 @@ export class Connector {
183202 // });
184203 // const pool = new Pool(opts)
185204 // const res = await pool.query('SELECT * FROM pg_catalog.pg_tables;')
186- async getOptions ( {
187- authType = AuthTypes . PASSWORD ,
188- ipType = IpAddressTypes . PUBLIC ,
189- instanceConnectionName,
190- domainName,
191- } : ConnectionOptions ) : Promise < DriverOptions > {
205+ async getOptions ( opts : ConnectionOptions ) : Promise < DriverOptions > {
192206 const { instances} = this ;
193- await instances . loadInstance ( {
194- ipType,
195- authType,
196- instanceConnectionName,
197- domainName
198- } ) ;
207+ await instances . loadInstance ( opts ) ;
199208
200209 return {
201210 stream ( ) {
202- const cloudSqlInstance = instances . getInstance ( {
203- ipType,
204- instanceConnectionName,
205- domainName,
206- authType,
207- } ) ;
211+ const cloudSqlInstance = instances . getInstance ( opts ) ;
208212 const {
209213 instanceInfo,
210214 ephemeralCert,
@@ -252,10 +256,10 @@ export class Connector {
252256 }
253257
254258 async getTediousOptions ( {
255- authType,
256- ipType,
257- instanceConnectionName,
258- } : ConnectionOptions ) : Promise < TediousDriverOptions > {
259+ authType,
260+ ipType,
261+ instanceConnectionName,
262+ } : ConnectionOptions ) : Promise < TediousDriverOptions > {
259263 if ( authType === AuthTypes . IAM ) {
260264 throw new CloudSQLConnectorError ( {
261265 message : 'Tedious does not support Auto IAM DB Authentication' ,
@@ -292,11 +296,11 @@ export class Connector {
292296 // `postgresql://${user}@localhost /${database}?host=${process.cwd()}`;
293297 // const prisma = new PrismaClient({ datasourceUrl });
294298 async startLocalProxy ( {
295- authType,
296- ipType,
297- instanceConnectionName,
298- listenOptions,
299- } : SocketConnectionOptions ) : Promise < void > {
299+ authType,
300+ ipType,
301+ instanceConnectionName,
302+ listenOptions,
303+ } : SocketConnectionOptions ) : Promise < void > {
300304 const { stream} = await this . getOptions ( {
301305 authType,
302306 ipType,
@@ -337,7 +341,7 @@ export class Connector {
337341 // Also clear up any local proxy servers and socket connections.
338342 close ( ) : void {
339343 for ( const instance of this . instances . values ( ) ) {
340- instance . close ( ) ;
344+ instance . promise . then ( inst => inst . close ( ) ) ;
341345 }
342346 for ( const server of this . localProxies ) {
343347 server . close ( ) ;
0 commit comments