Merge pull request #1083 from JrSchild/master

fix(sub-channels): clear the cleanup interval when all channels are unrefed.
This commit is contained in:
Michael Lumish 2019-11-06 13:22:45 -08:00 committed by GitHub
commit a7567f05cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 62 additions and 26 deletions

View File

@ -288,6 +288,8 @@ export class ChannelImplementation implements Channel {
close() {
this.resolvingLoadBalancer.destroy();
this.updateState(ConnectivityState.SHUTDOWN);
this.subchannelPool.unrefUnusedSubchannels();
}
getTarget() {

View File

@ -37,39 +37,71 @@ export class SubchannelPool {
};
} = Object.create(null);
/**
* A timer of a task performing a periodic subchannel cleanup.
*/
private cleanupTimer: NodeJS.Timer | null = null;
/**
* A pool of subchannels use for making connections. Subchannels with the
* exact same parameters will be reused.
* @param global If true, this is the global subchannel pool. Otherwise, it
* is the pool for a single channel.
*/
constructor(private global: boolean) {
if (global) {
setInterval(() => {
/* These objects are created with Object.create(null), so they do not
* have a prototype, which means that for (... in ...) loops over them
* do not need to be filtered */
// tslint:disable-next-line:forin
for (const channelTarget in this.pool) {
// tslint:disable-next-line:forin
for (const subchannelTarget in this.pool[channelTarget]) {
const subchannelObjArray = this.pool[channelTarget][
subchannelTarget
];
/* For each subchannel in the pool, try to unref it if it has
* exactly one ref (which is the ref from the pool itself). If that
* does happen, remove the subchannel from the pool */
this.pool[channelTarget][
subchannelTarget
] = subchannelObjArray.filter(
value => !value.subchannel.unrefIfOneRef()
);
}
constructor(private global: boolean) {}
/**
* Unrefs all unused subchannels and cancels the cleanup task if all
* subchannels have been unrefed.
*/
unrefUnusedSubchannels(): void {
let allSubchannelsUnrefed = true;
/* These objects are created with Object.create(null), so they do not
* have a prototype, which means that for (... in ...) loops over them
* do not need to be filtered */
// tslint:disable-next-line:forin
for (const channelTarget in this.pool) {
// tslint:disable-next-line:forin
for (const subchannelTarget in this.pool[channelTarget]) {
const subchannelObjArray = this.pool[channelTarget][
subchannelTarget
];
const refedSubchannels = subchannelObjArray
.filter(value => !value.subchannel.unrefIfOneRef());
if (refedSubchannels.length > 0) {
allSubchannelsUnrefed = false;
}
/* Currently we do not delete keys with empty values. If that results
* in significant memory usage we should change it. */
}, REF_CHECK_INTERVAL).unref();
// Unref because this timer should not keep the event loop running
/* For each subchannel in the pool, try to unref it if it has
* exactly one ref (which is the ref from the pool itself). If that
* does happen, remove the subchannel from the pool */
this.pool[channelTarget][subchannelTarget] = refedSubchannels;
}
}
/* Currently we do not delete keys with empty values. If that results
* in significant memory usage we should change it. */
// Cancel the cleanup task if all subchannels have been unrefed.
if (allSubchannelsUnrefed && this.cleanupTimer !== null) {
clearInterval(this.cleanupTimer);
this.cleanupTimer = null;
}
}
/**
* Ensures that the cleanup task is spawned.
*/
ensureCleanupTask(): void {
if (this.global && this.cleanupTimer === null) {
this.cleanupTimer = setInterval(() => {
this.unrefUnusedSubchannels();
}, REF_CHECK_INTERVAL);
// Unref because this timer should not keep the event loop running.
this.cleanupTimer.unref();
}
}
@ -87,6 +119,8 @@ export class SubchannelPool {
channelArguments: ChannelOptions,
channelCredentials: ChannelCredentials
): Subchannel {
this.ensureCleanupTask();
if (channelTarget in this.pool) {
if (subchannelTarget in this.pool[channelTarget]) {
const subchannelObjArray = this.pool[channelTarget][subchannelTarget];