Skip to content

Commit 3f17979

Browse files
committed
refactor: Config has separate fields for instanceConnectionName and domainName
wip: periodic check for domain change wip: periodic checks wip: close sockets on instance closed
1 parent 3126f68 commit 3f17979

File tree

9 files changed

+163
-189
lines changed

9 files changed

+163
-189
lines changed

src/cloud-sql-instance.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@ export class CloudSQLInstance {
5757
): Promise<CloudSQLInstance> {
5858
const instance = new CloudSQLInstance({
5959
options: options,
60-
instanceInfo: await resolveInstanceName(options.instanceConnectionName, options.domainName),
60+
instanceInfo: await resolveInstanceName(
61+
options.instanceConnectionName,
62+
options.domainName
63+
),
6164
});
6265
await instance.refresh();
6366
return instance;

src/connector.ts

Lines changed: 58 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {IpAddressTypes} from './ip-addresses';
2222
import {AuthTypes} from './auth-types';
2323
import {SQLAdminFetcher} from './sqladmin-fetcher';
2424
import {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

5050
export 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();

src/instance-connection-info.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,4 @@ export interface InstanceConnectionInfo {
1717
regionId: string;
1818
instanceId: string;
1919
domainName: string | undefined;
20-
21-
2220
}

src/parse-instance-connection-name.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,16 @@ import {InstanceConnectionInfo} from './instance-connection-info';
1616
import {CloudSQLConnectorError} from './errors';
1717
import {resolveTxtRecord} from './dns-lookup';
1818

19-
export function isSameInstance(a: InstanceConnectionInfo, b: InstanceConnectionInfo): boolean {
20-
return a.instanceId == b.instanceId &&
21-
a.regionId == b.regionId &&
22-
a.projectId == b.projectId &&
23-
a.domainName == b.domainName;
19+
export function isSameInstance(
20+
a: InstanceConnectionInfo,
21+
b: InstanceConnectionInfo
22+
): boolean {
23+
return (
24+
a.instanceId === b.instanceId &&
25+
a.regionId === b.regionId &&
26+
a.projectId === b.projectId &&
27+
a.domainName === b.domainName
28+
);
2429
}
2530

2631
export async function resolveInstanceName(
@@ -33,7 +38,10 @@ export async function resolveInstanceName(
3338
'Missing instance connection name, expected: "PROJECT:REGION:INSTANCE" or a valid domain name.',
3439
code: 'ENOCONNECTIONNAME',
3540
});
36-
} else if (instanceConnectionName && isInstanceConnectionName(instanceConnectionName)) {
41+
} else if (
42+
instanceConnectionName &&
43+
isInstanceConnectionName(instanceConnectionName)
44+
) {
3745
return parseInstanceConnectionName(instanceConnectionName);
3846
} else if (domainName && isValidDomainName(domainName)) {
3947
return await resolveDomainName(domainName);

system-test/pg-connect.cjs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,7 @@ t.test(
141141
async t => {
142142
const connector = new Connector();
143143
const clientOpts = await connector.getOptions({
144-
instanceConnectionName: String(
145-
process.env.POSTGRES_CUSTOMER_CAS_DOMAIN_NAME
146-
),
144+
domainName: String(process.env.POSTGRES_CUSTOMER_CAS_DOMAIN_NAME),
147145
});
148146
const client = new Client({
149147
...clientOpts,
@@ -173,9 +171,7 @@ t.test(
173171
async t => {
174172
const connector = new Connector();
175173
const clientOpts = await connector.getOptions({
176-
instanceConnectionName: String(
177-
process.env.POSTGRES_CUSTOMER_CAS_INVALID_DOMAIN_NAME
178-
),
174+
domainName: String(process.env.POSTGRES_CUSTOMER_CAS_INVALID_DOMAIN_NAME),
179175
});
180176
const client = new Client({
181177
...clientOpts,
@@ -202,3 +198,4 @@ t.test(
202198
}
203199
}
204200
);
201+

system-test/pg-connect.mjs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,7 @@ t.test(
142142
async t => {
143143
const connector = new Connector();
144144
const clientOpts = await connector.getOptions({
145-
instanceConnectionName: String(
146-
process.env.POSTGRES_CUSTOMER_CAS_DOMAIN_NAME
147-
),
145+
domainName: String(process.env.POSTGRES_CUSTOMER_CAS_DOMAIN_NAME),
148146
});
149147
const client = new Client({
150148
...clientOpts,
@@ -174,9 +172,7 @@ t.test(
174172
async t => {
175173
const connector = new Connector();
176174
const clientOpts = await connector.getOptions({
177-
instanceConnectionName: String(
178-
process.env.POSTGRES_CUSTOMER_CAS_INVALID_DOMAIN_NAME
179-
),
175+
domainName: String(process.env.POSTGRES_CUSTOMER_CAS_INVALID_DOMAIN_NAME),
180176
});
181177
const client = new Client({
182178
...clientOpts,

system-test/pg-connect.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,7 @@ t.test(
145145
async t => {
146146
const connector = new Connector();
147147
const clientOpts = await connector.getOptions({
148-
domainName: String(
149-
process.env.POSTGRES_CUSTOMER_CAS_DOMAIN_NAME
150-
),
148+
domainName: String(process.env.POSTGRES_CUSTOMER_CAS_DOMAIN_NAME),
151149
});
152150
const client = new Client({
153151
...clientOpts,
@@ -177,9 +175,7 @@ t.test(
177175
async t => {
178176
const connector = new Connector();
179177
const clientOpts = await connector.getOptions({
180-
domainName: String(
181-
process.env.POSTGRES_CUSTOMER_CAS_INVALID_DOMAIN_NAME
182-
),
178+
domainName: String(process.env.POSTGRES_CUSTOMER_CAS_INVALID_DOMAIN_NAME),
183179
});
184180
const client = new Client({
185181
...clientOpts,

0 commit comments

Comments
 (0)