Skip to content

Commit f4f69c1

Browse files
committed
refactor: Add CloudSqlInstance.close() to stop the refresh cycle.
1 parent 4387cad commit f4f69c1

File tree

3 files changed

+79
-19
lines changed

3 files changed

+79
-19
lines changed

src/cloud-sql-instance.ts

+38-13
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export class CloudSQLInstance {
6969
private scheduledRefreshID?: ReturnType<typeof setTimeout> | null = undefined;
7070
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
7171
private throttle?: any;
72+
private closed = false;
7273
public readonly instanceInfo: InstanceConnectionInfo;
7374
public ephemeralCert?: SslCert;
7475
public host?: string;
@@ -106,37 +107,45 @@ export class CloudSQLInstance {
106107
}) as ReturnType<typeof pThrottle>;
107108
}
108109

109-
forceRefresh(){
110+
forceRefresh(): Promise<void> {
110111
// if a refresh is already ongoing, just await for its promise to fulfill
111112
// so that a new instance info is available before reconnecting
112113
if (this.next) {
113-
return;
114+
return new Promise(resolve => {
115+
if (this.next) {
116+
this.next.finally(resolve);
117+
} else {
118+
resolve();
119+
}
120+
});
114121
}
122+
115123
this.cancelRefresh();
116124
this.scheduleRefresh(0);
117-
}
118125

119-
// refreshComplete Returns a promise that resolves when the current refresh
120-
// cycle has completed, either with success or failure. If no refresh is
121-
// in progress, the promise will resolve immediately.
122-
refreshComplete(): Promise<void> {
123-
return new Promise((resolve) => {
126+
return new Promise(resolve => {
124127
// setTimeout() to yield execution to allow other refresh background
125-
// tasks to start.
126-
setTimeout(()=> {
127-
if(this.next){
128+
// task to start.
129+
setTimeout(() => {
130+
if (this.next) {
128131
// If there is a refresh promise in progress, resolve this promise
129132
// when the refresh is complete.
130-
this.next.finally(resolve)
133+
this.next.finally(resolve);
131134
} else {
132135
// Else resolve immediately.
133-
resolve()
136+
resolve();
134137
}
135138
}, 0);
136139
});
137140
}
138141

139142
refresh(): Promise<RefreshResult> {
143+
if (this.closed) {
144+
this.scheduledRefreshID = undefined;
145+
this.next = undefined;
146+
return Promise.reject('closed');
147+
}
148+
140149
const currentRefreshId = this.scheduledRefreshID;
141150

142151
// Since forceRefresh might be invoked during an ongoing refresh
@@ -203,6 +212,12 @@ export class CloudSQLInstance {
203212
// used to create new connections to a Cloud SQL instance. It throws in
204213
// case any of the internal steps fails.
205214
private async performRefresh(): Promise<RefreshResult> {
215+
if (this.closed) {
216+
// The connector may be closed while the rate limiter delayed
217+
// a call to performRefresh() so check this.closed before continuing.
218+
return Promise.reject('closed');
219+
}
220+
206221
const rsaKeys: RSAKeys = await generateKeys();
207222
const metadata: InstanceMetadata =
208223
await this.sqlAdminFetcher.getInstanceMetadata(this.instanceInfo);
@@ -264,6 +279,9 @@ export class CloudSQLInstance {
264279
}
265280

266281
private scheduleRefresh(delay: number): void {
282+
if (this.closed) {
283+
return;
284+
}
267285
this.scheduledRefreshID = setTimeout(() => this.refresh(), delay);
268286
}
269287

@@ -280,4 +298,11 @@ export class CloudSQLInstance {
280298
setEstablishedConnection(): void {
281299
this.establishedConnection = true;
282300
}
301+
302+
// close stops any refresh process in progress and prevents future refresh
303+
// connections.
304+
close(): void {
305+
this.closed = true;
306+
this.cancelRefresh();
307+
}
283308
}

src/connector.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ export class Connector {
333333
// Also clear up any local proxy servers and socket connections.
334334
close(): void {
335335
for (const instance of this.instances.values()) {
336-
instance.cancelRefresh();
336+
instance.close();
337337
}
338338
for (const server of this.localProxies) {
339339
server.close();

test/cloud-sql-instance.ts

+40-5
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,7 @@ t.test('CloudSQLInstance', async t => {
270270
instance.refresh = CloudSQLInstance.prototype.refresh;
271271
};
272272

273-
instance.forceRefresh();
274-
await instance.refreshComplete();
273+
await instance.forceRefresh();
275274

276275
t.ok(
277276
cancelRefreshCalled,
@@ -293,7 +292,7 @@ t.test('CloudSQLInstance', async t => {
293292
let cancelRefreshCalled = false;
294293
let refreshCalled = false;
295294

296-
const refreshPromise = instance.refresh();
295+
instance.refresh();
297296

298297
instance.cancelRefresh = () => {
299298
cancelRefreshCalled = true;
@@ -305,8 +304,7 @@ t.test('CloudSQLInstance', async t => {
305304
return CloudSQLInstance.prototype.refresh.call(instance);
306305
};
307306

308-
instance.forceRefresh();
309-
await instance.refreshComplete()
307+
await instance.forceRefresh();
310308

311309
t.ok(
312310
!cancelRefreshCalled,
@@ -479,6 +477,43 @@ t.test('CloudSQLInstance', async t => {
479477
}
480478
);
481479

480+
t.test(
481+
'close on established connection and ongoing failed cycle',
482+
async t => {
483+
let metadataCount = 0;
484+
const failAndSlowFetcher = {
485+
...fetcher,
486+
async getInstanceMetadata() {
487+
await (() => new Promise(res => setTimeout(res, 50)))();
488+
metadataCount++;
489+
return fetcher.getInstanceMetadata();
490+
},
491+
};
492+
493+
const instance = new CloudSQLInstance({
494+
ipType: IpAddressTypes.PUBLIC,
495+
authType: AuthTypes.PASSWORD,
496+
instanceConnectionName: 'my-project:us-east1:my-instance',
497+
sqlAdminFetcher: failAndSlowFetcher,
498+
limitRateInterval: 50,
499+
});
500+
501+
await instance.refresh();
502+
instance.setEstablishedConnection();
503+
504+
// starts a new refresh cycle but do not await on it
505+
instance.close();
506+
await instance.forceRefresh();
507+
t.equal(metadataCount, 1, 'No refresh after close');
508+
509+
await t.rejects(
510+
instance.refresh(),
511+
'closed',
512+
'Refresh after close rejected.'
513+
);
514+
}
515+
);
516+
482517
t.test(
483518
'get invalid certificate data while having a current valid',
484519
async t => {

0 commit comments

Comments
 (0)