UNPKG

5.38 kBPlain TextView Raw
1/*
2 * Copyright 2019 gRPC authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18import { ChannelOptions, channelOptionsEqual } from './channel-options';
19import { Subchannel } from './subchannel';
20import {
21 SubchannelAddress,
22 subchannelAddressEqual,
23} from './subchannel-address';
24import { ChannelCredentials } from './channel-credentials';
25import { GrpcUri, uriToString } from './uri-parser';
26
27// 10 seconds in milliseconds. This value is arbitrary.
28/**
29 * The amount of time in between checks for dropping subchannels that have no
30 * other references
31 */
32const REF_CHECK_INTERVAL = 10_000;
33
34export class SubchannelPool {
35 private pool: {
36 [channelTarget: string]: Array<{
37 subchannelAddress: SubchannelAddress;
38 channelArguments: ChannelOptions;
39 channelCredentials: ChannelCredentials;
40 subchannel: Subchannel;
41 }>;
42 } = Object.create(null);
43
44 /**
45 * A timer of a task performing a periodic subchannel cleanup.
46 */
47 private cleanupTimer: NodeJS.Timer | null = null;
48
49 /**
50 * A pool of subchannels use for making connections. Subchannels with the
51 * exact same parameters will be reused.
52 */
53 constructor() {}
54
55 /**
56 * Unrefs all unused subchannels and cancels the cleanup task if all
57 * subchannels have been unrefed.
58 */
59 unrefUnusedSubchannels(): void {
60 let allSubchannelsUnrefed = true;
61
62 /* These objects are created with Object.create(null), so they do not
63 * have a prototype, which means that for (... in ...) loops over them
64 * do not need to be filtered */
65 // eslint-disable-disable-next-line:forin
66 for (const channelTarget in this.pool) {
67 const subchannelObjArray = this.pool[channelTarget];
68
69 const refedSubchannels = subchannelObjArray.filter(
70 (value) => !value.subchannel.unrefIfOneRef()
71 );
72
73 if (refedSubchannels.length > 0) {
74 allSubchannelsUnrefed = false;
75 }
76
77 /* For each subchannel in the pool, try to unref it if it has
78 * exactly one ref (which is the ref from the pool itself). If that
79 * does happen, remove the subchannel from the pool */
80 this.pool[channelTarget] = refedSubchannels;
81 }
82 /* Currently we do not delete keys with empty values. If that results
83 * in significant memory usage we should change it. */
84
85 // Cancel the cleanup task if all subchannels have been unrefed.
86 if (allSubchannelsUnrefed && this.cleanupTimer !== null) {
87 clearInterval(this.cleanupTimer);
88 this.cleanupTimer = null;
89 }
90 }
91
92 /**
93 * Ensures that the cleanup task is spawned.
94 */
95 ensureCleanupTask(): void {
96 if (this.cleanupTimer === null) {
97 this.cleanupTimer = setInterval(() => {
98 this.unrefUnusedSubchannels();
99 }, REF_CHECK_INTERVAL);
100
101 // Unref because this timer should not keep the event loop running.
102 // Call unref only if it exists to address electron/electron#21162
103 this.cleanupTimer.unref?.();
104 }
105 }
106
107 /**
108 * Get a subchannel if one already exists with exactly matching parameters.
109 * Otherwise, create and save a subchannel with those parameters.
110 * @param channelTarget
111 * @param subchannelTarget
112 * @param channelArguments
113 * @param channelCredentials
114 */
115 getOrCreateSubchannel(
116 channelTargetUri: GrpcUri,
117 subchannelTarget: SubchannelAddress,
118 channelArguments: ChannelOptions,
119 channelCredentials: ChannelCredentials
120 ): Subchannel {
121 this.ensureCleanupTask();
122 const channelTarget = uriToString(channelTargetUri);
123 if (channelTarget in this.pool) {
124 const subchannelObjArray = this.pool[channelTarget];
125 for (const subchannelObj of subchannelObjArray) {
126 if (
127 subchannelAddressEqual(
128 subchannelTarget,
129 subchannelObj.subchannelAddress
130 ) &&
131 channelOptionsEqual(
132 channelArguments,
133 subchannelObj.channelArguments
134 ) &&
135 channelCredentials._equals(subchannelObj.channelCredentials)
136 ) {
137 return subchannelObj.subchannel;
138 }
139 }
140 }
141 // If we get here, no matching subchannel was found
142 const subchannel = new Subchannel(
143 channelTargetUri,
144 subchannelTarget,
145 channelArguments,
146 channelCredentials
147 );
148 if (!(channelTarget in this.pool)) {
149 this.pool[channelTarget] = [];
150 }
151 this.pool[channelTarget].push({
152 subchannelAddress: subchannelTarget,
153 channelArguments,
154 channelCredentials,
155 subchannel,
156 });
157 subchannel.ref();
158 return subchannel;
159 }
160}
161
162const globalSubchannelPool = new SubchannelPool();
163
164/**
165 * Get either the global subchannel pool, or a new subchannel pool.
166 * @param global
167 */
168export function getSubchannelPool(global: boolean): SubchannelPool {
169 if (global) {
170 return globalSubchannelPool;
171 } else {
172 return new SubchannelPool();
173 }
174}