UNPKG

5.49 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';
26import { Http2SubchannelConnector } from './transport';
27
28// 10 seconds in milliseconds. This value is arbitrary.
29/**
30 * The amount of time in between checks for dropping subchannels that have no
31 * other references
32 */
33const REF_CHECK_INTERVAL = 10_000;
34
35export class SubchannelPool {
36 private pool: {
37 [channelTarget: string]: Array<{
38 subchannelAddress: SubchannelAddress;
39 channelArguments: ChannelOptions;
40 channelCredentials: ChannelCredentials;
41 subchannel: Subchannel;
42 }>;
43 } = Object.create(null);
44
45 /**
46 * A timer of a task performing a periodic subchannel cleanup.
47 */
48 private cleanupTimer: NodeJS.Timer | null = null;
49
50 /**
51 * A pool of subchannels use for making connections. Subchannels with the
52 * exact same parameters will be reused.
53 */
54 constructor() {}
55
56 /**
57 * Unrefs all unused subchannels and cancels the cleanup task if all
58 * subchannels have been unrefed.
59 */
60 unrefUnusedSubchannels(): void {
61 let allSubchannelsUnrefed = true;
62
63 /* These objects are created with Object.create(null), so they do not
64 * have a prototype, which means that for (... in ...) loops over them
65 * do not need to be filtered */
66 // eslint-disable-disable-next-line:forin
67 for (const channelTarget in this.pool) {
68 const subchannelObjArray = this.pool[channelTarget];
69
70 const refedSubchannels = subchannelObjArray.filter(
71 (value) => !value.subchannel.unrefIfOneRef()
72 );
73
74 if (refedSubchannels.length > 0) {
75 allSubchannelsUnrefed = false;
76 }
77
78 /* For each subchannel in the pool, try to unref it if it has
79 * exactly one ref (which is the ref from the pool itself). If that
80 * does happen, remove the subchannel from the pool */
81 this.pool[channelTarget] = refedSubchannels;
82 }
83 /* Currently we do not delete keys with empty values. If that results
84 * in significant memory usage we should change it. */
85
86 // Cancel the cleanup task if all subchannels have been unrefed.
87 if (allSubchannelsUnrefed && this.cleanupTimer !== null) {
88 clearInterval(this.cleanupTimer);
89 this.cleanupTimer = null;
90 }
91 }
92
93 /**
94 * Ensures that the cleanup task is spawned.
95 */
96 ensureCleanupTask(): void {
97 if (this.cleanupTimer === null) {
98 this.cleanupTimer = setInterval(() => {
99 this.unrefUnusedSubchannels();
100 }, REF_CHECK_INTERVAL);
101
102 // Unref because this timer should not keep the event loop running.
103 // Call unref only if it exists to address electron/electron#21162
104 this.cleanupTimer.unref?.();
105 }
106 }
107
108 /**
109 * Get a subchannel if one already exists with exactly matching parameters.
110 * Otherwise, create and save a subchannel with those parameters.
111 * @param channelTarget
112 * @param subchannelTarget
113 * @param channelArguments
114 * @param channelCredentials
115 */
116 getOrCreateSubchannel(
117 channelTargetUri: GrpcUri,
118 subchannelTarget: SubchannelAddress,
119 channelArguments: ChannelOptions,
120 channelCredentials: ChannelCredentials
121 ): Subchannel {
122 this.ensureCleanupTask();
123 const channelTarget = uriToString(channelTargetUri);
124 if (channelTarget in this.pool) {
125 const subchannelObjArray = this.pool[channelTarget];
126 for (const subchannelObj of subchannelObjArray) {
127 if (
128 subchannelAddressEqual(
129 subchannelTarget,
130 subchannelObj.subchannelAddress
131 ) &&
132 channelOptionsEqual(
133 channelArguments,
134 subchannelObj.channelArguments
135 ) &&
136 channelCredentials._equals(subchannelObj.channelCredentials)
137 ) {
138 return subchannelObj.subchannel;
139 }
140 }
141 }
142 // If we get here, no matching subchannel was found
143 const subchannel = new Subchannel(
144 channelTargetUri,
145 subchannelTarget,
146 channelArguments,
147 channelCredentials,
148 new Http2SubchannelConnector(channelTargetUri)
149 );
150 if (!(channelTarget in this.pool)) {
151 this.pool[channelTarget] = [];
152 }
153 this.pool[channelTarget].push({
154 subchannelAddress: subchannelTarget,
155 channelArguments,
156 channelCredentials,
157 subchannel,
158 });
159 subchannel.ref();
160 return subchannel;
161 }
162}
163
164const globalSubchannelPool = new SubchannelPool();
165
166/**
167 * Get either the global subchannel pool, or a new subchannel pool.
168 * @param global
169 */
170export function getSubchannelPool(global: boolean): SubchannelPool {
171 if (global) {
172 return globalSubchannelPool;
173 } else {
174 return new SubchannelPool();
175 }
176}