@@ -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
@@ -74,11 +73,24 @@ export declare interface TediousDriverOptions {
7473 connector : PromisedStreamFunction ;
7574 encrypt : boolean ;
7675}
76+ class CacheEntry {
77+ promise : Promise < CloudSQLInstance > ;
78+ instance ?: CloudSQLInstance ;
79+
80+ constructor ( promise : Promise < CloudSQLInstance > ) {
81+ this . promise = promise ;
82+ this . promise . then ( inst => ( this . instance = inst ) ) ;
83+ }
84+
85+ isResolved ( ) : boolean {
86+ return Boolean ( this . instance ) ;
87+ }
88+ }
7789
7890// Internal mapping of the CloudSQLInstances that
7991// adds extra logic to async initialize items.
80- class CloudSQLInstanceMap extends Map < string , CloudSQLInstance > {
81- private readonly sqlAdminFetcher : SQLAdminFetcher
92+ class CloudSQLInstanceMap extends Map < string , CacheEntry > {
93+ private readonly sqlAdminFetcher : SQLAdminFetcher ;
8294
8395 constructor ( sqlAdminFetcher : SQLAdminFetcher ) {
8496 super ( ) ;
@@ -91,53 +103,60 @@ class CloudSQLInstanceMap extends Map<string,CloudSQLInstance> {
91103 // https://github.com/GoogleCloudPlatform/cloud-sql-nodejs-connector/pull/426
92104 // then the cache key should contain both the domain name
93105 // and the resolved instance name.
94- return ( opts . instanceConnectionName || opts . domainName ) + "-" +
95- opts . authType + "-" + opts . ipType ;
106+ return (
107+ ( opts . instanceConnectionName || opts . domainName ) +
108+ '-' +
109+ opts . authType +
110+ '-' +
111+ opts . ipType
112+ ) ;
96113 }
97114
98115 async loadInstance ( opts : ConnectionOptions ) : Promise < void > {
99116 // in case an instance to that connection name has already
100117 // 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.
118+ const entry = this . get ( this . cacheKey ( opts ) ) ;
119+ if ( entry ) {
120+ if ( ! entry . isResolved ( ) ) {
121+ // A cache entry request is in progress.
122+ await entry . promise ;
117123 return ;
124+ } else {
125+ // Check the domain name, then if the instance is still open
126+ // return it
127+ await entry . instance ?. checkDomainChanged ( ) ;
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 ,
126142 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
@@ -185,17 +204,17 @@ export class Connector {
185204 // const pool = new Pool(opts)
186205 // const res = await pool.query('SELECT * FROM pg_catalog.pg_tables;')
187206 async getOptions ( {
188- authType = AuthTypes . PASSWORD ,
189- ipType = IpAddressTypes . PUBLIC ,
190- instanceConnectionName,
191- domainName,
192- } : ConnectionOptions ) : Promise < DriverOptions > {
207+ authType = AuthTypes . PASSWORD ,
208+ ipType = IpAddressTypes . PUBLIC ,
209+ instanceConnectionName,
210+ domainName,
211+ } : ConnectionOptions ) : Promise < DriverOptions > {
193212 const { instances} = this ;
194213 await instances . loadInstance ( {
195214 ipType,
196215 authType,
197216 instanceConnectionName,
198- domainName
217+ domainName,
199218 } ) ;
200219
201220 return {
@@ -253,10 +272,10 @@ export class Connector {
253272 }
254273
255274 async getTediousOptions ( {
256- authType,
257- ipType,
258- instanceConnectionName,
259- } : ConnectionOptions ) : Promise < TediousDriverOptions > {
275+ authType,
276+ ipType,
277+ instanceConnectionName,
278+ } : ConnectionOptions ) : Promise < TediousDriverOptions > {
260279 if ( authType === AuthTypes . IAM ) {
261280 throw new CloudSQLConnectorError ( {
262281 message : 'Tedious does not support Auto IAM DB Authentication' ,
@@ -293,11 +312,11 @@ export class Connector {
293312 // `postgresql://${user}@localhost /${database}?host=${process.cwd()}`;
294313 // const prisma = new PrismaClient({ datasourceUrl });
295314 async startLocalProxy ( {
296- authType,
297- ipType,
298- instanceConnectionName,
299- listenOptions,
300- } : SocketConnectionOptions ) : Promise < void > {
315+ authType,
316+ ipType,
317+ instanceConnectionName,
318+ listenOptions,
319+ } : SocketConnectionOptions ) : Promise < void > {
301320 const { stream} = await this . getOptions ( {
302321 authType,
303322 ipType,
@@ -339,7 +358,7 @@ export class Connector {
339358 close ( ) : void {
340359 this . closed = true ;
341360 for ( const instance of this . instances . values ( ) ) {
342- instance . close ( ) ;
361+ instance . promise . then ( inst => inst . close ( ) ) ;
343362 }
344363 for ( const server of this . localProxies ) {
345364 server . close ( ) ;
@@ -349,7 +368,7 @@ export class Connector {
349368 }
350369 }
351370
352- isClosed ( ) :boolean {
371+ isClosed ( ) : boolean {
353372 return this . closed ;
354373 }
355374}
0 commit comments