feat: channelz improvements, idle timeout implementation

This commit is contained in:
AVVS 2024-02-27 12:27:25 -08:00
parent 210967ffa3
commit e0b900dd69
No known key found for this signature in database
GPG Key ID: 2B890ABACCC3E369
13 changed files with 813 additions and 546 deletions

View File

@ -50,6 +50,7 @@
"@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/ban-types": "off", "@typescript-eslint/ban-types": "off",
"@typescript-eslint/camelcase": "off", "@typescript-eslint/camelcase": "off",
"@typescript-eslint/no-explicit-any": "off",
"node/no-missing-import": "off", "node/no-missing-import": "off",
"node/no-empty-function": "off", "node/no-empty-function": "off",
"node/no-unsupported-features/es-syntax": "off", "node/no-unsupported-features/es-syntax": "off",

View File

@ -35,14 +35,17 @@ const pkgPath = path.resolve(jsCoreDir, 'package.json');
const supportedVersionRange = require(pkgPath).engines.node; const supportedVersionRange = require(pkgPath).engines.node;
const versionNotSupported = () => { const versionNotSupported = () => {
console.log(`Skipping grpc-js task for Node ${process.version}`); console.log(`Skipping grpc-js task for Node ${process.version}`);
return () => { return Promise.resolve(); }; return () => {
return Promise.resolve();
};
}; };
const identity = (value: any): any => value; const identity = (value: any): any => value;
const checkTask = semver.satisfies(process.version, supportedVersionRange) ? const checkTask = semver.satisfies(process.version, supportedVersionRange)
identity : versionNotSupported; ? identity
: versionNotSupported;
const execNpmVerb = (verb: string, ...args: string[]) => const execNpmVerb = (verb: string, ...args: string[]) =>
execa('npm', [verb, ...args], {cwd: jsCoreDir, stdio: 'inherit'}); execa('npm', [verb, ...args], { cwd: jsCoreDir, stdio: 'inherit' });
const execNpmCommand = execNpmVerb.bind(null, 'run'); const execNpmCommand = execNpmVerb.bind(null, 'run');
const install = checkTask(() => execNpmVerb('install', '--unsafe-perm')); const install = checkTask(() => execNpmVerb('install', '--unsafe-perm'));
@ -64,22 +67,20 @@ const cleanAll = gulp.parallel(clean);
*/ */
const compile = checkTask(() => execNpmCommand('compile')); const compile = checkTask(() => execNpmCommand('compile'));
const copyTestFixtures = checkTask(() => ncpP(`${jsCoreDir}/test/fixtures`, `${outDir}/test/fixtures`)); const copyTestFixtures = checkTask(() =>
ncpP(`${jsCoreDir}/test/fixtures`, `${outDir}/test/fixtures`)
);
const runTests = checkTask(() => { const runTests = checkTask(() => {
process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION = 'true'; process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION = 'true';
return gulp.src(`${outDir}/test/**/*.js`) return gulp.src(`${outDir}/test/**/*.js`).pipe(
.pipe(mocha({reporter: 'mocha-jenkins-reporter', mocha({
require: ['ts-node/register']})); reporter: 'mocha-jenkins-reporter',
require: ['ts-node/register'],
})
);
}); });
const test = gulp.series(install, copyTestFixtures, runTests); const test = gulp.series(install, copyTestFixtures, runTests);
export { export { install, lint, clean, cleanAll, compile, test };
install,
lint,
clean,
cleanAll,
compile,
test
}

View File

@ -6,7 +6,7 @@
"repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js",
"main": "build/src/index.js", "main": "build/src/index.js",
"engines": { "engines": {
"node": "^8.13.0 || >=10.10.0" "node": ">=12.10.0"
}, },
"keywords": [], "keywords": [],
"author": { "author": {
@ -15,17 +15,18 @@
"types": "build/src/index.d.ts", "types": "build/src/index.d.ts",
"license": "Apache-2.0", "license": "Apache-2.0",
"devDependencies": { "devDependencies": {
"@types/gulp": "^4.0.6", "@types/gulp": "^4.0.17",
"@types/gulp-mocha": "0.0.32", "@types/gulp-mocha": "0.0.37",
"@types/lodash": "^4.14.186", "@types/lodash": "^4.14.202",
"@types/mocha": "^5.2.6", "@types/mocha": "^10.0.6",
"@types/ncp": "^2.0.1", "@types/ncp": "^2.0.8",
"@types/pify": "^3.0.2", "@types/pify": "^5.0.4",
"@types/semver": "^7.3.9", "@types/semver": "^7.5.8",
"@typescript-eslint/eslint-plugin": "^5.59.11", "@types/node": ">=20.11.20",
"@typescript-eslint/parser": "^5.59.11", "@typescript-eslint/eslint-plugin": "^7.1.0",
"@typescript-eslint/typescript-estree": "^5.59.11", "@typescript-eslint/parser": "^7.1.0",
"clang-format": "^1.0.55", "@typescript-eslint/typescript-estree": "^7.1.0",
"clang-format": "^1.8.0",
"eslint": "^8.42.0", "eslint": "^8.42.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.8.0",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
@ -33,16 +34,16 @@
"execa": "^2.0.3", "execa": "^2.0.3",
"gulp": "^4.0.2", "gulp": "^4.0.2",
"gulp-mocha": "^6.0.0", "gulp-mocha": "^6.0.0",
"lodash": "^4.17.4", "lodash": "^4.17.21",
"madge": "^5.0.1", "madge": "^5.0.1",
"mocha-jenkins-reporter": "^0.4.1", "mocha-jenkins-reporter": "^0.4.1",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"pify": "^4.0.1", "pify": "^4.0.1",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"semver": "^7.3.5", "semver": "^7.6.0",
"ts-node": "^10.9.1", "ts-node": "^10.9.2",
"typescript": "^5.1.3" "typescript": "^5.3.3"
}, },
"contributors": [ "contributors": [
{ {
@ -65,8 +66,8 @@
"generate-test-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --include-dirs test/fixtures/ -O test/generated/ --grpcLib ../../src/index test_service.proto" "generate-test-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --include-dirs test/fixtures/ -O test/generated/ --grpcLib ../../src/index test_service.proto"
}, },
"dependencies": { "dependencies": {
"@grpc/proto-loader": "^0.7.8", "@grpc/proto-loader": "^0.7.10",
"@types/node": ">=12.12.47" "@js-sdsl/ordered-map": "^4.4.2"
}, },
"files": [ "files": [
"src/**/*.ts", "src/**/*.ts",

View File

@ -20,7 +20,7 @@ import { ChannelOptions } from './channel-options';
import { ServerSurfaceCall } from './server-call'; import { ServerSurfaceCall } from './server-call';
import { ConnectivityState } from './connectivity-state'; import { ConnectivityState } from './connectivity-state';
import { ChannelRef } from './channelz'; import type { ChannelRef } from './channelz';
import { Call } from './call-interface'; import { Call } from './call-interface';
import { InternalChannel } from './internal-channel'; import { InternalChannel } from './internal-channel';
import { Deadline } from './deadline'; import { Deadline } from './deadline';

View File

@ -16,6 +16,7 @@
*/ */
import { isIPv4, isIPv6 } from 'net'; import { isIPv4, isIPv6 } from 'net';
import { OrderedMap, type OrderedMapIterator } from '@js-sdsl/ordered-map';
import { ConnectivityState } from './connectivity-state'; import { ConnectivityState } from './connectivity-state';
import { Status } from './constants'; import { Status } from './constants';
import { Timestamp } from './generated/google/protobuf/Timestamp'; import { Timestamp } from './generated/google/protobuf/Timestamp';
@ -66,24 +67,25 @@ export type TraceSeverity =
| 'CT_ERROR'; | 'CT_ERROR';
export interface ChannelRef { export interface ChannelRef {
kind: 'channel'; kind: EntityTypes.channel;
id: number; id: number;
name: string; name: string;
} }
export interface SubchannelRef { export interface SubchannelRef {
kind: 'subchannel'; kind: EntityTypes.subchannel;
id: number; id: number;
name: string; name: string;
} }
export interface ServerRef { export interface ServerRef {
kind: 'server'; kind: EntityTypes.server;
id: number; id: number;
name: string;
} }
export interface SocketRef { export interface SocketRef {
kind: 'socket'; kind: EntityTypes.socket;
id: number; id: number;
name: string; name: string;
} }
@ -131,6 +133,21 @@ interface TraceEvent {
*/ */
const TARGET_RETAINED_TRACES = 32; const TARGET_RETAINED_TRACES = 32;
export class ChannelzTraceStub {
readonly events: TraceEvent[] = [];
readonly creationTimestamp: Date = new Date();
readonly eventsLogged = 0;
addTrace(): void {}
getTraceMessage(): ChannelTrace {
return {
creation_timestamp: dateToProtoTimestamp(this.creationTimestamp),
num_events_logged: this.eventsLogged,
events: [],
};
}
}
export class ChannelzTrace { export class ChannelzTrace {
events: TraceEvent[] = []; events: TraceEvent[] = [];
creationTimestamp: Date; creationTimestamp: Date;
@ -182,105 +199,64 @@ export class ChannelzTrace {
} }
export class ChannelzChildrenTracker { export class ChannelzChildrenTracker {
private channelChildren: Map<number, { ref: ChannelRef; count: number }> = private channelChildren = new OrderedMap<
new Map<number, { ref: ChannelRef; count: number }>(); number,
private subchannelChildren: Map< { ref: ChannelRef; count: number }
>();
private subchannelChildren = new OrderedMap<
number, number,
{ ref: SubchannelRef; count: number } { ref: SubchannelRef; count: number }
> = new Map<number, { ref: SubchannelRef; count: number }>(); >();
private socketChildren: Map<number, { ref: SocketRef; count: number }> = private socketChildren = new OrderedMap<
new Map<number, { ref: SocketRef; count: number }>(); number,
{ ref: SocketRef; count: number }
>();
private trackerMap = {
[EntityTypes.channel]: this.channelChildren,
[EntityTypes.subchannel]: this.subchannelChildren,
[EntityTypes.socket]: this.socketChildren,
} as const;
refChild(child: ChannelRef | SubchannelRef | SocketRef) { refChild(child: ChannelRef | SubchannelRef | SocketRef) {
switch (child.kind) { const tracker = this.trackerMap[child.kind];
case 'channel': { const trackedChild = tracker.getElementByKey(child.id);
const trackedChild = this.channelChildren.get(child.id) ?? {
ref: child, if (trackedChild === undefined) {
count: 0, tracker.setElement(child.id, {
}; // @ts-expect-error union issues
trackedChild.count += 1; ref: child,
this.channelChildren.set(child.id, trackedChild); count: 1,
break; });
} } else {
case 'subchannel': { trackedChild.count += 1;
const trackedChild = this.subchannelChildren.get(child.id) ?? {
ref: child,
count: 0,
};
trackedChild.count += 1;
this.subchannelChildren.set(child.id, trackedChild);
break;
}
case 'socket': {
const trackedChild = this.socketChildren.get(child.id) ?? {
ref: child,
count: 0,
};
trackedChild.count += 1;
this.socketChildren.set(child.id, trackedChild);
break;
}
} }
} }
unrefChild(child: ChannelRef | SubchannelRef | SocketRef) { unrefChild(child: ChannelRef | SubchannelRef | SocketRef) {
switch (child.kind) { const tracker = this.trackerMap[child.kind];
case 'channel': { const trackedChild = tracker.getElementByKey(child.id);
const trackedChild = this.channelChildren.get(child.id); if (trackedChild !== undefined) {
if (trackedChild !== undefined) { trackedChild.count -= 1;
trackedChild.count -= 1; if (trackedChild.count === 0) {
if (trackedChild.count === 0) { tracker.eraseElementByKey(child.id);
this.channelChildren.delete(child.id);
} else {
this.channelChildren.set(child.id, trackedChild);
}
}
break;
}
case 'subchannel': {
const trackedChild = this.subchannelChildren.get(child.id);
if (trackedChild !== undefined) {
trackedChild.count -= 1;
if (trackedChild.count === 0) {
this.subchannelChildren.delete(child.id);
} else {
this.subchannelChildren.set(child.id, trackedChild);
}
}
break;
}
case 'socket': {
const trackedChild = this.socketChildren.get(child.id);
if (trackedChild !== undefined) {
trackedChild.count -= 1;
if (trackedChild.count === 0) {
this.socketChildren.delete(child.id);
} else {
this.socketChildren.set(child.id, trackedChild);
}
}
break;
} }
} }
} }
getChildLists(): ChannelzChildren { getChildLists(): ChannelzChildren {
const channels: ChannelRef[] = []; return {
for (const { ref } of this.channelChildren.values()) { channels: this.channelChildren,
channels.push(ref); subchannels: this.subchannelChildren,
} sockets: this.socketChildren,
const subchannels: SubchannelRef[] = []; };
for (const { ref } of this.subchannelChildren.values()) {
subchannels.push(ref);
}
const sockets: SocketRef[] = [];
for (const { ref } of this.socketChildren.values()) {
sockets.push(ref);
}
return { channels, subchannels, sockets };
} }
} }
export class ChannelzChildrenTrackerStub extends ChannelzChildrenTracker {
override refChild(): void {}
override unrefChild(): void {}
}
export class ChannelzCallTracker { export class ChannelzCallTracker {
callsStarted = 0; callsStarted = 0;
callsSucceeded = 0; callsSucceeded = 0;
@ -299,17 +275,23 @@ export class ChannelzCallTracker {
} }
} }
export class ChannelzCallTrackerStub extends ChannelzCallTracker {
override addCallStarted() {}
override addCallSucceeded() {}
override addCallFailed() {}
}
export interface ChannelzChildren { export interface ChannelzChildren {
channels: ChannelRef[]; channels: OrderedMap<number, { ref: ChannelRef; count: number }>;
subchannels: SubchannelRef[]; subchannels: OrderedMap<number, { ref: SubchannelRef; count: number }>;
sockets: SocketRef[]; sockets: OrderedMap<number, { ref: SocketRef; count: number }>;
} }
export interface ChannelInfo { export interface ChannelInfo {
target: string; target: string;
state: ConnectivityState; state: ConnectivityState;
trace: ChannelzTrace; trace: ChannelzTrace | ChannelzTraceStub;
callTracker: ChannelzCallTracker; callTracker: ChannelzCallTracker | ChannelzCallTrackerStub;
children: ChannelzChildren; children: ChannelzChildren;
} }
@ -348,105 +330,102 @@ export interface SocketInfo {
remoteFlowControlWindow: number | null; remoteFlowControlWindow: number | null;
} }
interface ChannelEntry { type ChannelEntry = {
ref: ChannelRef; ref: ChannelRef;
getInfo(): ChannelInfo; getInfo(): ChannelInfo;
} };
interface SubchannelEntry { type SubchannelEntry = {
ref: SubchannelRef; ref: SubchannelRef;
getInfo(): SubchannelInfo; getInfo(): SubchannelInfo;
} };
interface ServerEntry { type ServerEntry = {
ref: ServerRef; ref: ServerRef;
getInfo(): ServerInfo; getInfo(): ServerInfo;
} };
interface SocketEntry { type SocketEntry = {
ref: SocketRef; ref: SocketRef;
getInfo(): SocketInfo; getInfo(): SocketInfo;
};
export const enum EntityTypes {
channel = 'channel',
subchannel = 'subchannel',
server = 'server',
socket = 'socket',
} }
let nextId = 1; const entityMaps = {
[EntityTypes.channel]: new OrderedMap<number, ChannelEntry>(),
[EntityTypes.subchannel]: new OrderedMap<number, SubchannelEntry>(),
[EntityTypes.server]: new OrderedMap<number, ServerEntry>(),
[EntityTypes.socket]: new OrderedMap<number, SocketEntry>(),
} as const;
function getNextId(): number { export type RefByType<T extends EntityTypes> = T extends EntityTypes.channel
return nextId++; ? ChannelRef
} : T extends EntityTypes.server
? ServerRef
: T extends EntityTypes.socket
? SocketRef
: T extends EntityTypes.subchannel
? SubchannelRef
: never;
const channels: (ChannelEntry | undefined)[] = []; export type EntryByType<T extends EntityTypes> = T extends EntityTypes.channel
const subchannels: (SubchannelEntry | undefined)[] = []; ? ChannelEntry
const servers: (ServerEntry | undefined)[] = []; : T extends EntityTypes.server
const sockets: (SocketEntry | undefined)[] = []; ? ServerEntry
: T extends EntityTypes.socket
? SocketEntry
: T extends EntityTypes.subchannel
? SubchannelEntry
: never;
export function registerChannelzChannel( export type InfoByType<T extends EntityTypes> = T extends EntityTypes.channel
name: string, ? ChannelInfo
getInfo: () => ChannelInfo, : T extends EntityTypes.subchannel
channelzEnabled: boolean ? SubchannelInfo
): ChannelRef { : T extends EntityTypes.server
const id = getNextId(); ? ServerInfo
const ref: ChannelRef = { id, name, kind: 'channel' }; : T extends EntityTypes.socket
if (channelzEnabled) { ? SocketInfo
channels[id] = { ref, getInfo }; : never;
const generateRegisterFn = <R extends EntityTypes>(kind: R) => {
let nextId = 1;
function getNextId(): number {
return nextId++;
} }
return ref;
}
export function registerChannelzSubchannel( return (
name: string, name: string,
getInfo: () => SubchannelInfo, getInfo: () => InfoByType<R>,
channelzEnabled: boolean channelzEnabled: boolean
): SubchannelRef { ): RefByType<R> => {
const id = getNextId(); const id = getNextId();
const ref: SubchannelRef = { id, name, kind: 'subchannel' }; const ref = { id, name, kind } as RefByType<R>;
if (channelzEnabled) { if (channelzEnabled) {
subchannels[id] = { ref, getInfo }; // @ts-expect-error typing issues
} entityMaps[kind].setElement(id, { ref, getInfo });
return ref; }
} return ref;
};
};
export function registerChannelzServer( export const registerChannelzChannel = generateRegisterFn(EntityTypes.channel);
getInfo: () => ServerInfo, export const registerChannelzSubchannel = generateRegisterFn(
channelzEnabled: boolean EntityTypes.subchannel
): ServerRef { );
const id = getNextId(); export const registerChannelzServer = generateRegisterFn(EntityTypes.server);
const ref: ServerRef = { id, kind: 'server' }; export const registerChannelzSocket = generateRegisterFn(EntityTypes.socket);
if (channelzEnabled) {
servers[id] = { ref, getInfo };
}
return ref;
}
export function registerChannelzSocket(
name: string,
getInfo: () => SocketInfo,
channelzEnabled: boolean
): SocketRef {
const id = getNextId();
const ref: SocketRef = { id, name, kind: 'socket' };
if (channelzEnabled) {
sockets[id] = { ref, getInfo };
}
return ref;
}
export function unregisterChannelzRef( export function unregisterChannelzRef(
ref: ChannelRef | SubchannelRef | ServerRef | SocketRef ref: ChannelRef | SubchannelRef | ServerRef | SocketRef
) { ) {
switch (ref.kind) { entityMaps[ref.kind].eraseElementByKey(ref.id);
case 'channel':
delete channels[ref.id];
return;
case 'subchannel':
delete subchannels[ref.id];
return;
case 'server':
delete servers[ref.id];
return;
case 'socket':
delete sockets[ref.id];
return;
}
} }
/** /**
@ -556,6 +535,17 @@ function dateToProtoTimestamp(date?: Date | null): Timestamp | null {
function getChannelMessage(channelEntry: ChannelEntry): ChannelMessage { function getChannelMessage(channelEntry: ChannelEntry): ChannelMessage {
const resolvedInfo = channelEntry.getInfo(); const resolvedInfo = channelEntry.getInfo();
const channelRef: ChannelRefMessage[] = [];
const subchannelRef: SubchannelRefMessage[] = [];
resolvedInfo.children.channels.forEach(el => {
channelRef.push(channelRefToMessage(el[1].ref));
});
resolvedInfo.children.subchannels.forEach(el => {
subchannelRef.push(subchannelRefToMessage(el[1].ref));
});
return { return {
ref: channelRefToMessage(channelEntry.ref), ref: channelRefToMessage(channelEntry.ref),
data: { data: {
@ -569,12 +559,8 @@ function getChannelMessage(channelEntry: ChannelEntry): ChannelMessage {
), ),
trace: resolvedInfo.trace.getTraceMessage(), trace: resolvedInfo.trace.getTraceMessage(),
}, },
channel_ref: resolvedInfo.children.channels.map(ref => channel_ref: channelRef,
channelRefToMessage(ref) subchannel_ref: subchannelRef,
),
subchannel_ref: resolvedInfo.children.subchannels.map(ref =>
subchannelRefToMessage(ref)
),
}; };
} }
@ -582,8 +568,9 @@ function GetChannel(
call: ServerUnaryCall<GetChannelRequest__Output, GetChannelResponse>, call: ServerUnaryCall<GetChannelRequest__Output, GetChannelResponse>,
callback: sendUnaryData<GetChannelResponse> callback: sendUnaryData<GetChannelResponse>
): void { ): void {
const channelId = Number.parseInt(call.request.channel_id); const channelId = parseInt(call.request.channel_id, 10);
const channelEntry = channels[channelId]; const channelEntry =
entityMaps[EntityTypes.channel].getElementByKey(channelId);
if (channelEntry === undefined) { if (channelEntry === undefined) {
callback({ callback({
code: Status.NOT_FOUND, code: Status.NOT_FOUND,
@ -598,27 +585,34 @@ function GetTopChannels(
call: ServerUnaryCall<GetTopChannelsRequest__Output, GetTopChannelsResponse>, call: ServerUnaryCall<GetTopChannelsRequest__Output, GetTopChannelsResponse>,
callback: sendUnaryData<GetTopChannelsResponse> callback: sendUnaryData<GetTopChannelsResponse>
): void { ): void {
const maxResults = Number.parseInt(call.request.max_results); const maxResults = parseInt(call.request.max_results, 10) || 100;
const resultList: ChannelMessage[] = []; const resultList: ChannelMessage[] = [];
let i = Number.parseInt(call.request.start_channel_id); const startId = parseInt(call.request.start_channel_id, 10);
for (; i < channels.length; i++) { const channelEntries = entityMaps[EntityTypes.channel];
const channelEntry = channels[i];
if (channelEntry === undefined) { let i: OrderedMapIterator<number, ChannelEntry>;
continue; for (
} i = channelEntries.lowerBound(startId);
resultList.push(getChannelMessage(channelEntry)); !i.equals(channelEntries.end()) && resultList.length < maxResults;
if (resultList.length >= maxResults) { i = i.next()
break; ) {
} resultList.push(getChannelMessage(i.pointer[1]));
} }
callback(null, { callback(null, {
channel: resultList, channel: resultList,
end: i >= servers.length, end: i.equals(channelEntries.end()),
}); });
} }
function getServerMessage(serverEntry: ServerEntry): ServerMessage { function getServerMessage(serverEntry: ServerEntry): ServerMessage {
const resolvedInfo = serverEntry.getInfo(); const resolvedInfo = serverEntry.getInfo();
const listenSocket: SocketRefMessage[] = [];
resolvedInfo.listenerChildren.sockets.forEach(el => {
listenSocket.push(socketRefToMessage(el[1].ref));
});
return { return {
ref: serverRefToMessage(serverEntry.ref), ref: serverRefToMessage(serverEntry.ref),
data: { data: {
@ -630,9 +624,7 @@ function getServerMessage(serverEntry: ServerEntry): ServerMessage {
), ),
trace: resolvedInfo.trace.getTraceMessage(), trace: resolvedInfo.trace.getTraceMessage(),
}, },
listen_socket: resolvedInfo.listenerChildren.sockets.map(ref => listen_socket: listenSocket,
socketRefToMessage(ref)
),
}; };
} }
@ -640,8 +632,9 @@ function GetServer(
call: ServerUnaryCall<GetServerRequest__Output, GetServerResponse>, call: ServerUnaryCall<GetServerRequest__Output, GetServerResponse>,
callback: sendUnaryData<GetServerResponse> callback: sendUnaryData<GetServerResponse>
): void { ): void {
const serverId = Number.parseInt(call.request.server_id); const serverId = parseInt(call.request.server_id, 10);
const serverEntry = servers[serverId]; const serverEntries = entityMaps[EntityTypes.server];
const serverEntry = serverEntries.getElementByKey(serverId);
if (serverEntry === undefined) { if (serverEntry === undefined) {
callback({ callback({
code: Status.NOT_FOUND, code: Status.NOT_FOUND,
@ -656,22 +649,23 @@ function GetServers(
call: ServerUnaryCall<GetServersRequest__Output, GetServersResponse>, call: ServerUnaryCall<GetServersRequest__Output, GetServersResponse>,
callback: sendUnaryData<GetServersResponse> callback: sendUnaryData<GetServersResponse>
): void { ): void {
const maxResults = Number.parseInt(call.request.max_results); const maxResults = parseInt(call.request.max_results, 10) || 100;
const startId = parseInt(call.request.start_server_id, 10);
const serverEntries = entityMaps[EntityTypes.server];
const resultList: ServerMessage[] = []; const resultList: ServerMessage[] = [];
let i = Number.parseInt(call.request.start_server_id);
for (; i < servers.length; i++) { let i: OrderedMapIterator<number, ServerEntry>;
const serverEntry = servers[i]; for (
if (serverEntry === undefined) { i = serverEntries.lowerBound(startId);
continue; !i.equals(serverEntries.end()) && resultList.length < maxResults;
} i = i.next()
resultList.push(getServerMessage(serverEntry)); ) {
if (resultList.length >= maxResults) { resultList.push(getServerMessage(i.pointer[1]));
break;
}
} }
callback(null, { callback(null, {
server: resultList, server: resultList,
end: i >= servers.length, end: i.equals(serverEntries.end()),
}); });
} }
@ -679,8 +673,9 @@ function GetSubchannel(
call: ServerUnaryCall<GetSubchannelRequest__Output, GetSubchannelResponse>, call: ServerUnaryCall<GetSubchannelRequest__Output, GetSubchannelResponse>,
callback: sendUnaryData<GetSubchannelResponse> callback: sendUnaryData<GetSubchannelResponse>
): void { ): void {
const subchannelId = Number.parseInt(call.request.subchannel_id); const subchannelId = parseInt(call.request.subchannel_id, 10);
const subchannelEntry = subchannels[subchannelId]; const subchannelEntry =
entityMaps[EntityTypes.subchannel].getElementByKey(subchannelId);
if (subchannelEntry === undefined) { if (subchannelEntry === undefined) {
callback({ callback({
code: Status.NOT_FOUND, code: Status.NOT_FOUND,
@ -689,6 +684,12 @@ function GetSubchannel(
return; return;
} }
const resolvedInfo = subchannelEntry.getInfo(); const resolvedInfo = subchannelEntry.getInfo();
const listenSocket: SocketRefMessage[] = [];
resolvedInfo.children.sockets.forEach(el => {
listenSocket.push(socketRefToMessage(el[1].ref));
});
const subchannelMessage: SubchannelMessage = { const subchannelMessage: SubchannelMessage = {
ref: subchannelRefToMessage(subchannelEntry.ref), ref: subchannelRefToMessage(subchannelEntry.ref),
data: { data: {
@ -702,9 +703,7 @@ function GetSubchannel(
), ),
trace: resolvedInfo.trace.getTraceMessage(), trace: resolvedInfo.trace.getTraceMessage(),
}, },
socket_ref: resolvedInfo.children.sockets.map(ref => socket_ref: listenSocket,
socketRefToMessage(ref)
),
}; };
callback(null, { subchannel: subchannelMessage }); callback(null, { subchannel: subchannelMessage });
} }
@ -735,8 +734,8 @@ function GetSocket(
call: ServerUnaryCall<GetSocketRequest__Output, GetSocketResponse>, call: ServerUnaryCall<GetSocketRequest__Output, GetSocketResponse>,
callback: sendUnaryData<GetSocketResponse> callback: sendUnaryData<GetSocketResponse>
): void { ): void {
const socketId = Number.parseInt(call.request.socket_id); const socketId = parseInt(call.request.socket_id, 10);
const socketEntry = sockets[socketId]; const socketEntry = entityMaps[EntityTypes.socket].getElementByKey(socketId);
if (socketEntry === undefined) { if (socketEntry === undefined) {
callback({ callback({
code: Status.NOT_FOUND, code: Status.NOT_FOUND,
@ -809,8 +808,9 @@ function GetServerSockets(
>, >,
callback: sendUnaryData<GetServerSocketsResponse> callback: sendUnaryData<GetServerSocketsResponse>
): void { ): void {
const serverId = Number.parseInt(call.request.server_id); const serverId = parseInt(call.request.server_id, 10);
const serverEntry = servers[serverId]; const serverEntry = entityMaps[EntityTypes.server].getElementByKey(serverId);
if (serverEntry === undefined) { if (serverEntry === undefined) {
callback({ callback({
code: Status.NOT_FOUND, code: Status.NOT_FOUND,
@ -818,28 +818,28 @@ function GetServerSockets(
}); });
return; return;
} }
const startId = Number.parseInt(call.request.start_socket_id);
const maxResults = Number.parseInt(call.request.max_results); const startId = parseInt(call.request.start_socket_id, 10);
const maxResults = parseInt(call.request.max_results, 10) || 100;
const resolvedInfo = serverEntry.getInfo(); const resolvedInfo = serverEntry.getInfo();
// If we wanted to include listener sockets in the result, this line would // If we wanted to include listener sockets in the result, this line would
// instead say // instead say
// const allSockets = resolvedInfo.listenerChildren.sockets.concat(resolvedInfo.sessionChildren.sockets).sort((ref1, ref2) => ref1.id - ref2.id); // const allSockets = resolvedInfo.listenerChildren.sockets.concat(resolvedInfo.sessionChildren.sockets).sort((ref1, ref2) => ref1.id - ref2.id);
const allSockets = resolvedInfo.sessionChildren.sockets.sort( const allSockets = resolvedInfo.sessionChildren.sockets;
(ref1, ref2) => ref1.id - ref2.id
);
const resultList: SocketRefMessage[] = []; const resultList: SocketRefMessage[] = [];
let i = 0;
for (; i < allSockets.length; i++) { let i: OrderedMapIterator<number, { ref: SocketRef }>;
if (allSockets[i].id >= startId) { for (
resultList.push(socketRefToMessage(allSockets[i])); i = allSockets.lowerBound(startId);
if (resultList.length >= maxResults) { !i.equals(allSockets.end()) && resultList.length < maxResults;
break; i = i.next()
} ) {
} resultList.push(socketRefToMessage(i.pointer[1].ref));
} }
callback(null, { callback(null, {
socket_ref: resultList, socket_ref: resultList,
end: i >= allSockets.length, end: i.equals(allSockets.end()),
}); });
} }

View File

@ -25,7 +25,7 @@ import { Endpoint, SubchannelAddress } from './subchannel-address';
import { ChannelOptions } from './channel-options'; import { ChannelOptions } from './channel-options';
import { ConnectivityState } from './connectivity-state'; import { ConnectivityState } from './connectivity-state';
import { Picker } from './picker'; import { Picker } from './picker';
import { ChannelRef, SubchannelRef } from './channelz'; import type { ChannelRef, SubchannelRef } from './channelz';
import { SubchannelInterface } from './subchannel-interface'; import { SubchannelInterface } from './subchannel-interface';
const TYPE_NAME = 'child_load_balancer_helper'; const TYPE_NAME = 'child_load_balancer_helper';

View File

@ -19,7 +19,7 @@ import { ChannelOptions } from './channel-options';
import { Endpoint, SubchannelAddress } from './subchannel-address'; import { Endpoint, SubchannelAddress } from './subchannel-address';
import { ConnectivityState } from './connectivity-state'; import { ConnectivityState } from './connectivity-state';
import { Picker } from './picker'; import { Picker } from './picker';
import { ChannelRef, SubchannelRef } from './channelz'; import type { ChannelRef, SubchannelRef } from './channelz';
import { SubchannelInterface } from './subchannel-interface'; import { SubchannelInterface } from './subchannel-interface';
import { LoadBalancingConfig } from './service-config'; import { LoadBalancingConfig } from './service-config';
import { log } from './logging'; import { log } from './logging';

View File

@ -19,12 +19,12 @@ import { EventEmitter } from 'events';
import { Duplex, Readable, Writable } from 'stream'; import { Duplex, Readable, Writable } from 'stream';
import { Status } from './constants'; import { Status } from './constants';
import { Deserialize, Serialize } from './make-client'; import type { Deserialize, Serialize } from './make-client';
import { Metadata } from './metadata'; import { Metadata } from './metadata';
import { ObjectReadable, ObjectWritable } from './object-stream'; import type { ObjectReadable, ObjectWritable } from './object-stream';
import { StatusObject, PartialStatusObject } from './call-interface'; import type { StatusObject, PartialStatusObject } from './call-interface';
import { Deadline } from './deadline'; import type { Deadline } from './deadline';
import { ServerInterceptingCallInterface } from './server-interceptors'; import type { ServerInterceptingCallInterface } from './server-interceptors';
export type ServerStatusResponse = Partial<StatusObject>; export type ServerStatusResponse = Partial<StatusObject>;
@ -330,7 +330,7 @@ export interface UnaryHandler<RequestType, ResponseType> {
func: handleUnaryCall<RequestType, ResponseType>; func: handleUnaryCall<RequestType, ResponseType>;
serialize: Serialize<ResponseType>; serialize: Serialize<ResponseType>;
deserialize: Deserialize<RequestType>; deserialize: Deserialize<RequestType>;
type: HandlerType; type: 'unary';
path: string; path: string;
} }
@ -338,7 +338,7 @@ export interface ClientStreamingHandler<RequestType, ResponseType> {
func: handleClientStreamingCall<RequestType, ResponseType>; func: handleClientStreamingCall<RequestType, ResponseType>;
serialize: Serialize<ResponseType>; serialize: Serialize<ResponseType>;
deserialize: Deserialize<RequestType>; deserialize: Deserialize<RequestType>;
type: HandlerType; type: 'clientStream';
path: string; path: string;
} }
@ -346,7 +346,7 @@ export interface ServerStreamingHandler<RequestType, ResponseType> {
func: handleServerStreamingCall<RequestType, ResponseType>; func: handleServerStreamingCall<RequestType, ResponseType>;
serialize: Serialize<ResponseType>; serialize: Serialize<ResponseType>;
deserialize: Deserialize<RequestType>; deserialize: Deserialize<RequestType>;
type: HandlerType; type: 'serverStream';
path: string; path: string;
} }
@ -354,7 +354,7 @@ export interface BidiStreamingHandler<RequestType, ResponseType> {
func: handleBidiStreamingCall<RequestType, ResponseType>; func: handleBidiStreamingCall<RequestType, ResponseType>;
serialize: Serialize<ResponseType>; serialize: Serialize<ResponseType>;
deserialize: Deserialize<RequestType>; deserialize: Deserialize<RequestType>;
type: HandlerType; type: 'bidi';
path: string; path: string;
} }

View File

@ -64,8 +64,11 @@ import {
} from './uri-parser'; } from './uri-parser';
import { import {
ChannelzCallTracker, ChannelzCallTracker,
ChannelzCallTrackerStub,
ChannelzChildrenTracker, ChannelzChildrenTracker,
ChannelzChildrenTrackerStub,
ChannelzTrace, ChannelzTrace,
ChannelzTraceStub,
registerChannelzServer, registerChannelzServer,
registerChannelzSocket, registerChannelzSocket,
ServerInfo, ServerInfo,
@ -87,6 +90,7 @@ import { CallEventTracker } from './transport';
const UNLIMITED_CONNECTION_AGE_MS = ~(1 << 31); const UNLIMITED_CONNECTION_AGE_MS = ~(1 << 31);
const KEEPALIVE_MAX_TIME_MS = ~(1 << 31); const KEEPALIVE_MAX_TIME_MS = ~(1 << 31);
const KEEPALIVE_TIMEOUT_MS = 20000; const KEEPALIVE_TIMEOUT_MS = 20000;
const MAX_CONNECTION_IDLE_MS = 30 * 60 * 1e3; // 30 min
const { HTTP2_HEADER_PATH } = http2.constants; const { HTTP2_HEADER_PATH } = http2.constants;
@ -177,9 +181,10 @@ function getDefaultHandler(handlerType: HandlerType, methodName: string) {
interface ChannelzSessionInfo { interface ChannelzSessionInfo {
ref: SocketRef; ref: SocketRef;
streamTracker: ChannelzCallTracker; streamTracker: ChannelzCallTracker | ChannelzCallTrackerStub;
messagesSent: number; messagesSent: number;
messagesReceived: number; messagesReceived: number;
keepAlivesSent: number;
lastMessageSentTimestamp: Date | null; lastMessageSentTimestamp: Date | null;
lastMessageReceivedTimestamp: Date | null; lastMessageReceivedTimestamp: Date | null;
} }
@ -243,6 +248,13 @@ export interface ServerOptions extends ChannelOptions {
export class Server { export class Server {
private boundPorts: Map<string, BoundPort> = new Map(); private boundPorts: Map<string, BoundPort> = new Map();
private http2Servers: Map<AnyHttp2Server, Http2ServerInfo> = new Map(); private http2Servers: Map<AnyHttp2Server, Http2ServerInfo> = new Map();
private sessionIdleTimeouts = new Map<
http2.Http2Session,
{
activeStreams: number;
timeout: NodeJS.Timeout | null;
}
>();
private handlers: Map<string, UntypedHandler> = new Map< private handlers: Map<string, UntypedHandler> = new Map<
string, string,
@ -261,10 +273,14 @@ export class Server {
// Channelz Info // Channelz Info
private readonly channelzEnabled: boolean = true; private readonly channelzEnabled: boolean = true;
private channelzRef: ServerRef; private channelzRef: ServerRef;
private channelzTrace = new ChannelzTrace(); private channelzTrace: ChannelzTrace | ChannelzTraceStub;
private callTracker = new ChannelzCallTracker(); private callTracker: ChannelzCallTracker | ChannelzCallTrackerStub;
private listenerChildrenTracker = new ChannelzChildrenTracker(); private listenerChildrenTracker:
private sessionChildrenTracker = new ChannelzChildrenTracker(); | ChannelzChildrenTracker
| ChannelzChildrenTrackerStub;
private sessionChildrenTracker:
| ChannelzChildrenTracker
| ChannelzChildrenTrackerStub;
private readonly maxConnectionAgeMs: number; private readonly maxConnectionAgeMs: number;
private readonly maxConnectionAgeGraceMs: number; private readonly maxConnectionAgeGraceMs: number;
@ -272,6 +288,8 @@ export class Server {
private readonly keepaliveTimeMs: number; private readonly keepaliveTimeMs: number;
private readonly keepaliveTimeoutMs: number; private readonly keepaliveTimeoutMs: number;
private readonly sessionIdleTimeout: number;
private readonly interceptors: ServerInterceptor[]; private readonly interceptors: ServerInterceptor[];
/** /**
@ -284,14 +302,24 @@ export class Server {
this.options = options ?? {}; this.options = options ?? {};
if (this.options['grpc.enable_channelz'] === 0) { if (this.options['grpc.enable_channelz'] === 0) {
this.channelzEnabled = false; this.channelzEnabled = false;
this.channelzTrace = new ChannelzTraceStub();
this.callTracker = new ChannelzCallTrackerStub();
this.listenerChildrenTracker = new ChannelzChildrenTrackerStub();
this.sessionChildrenTracker = new ChannelzChildrenTrackerStub();
} else {
this.channelzTrace = new ChannelzTrace();
this.callTracker = new ChannelzCallTracker();
this.listenerChildrenTracker = new ChannelzChildrenTracker();
this.sessionChildrenTracker = new ChannelzChildrenTracker();
} }
this.channelzRef = registerChannelzServer( this.channelzRef = registerChannelzServer(
'server',
() => this.getChannelzInfo(), () => this.getChannelzInfo(),
this.channelzEnabled this.channelzEnabled
); );
if (this.channelzEnabled) {
this.channelzTrace.addTrace('CT_INFO', 'Server created'); this.channelzTrace.addTrace('CT_INFO', 'Server created');
}
this.maxConnectionAgeMs = this.maxConnectionAgeMs =
this.options['grpc.max_connection_age_ms'] ?? UNLIMITED_CONNECTION_AGE_MS; this.options['grpc.max_connection_age_ms'] ?? UNLIMITED_CONNECTION_AGE_MS;
this.maxConnectionAgeGraceMs = this.maxConnectionAgeGraceMs =
@ -301,6 +329,9 @@ export class Server {
this.options['grpc.keepalive_time_ms'] ?? KEEPALIVE_MAX_TIME_MS; this.options['grpc.keepalive_time_ms'] ?? KEEPALIVE_MAX_TIME_MS;
this.keepaliveTimeoutMs = this.keepaliveTimeoutMs =
this.options['grpc.keepalive_timeout_ms'] ?? KEEPALIVE_TIMEOUT_MS; this.options['grpc.keepalive_timeout_ms'] ?? KEEPALIVE_TIMEOUT_MS;
this.sessionIdleTimeout =
this.options['grpc.max_connection_idle'] ?? MAX_CONNECTION_IDLE_MS;
this.commonServerOptions = { this.commonServerOptions = {
maxSendHeaderBlockLength: Number.MAX_SAFE_INTEGER, maxSendHeaderBlockLength: Number.MAX_SAFE_INTEGER,
}; };
@ -382,7 +413,7 @@ export class Server {
streamsFailed: sessionInfo.streamTracker.callsFailed, streamsFailed: sessionInfo.streamTracker.callsFailed,
messagesSent: sessionInfo.messagesSent, messagesSent: sessionInfo.messagesSent,
messagesReceived: sessionInfo.messagesReceived, messagesReceived: sessionInfo.messagesReceived,
keepAlivesSent: 0, keepAlivesSent: sessionInfo.keepAlivesSent,
lastLocalStreamCreatedTimestamp: null, lastLocalStreamCreatedTimestamp: null,
lastRemoteStreamCreatedTimestamp: lastRemoteStreamCreatedTimestamp:
sessionInfo.streamTracker.lastCallStartedTimestamp, sessionInfo.streamTracker.lastCallStartedTimestamp,
@ -581,9 +612,8 @@ export class Server {
const channelzRef = this.registerListenerToChannelz( const channelzRef = this.registerListenerToChannelz(
boundSubchannelAddress boundSubchannelAddress
); );
if (this.channelzEnabled) { this.listenerChildrenTracker.refChild(channelzRef);
this.listenerChildrenTracker.refChild(channelzRef);
}
this.http2Servers.set(http2Server, { this.http2Servers.set(http2Server, {
channelzRef: channelzRef, channelzRef: channelzRef,
sessions: new Set(), sessions: new Set(),
@ -854,7 +884,7 @@ export class Server {
); );
const serverInfo = this.http2Servers.get(server); const serverInfo = this.http2Servers.get(server);
server.close(() => { server.close(() => {
if (this.channelzEnabled && serverInfo) { if (serverInfo) {
this.listenerChildrenTracker.unrefChild(serverInfo.channelzRef); this.listenerChildrenTracker.unrefChild(serverInfo.channelzRef);
unregisterChannelzRef(serverInfo.channelzRef); unregisterChannelzRef(serverInfo.channelzRef);
} }
@ -870,15 +900,15 @@ export class Server {
this.trace('Closing session initiated by ' + session.socket?.remoteAddress); this.trace('Closing session initiated by ' + session.socket?.remoteAddress);
const sessionInfo = this.sessions.get(session); const sessionInfo = this.sessions.get(session);
const closeCallback = () => { const closeCallback = () => {
if (this.channelzEnabled && sessionInfo) { if (sessionInfo) {
this.sessionChildrenTracker.unrefChild(sessionInfo.ref); this.sessionChildrenTracker.unrefChild(sessionInfo.ref);
unregisterChannelzRef(sessionInfo.ref); unregisterChannelzRef(sessionInfo.ref);
this.sessions.delete(session);
} }
this.sessions.delete(session);
callback?.(); callback?.();
}; };
if (session.closed) { if (session.closed) {
process.nextTick(closeCallback); queueMicrotask(closeCallback);
} else { } else {
session.close(closeCallback); session.close(closeCallback);
} }
@ -956,14 +986,13 @@ export class Server {
const allSessions: Set<http2.Http2Session> = new Set(); const allSessions: Set<http2.Http2Session> = new Set();
for (const http2Server of boundPortObject.listeningServers) { for (const http2Server of boundPortObject.listeningServers) {
const serverEntry = this.http2Servers.get(http2Server); const serverEntry = this.http2Servers.get(http2Server);
if (!serverEntry) { if (serverEntry) {
continue; for (const session of serverEntry.sessions) {
} allSessions.add(session);
for (const session of serverEntry.sessions) { this.closeSession(session, () => {
allSessions.add(session); allSessions.delete(session);
this.closeSession(session, () => { });
allSessions.delete(session); }
});
} }
} }
/* After the grace time ends, send another goaway to all remaining sessions /* After the grace time ends, send another goaway to all remaining sessions
@ -995,9 +1024,7 @@ export class Server {
session.destroy(http2.constants.NGHTTP2_CANCEL as any); session.destroy(http2.constants.NGHTTP2_CANCEL as any);
}); });
this.sessions.clear(); this.sessions.clear();
if (this.channelzEnabled) { unregisterChannelzRef(this.channelzRef);
unregisterChannelzRef(this.channelzRef);
}
this.shutdown = true; this.shutdown = true;
} }
@ -1049,9 +1076,7 @@ export class Server {
tryShutdown(callback: (error?: Error) => void): void { tryShutdown(callback: (error?: Error) => void): void {
const wrappedCallback = (error?: Error) => { const wrappedCallback = (error?: Error) => {
if (this.channelzEnabled) { unregisterChannelzRef(this.channelzRef);
unregisterChannelzRef(this.channelzRef);
}
callback(error); callback(error);
}; };
let pendingChecks = 0; let pendingChecks = 0;
@ -1065,24 +1090,26 @@ export class Server {
} }
this.shutdown = true; this.shutdown = true;
for (const server of this.http2Servers.keys()) { for (const [serverKey, server] of this.http2Servers.entries()) {
pendingChecks++; pendingChecks++;
const serverString = this.http2Servers.get(server)!.channelzRef.name; const serverString = server.channelzRef.name;
this.trace('Waiting for server ' + serverString + ' to close'); this.trace('Waiting for server ' + serverString + ' to close');
this.closeServer(server, () => { this.closeServer(serverKey, () => {
this.trace('Server ' + serverString + ' finished closing'); this.trace('Server ' + serverString + ' finished closing');
maybeCallback(); maybeCallback();
}); });
for (const session of server.sessions.keys()) {
pendingChecks++;
const sessionString = session.socket?.remoteAddress;
this.trace('Waiting for session ' + sessionString + ' to close');
this.closeSession(session, () => {
this.trace('Session ' + sessionString + ' finished closing');
maybeCallback();
});
}
} }
for (const session of this.sessions.keys()) {
pendingChecks++;
const sessionString = session.socket?.remoteAddress;
this.trace('Waiting for session ' + sessionString + ' to close');
this.closeSession(session, () => {
this.trace('Session ' + sessionString + ' finished closing');
maybeCallback();
});
}
if (pendingChecks === 0) { if (pendingChecks === 0) {
wrappedCallback(); wrappedCallback();
} }
@ -1160,142 +1187,395 @@ export class Server {
}; };
stream.respond(trailersToSend, { endStream: true }); stream.respond(trailersToSend, { endStream: true });
if (this.channelzEnabled) { this.callTracker.addCallFailed();
this.callTracker.addCallFailed(); channelzSessionInfo?.streamTracker.addCallFailed();
channelzSessionInfo?.streamTracker.addCallFailed();
}
} }
private _channelzHandler( private _sessionHandler(
stream: http2.ServerHttp2Stream, http2Server: http2.Http2Server | http2.Http2SecureServer
headers: http2.IncomingHttpHeaders
) { ) {
const channelzSessionInfo = this.sessions.get( return (session: http2.ServerHttp2Session) => {
stream.session as http2.ServerHttp2Session this.http2Servers.get(http2Server)?.sessions.add(session);
);
this.callTracker.addCallStarted(); let connectionAgeTimer: NodeJS.Timeout | null = null;
channelzSessionInfo?.streamTracker.addCallStarted(); let connectionAgeGraceTimer: NodeJS.Timeout | null = null;
let sessionClosedByServer = false;
if (!this._verifyContentType(stream, headers)) { const idleTimeoutObj = this.enableIdleTimeout(session);
this.callTracker.addCallFailed();
channelzSessionInfo?.streamTracker.addCallFailed();
return;
}
const path = headers[HTTP2_HEADER_PATH] as string; if (this.maxConnectionAgeMs !== UNLIMITED_CONNECTION_AGE_MS) {
// Apply a random jitter within a +/-10% range
const jitterMagnitude = this.maxConnectionAgeMs / 10;
const jitter = Math.random() * jitterMagnitude * 2 - jitterMagnitude;
const handler = this._retrieveHandler(path); connectionAgeTimer = setTimeout(() => {
if (!handler) { sessionClosedByServer = true;
this._respondWithError(
getUnimplementedStatusResponse(path),
stream,
channelzSessionInfo
);
return;
}
const callEventTracker: CallEventTracker = { this.trace(
addMessageSent: () => { `Connection dropped by max connection age: ${session.socket?.remoteAddress}`
if (channelzSessionInfo) { );
channelzSessionInfo.messagesSent += 1;
channelzSessionInfo.lastMessageSentTimestamp = new Date(); try {
} session.goaway(
}, http2.constants.NGHTTP2_NO_ERROR,
addMessageReceived: () => { ~(1 << 31),
if (channelzSessionInfo) { Buffer.from('max_age')
channelzSessionInfo.messagesReceived += 1; );
channelzSessionInfo.lastMessageReceivedTimestamp = new Date(); } catch (e) {
} // The goaway can't be sent because the session is already closed
}, session.destroy();
onCallEnd: status => { return;
if (status.code === Status.OK) {
this.callTracker.addCallSucceeded();
} else {
this.callTracker.addCallFailed();
}
},
onStreamEnd: success => {
if (channelzSessionInfo) {
if (success) {
channelzSessionInfo.streamTracker.addCallSucceeded();
} else {
channelzSessionInfo.streamTracker.addCallFailed();
} }
session.close();
/* Allow a grace period after sending the GOAWAY before forcibly
* closing the connection. */
if (this.maxConnectionAgeGraceMs !== UNLIMITED_CONNECTION_AGE_MS) {
connectionAgeGraceTimer = setTimeout(() => {
session.destroy();
}, this.maxConnectionAgeGraceMs).unref?.();
}
}, this.maxConnectionAgeMs + jitter).unref?.();
}
const keeapliveTimeTimer: NodeJS.Timeout | null = setInterval(() => {
const timeoutTImer = setTimeout(() => {
sessionClosedByServer = true;
session.close();
}, this.keepaliveTimeoutMs).unref?.();
try {
session.ping(
(err: Error | null, duration: number, payload: Buffer) => {
clearTimeout(timeoutTImer);
if (err) {
sessionClosedByServer = true;
this.trace(
`Connection dropped due to error of a ping frame ${err.message} return in ${duration}`
);
session.close();
}
}
);
} catch (e) {
// The ping can't be sent because the session is already closed
session.destroy();
} }
}, }, this.keepaliveTimeMs).unref?.();
};
const call = getServerInterceptingCall( session.on('close', () => {
this.interceptors, if (!sessionClosedByServer) {
stream, this.trace(
headers, `Connection dropped by client ${session.socket?.remoteAddress}`
callEventTracker, );
handler, }
this.options
);
if (!this._runHandlerForCall(call, handler)) { if (connectionAgeTimer) {
this.callTracker.addCallFailed(); clearTimeout(connectionAgeTimer);
channelzSessionInfo?.streamTracker.addCallFailed(); }
call.sendStatus({ if (connectionAgeGraceTimer) {
code: Status.INTERNAL, clearTimeout(connectionAgeGraceTimer);
details: `Unknown handler type: ${handler.type}`, }
if (keeapliveTimeTimer) {
clearTimeout(keeapliveTimeTimer);
}
clearTimeout(idleTimeoutObj.timeout);
this.sessionIdleTimeouts.delete(session);
this.http2Servers.get(http2Server)?.sessions.delete(session);
}); });
} };
} }
private _streamHandler( private _channelzSessionHandler(
stream: http2.ServerHttp2Stream, http2Server: http2.Http2Server | http2.Http2SecureServer
headers: http2.IncomingHttpHeaders
) { ) {
if (this._verifyContentType(stream, headers) !== true) { return (session: http2.ServerHttp2Session) => {
return; const channelzRef = registerChannelzSocket(
} session.socket?.remoteAddress ?? 'unknown',
this.getChannelzSessionInfoGetter(session),
const path = headers[HTTP2_HEADER_PATH] as string; this.channelzEnabled
const handler = this._retrieveHandler(path);
if (!handler) {
this._respondWithError(
getUnimplementedStatusResponse(path),
stream,
null
); );
return;
}
const call = getServerInterceptingCall( const channelzSessionInfo: ChannelzSessionInfo = {
this.interceptors, ref: channelzRef,
stream, streamTracker: this.channelzEnabled
headers, ? new ChannelzCallTracker()
null, : new ChannelzCallTrackerStub(),
handler, messagesSent: 0,
this.options messagesReceived: 0,
); keepAlivesSent: 0,
lastMessageSentTimestamp: null,
lastMessageReceivedTimestamp: null,
};
if (!this._runHandlerForCall(call, handler)) { this.http2Servers.get(http2Server)?.sessions.add(session);
call.sendStatus({ this.sessions.set(session, channelzSessionInfo);
code: Status.INTERNAL, const clientAddress = `${session.socket.remoteAddress}:${session.socket.remotePort}`;
details: `Unknown handler type: ${handler.type}`,
this.channelzTrace.addTrace(
'CT_INFO',
'Connection established by client ' + clientAddress
);
this.trace('Connection established by client ' + clientAddress);
this.sessionChildrenTracker.refChild(channelzRef);
let connectionAgeTimer: NodeJS.Timeout | null = null;
let connectionAgeGraceTimer: NodeJS.Timeout | null = null;
let sessionClosedByServer = false;
const idleTimeoutObj = this.enableIdleTimeout(session);
if (this.maxConnectionAgeMs !== UNLIMITED_CONNECTION_AGE_MS) {
// Apply a random jitter within a +/-10% range
const jitterMagnitude = this.maxConnectionAgeMs / 10;
const jitter = Math.random() * jitterMagnitude * 2 - jitterMagnitude;
connectionAgeTimer = setTimeout(() => {
sessionClosedByServer = true;
this.channelzTrace.addTrace(
'CT_INFO',
'Connection dropped by max connection age from ' + clientAddress
);
try {
session.goaway(
http2.constants.NGHTTP2_NO_ERROR,
~(1 << 31),
Buffer.from('max_age')
);
} catch (e) {
// The goaway can't be sent because the session is already closed
session.destroy();
return;
}
session.close();
/* Allow a grace period after sending the GOAWAY before forcibly
* closing the connection. */
if (this.maxConnectionAgeGraceMs !== UNLIMITED_CONNECTION_AGE_MS) {
connectionAgeGraceTimer = setTimeout(() => {
session.destroy();
}, this.maxConnectionAgeGraceMs).unref?.();
}
}, this.maxConnectionAgeMs + jitter).unref?.();
}
const keeapliveTimeTimer: NodeJS.Timeout | null = setInterval(() => {
const timeoutTImer = setTimeout(() => {
sessionClosedByServer = true;
this.channelzTrace.addTrace(
'CT_INFO',
'Connection dropped by keepalive timeout from ' + clientAddress
);
session.close();
}, this.keepaliveTimeoutMs).unref?.();
try {
session.ping(
(err: Error | null, duration: number, payload: Buffer) => {
clearTimeout(timeoutTImer);
if (err) {
sessionClosedByServer = true;
this.channelzTrace.addTrace(
'CT_INFO',
`Connection dropped due to error of a ping frame ${err.message} return in ${duration}`
);
session.close();
}
}
);
channelzSessionInfo.keepAlivesSent += 1;
} catch (e) {
// The ping can't be sent because the session is already closed
session.destroy();
}
}, this.keepaliveTimeMs).unref?.();
session.on('close', () => {
if (!sessionClosedByServer) {
this.channelzTrace.addTrace(
'CT_INFO',
'Connection dropped by client ' + clientAddress
);
}
this.trace(
`DROPPING ${channelzRef.name} - ${channelzRef.kind} - ${channelzRef.id}`
);
this.sessionChildrenTracker.unrefChild(channelzRef);
unregisterChannelzRef(channelzRef);
if (connectionAgeTimer) {
clearTimeout(connectionAgeTimer);
}
if (connectionAgeGraceTimer) {
clearTimeout(connectionAgeGraceTimer);
}
if (keeapliveTimeTimer) {
clearTimeout(keeapliveTimeTimer);
}
clearTimeout(idleTimeoutObj.timeout);
this.sessionIdleTimeouts.delete(session);
this.http2Servers.get(http2Server)?.sessions.delete(session);
this.sessions.delete(session);
}); });
} };
}
private _channelzHandler(http2Server: http2.Http2Server) {
return (
stream: http2.ServerHttp2Stream,
headers: http2.IncomingHttpHeaders
) => {
// for handling idle timeout
this.onStreamOpened(stream);
const channelzSessionInfo = this.sessions.get(
stream.session as http2.ServerHttp2Session
);
this.callTracker.addCallStarted();
channelzSessionInfo?.streamTracker.addCallStarted();
if (!this._verifyContentType(stream, headers)) {
this.callTracker.addCallFailed();
channelzSessionInfo?.streamTracker.addCallFailed();
return;
}
const path = headers[HTTP2_HEADER_PATH] as string;
const handler = this._retrieveHandler(path);
if (!handler) {
this._respondWithError(
getUnimplementedStatusResponse(path),
stream,
channelzSessionInfo
);
return;
}
const callEventTracker: CallEventTracker = {
addMessageSent: () => {
if (channelzSessionInfo) {
channelzSessionInfo.messagesSent += 1;
channelzSessionInfo.lastMessageSentTimestamp = new Date();
}
},
addMessageReceived: () => {
if (channelzSessionInfo) {
channelzSessionInfo.messagesReceived += 1;
channelzSessionInfo.lastMessageReceivedTimestamp = new Date();
}
},
onCallEnd: status => {
if (status.code === Status.OK) {
this.callTracker.addCallSucceeded();
} else {
this.callTracker.addCallFailed();
}
},
onStreamEnd: success => {
if (channelzSessionInfo) {
if (success) {
channelzSessionInfo.streamTracker.addCallSucceeded();
} else {
channelzSessionInfo.streamTracker.addCallFailed();
}
}
},
};
const call = getServerInterceptingCall(
this.interceptors,
stream,
headers,
callEventTracker,
handler,
this.options
);
if (!this._runHandlerForCall(call, handler)) {
this.callTracker.addCallFailed();
channelzSessionInfo?.streamTracker.addCallFailed();
call.sendStatus({
code: Status.INTERNAL,
details: `Unknown handler type: ${handler.type}`,
});
}
};
}
private _streamHandler(http2Server: http2.Http2Server) {
return (
stream: http2.ServerHttp2Stream,
headers: http2.IncomingHttpHeaders
) => {
// for handling idle timeout
this.onStreamOpened(stream);
if (this._verifyContentType(stream, headers) !== true) {
return;
}
const path = headers[HTTP2_HEADER_PATH] as string;
const handler = this._retrieveHandler(path);
if (!handler) {
this._respondWithError(
getUnimplementedStatusResponse(path),
stream,
null
);
return;
}
const call = getServerInterceptingCall(
this.interceptors,
stream,
headers,
null,
handler,
this.options
);
if (!this._runHandlerForCall(call, handler)) {
call.sendStatus({
code: Status.INTERNAL,
details: `Unknown handler type: ${handler.type}`,
});
}
};
} }
private _runHandlerForCall( private _runHandlerForCall(
call: ServerInterceptingCallInterface, call: ServerInterceptingCallInterface,
handler: Handler<any, any> handler:
| UntypedUnaryHandler
| UntypedClientStreamingHandler
| UntypedServerStreamingHandler
| UntypedBidiStreamingHandler
): boolean { ): boolean {
const { type } = handler; const { type } = handler;
if (type === 'unary') { if (type === 'unary') {
handleUnary(call, handler as UntypedUnaryHandler); handleUnary(call, handler);
} else if (type === 'clientStream') { } else if (type === 'clientStream') {
handleClientStreaming(call, handler as UntypedClientStreamingHandler); handleClientStreaming(call, handler);
} else if (type === 'serverStream') { } else if (type === 'serverStream') {
handleServerStreaming(call, handler as UntypedServerStreamingHandler); handleServerStreaming(call, handler);
} else if (type === 'bidi') { } else if (type === 'bidi') {
handleBidiStreaming(call, handler as UntypedBidiStreamingHandler); handleBidiStreaming(call, handler);
} else { } else {
return false; return false;
} }
@ -1322,118 +1602,78 @@ export class Server {
this.serverAddressString = serverAddressString; this.serverAddressString = serverAddressString;
const handler = this.channelzEnabled const handler = this.channelzEnabled
? this._channelzHandler ? this._channelzHandler(http2Server)
: this._streamHandler; : this._streamHandler(http2Server);
http2Server.on('stream', handler.bind(this)); const sessionHandler = this.channelzEnabled
http2Server.on('session', session => { ? this._channelzSessionHandler(http2Server)
const channelzRef = registerChannelzSocket( : this._sessionHandler(http2Server);
session.socket.remoteAddress ?? 'unknown',
this.getChannelzSessionInfoGetter(session), http2Server.on('stream', handler);
this.channelzEnabled http2Server.on('session', sessionHandler);
}
private enableIdleTimeout(session: http2.ServerHttp2Session) {
const idleTimeoutObj = {
activeStreams: 0,
timeout: setTimeout(
this.onIdleTimeout,
this.sessionIdleTimeout,
this,
session
).unref(),
};
this.sessionIdleTimeouts.set(session, idleTimeoutObj);
this.trace(`Enable idle timeout for ${session.socket?.remoteAddress}`);
return idleTimeoutObj;
}
private onIdleTimeout(ctx: Server, session: http2.ServerHttp2Session) {
ctx.trace(`Idle timeout for ${session.socket?.remoteAddress}`);
ctx.closeSession(session);
}
private onStreamOpened(stream: http2.ServerHttp2Stream) {
const session = stream.session as http2.ServerHttp2Session;
this.trace(`Stream opened for ${session.socket?.remoteAddress}`);
const idleTimeoutObj = this.sessionIdleTimeouts.get(session);
if (idleTimeoutObj) {
idleTimeoutObj.activeStreams += 1;
if (idleTimeoutObj.timeout) {
clearTimeout(idleTimeoutObj.timeout);
idleTimeoutObj.timeout = null;
}
this.trace(
`onStreamOpened: adding on stream close event for ${session.socket?.remoteAddress}`
); );
stream.once('close', () => this.onStreamClose(session));
} else {
this.trace(
`onStreamOpened: missing stream for ${session.socket?.remoteAddress}`
);
}
}
const channelzSessionInfo: ChannelzSessionInfo = { private onStreamClose(session: http2.ServerHttp2Session) {
ref: channelzRef, this.trace(`Stream closed for ${session.socket?.remoteAddress}`);
streamTracker: new ChannelzCallTracker(), const idleTimeoutObj = this.sessionIdleTimeouts.get(session);
messagesSent: 0, if (idleTimeoutObj) {
messagesReceived: 0, idleTimeoutObj.activeStreams -= 1;
lastMessageSentTimestamp: null, if (idleTimeoutObj.activeStreams === 0) {
lastMessageReceivedTimestamp: null, this.trace(
}; `onStreamClose: set idle timeout for ${this.sessionIdleTimeout}ms ${session.socket?.remoteAddress}`
this.http2Servers.get(http2Server)?.sessions.add(session);
this.sessions.set(session, channelzSessionInfo);
const clientAddress = session.socket.remoteAddress;
if (this.channelzEnabled) {
this.channelzTrace.addTrace(
'CT_INFO',
'Connection established by client ' + clientAddress
); );
this.sessionChildrenTracker.refChild(channelzRef); idleTimeoutObj.timeout = setTimeout(
this.onIdleTimeout,
this.sessionIdleTimeout,
this,
session
).unref();
} }
let connectionAgeTimer: NodeJS.Timeout | null = null; }
let connectionAgeGraceTimer: NodeJS.Timeout | null = null;
let sessionClosedByServer = false;
if (this.maxConnectionAgeMs !== UNLIMITED_CONNECTION_AGE_MS) {
// Apply a random jitter within a +/-10% range
const jitterMagnitude = this.maxConnectionAgeMs / 10;
const jitter = Math.random() * jitterMagnitude * 2 - jitterMagnitude;
connectionAgeTimer = setTimeout(() => {
sessionClosedByServer = true;
if (this.channelzEnabled) {
this.channelzTrace.addTrace(
'CT_INFO',
'Connection dropped by max connection age from ' + clientAddress
);
}
try {
session.goaway(
http2.constants.NGHTTP2_NO_ERROR,
~(1 << 31),
Buffer.from('max_age')
);
} catch (e) {
// The goaway can't be sent because the session is already closed
session.destroy();
return;
}
session.close();
/* Allow a grace period after sending the GOAWAY before forcibly
* closing the connection. */
if (this.maxConnectionAgeGraceMs !== UNLIMITED_CONNECTION_AGE_MS) {
connectionAgeGraceTimer = setTimeout(() => {
session.destroy();
}, this.maxConnectionAgeGraceMs).unref?.();
}
}, this.maxConnectionAgeMs + jitter).unref?.();
}
const keeapliveTimeTimer: NodeJS.Timeout | null = setInterval(() => {
const timeoutTImer = setTimeout(() => {
sessionClosedByServer = true;
if (this.channelzEnabled) {
this.channelzTrace.addTrace(
'CT_INFO',
'Connection dropped by keepalive timeout from ' + clientAddress
);
}
session.close();
}, this.keepaliveTimeoutMs).unref?.();
try {
session.ping(
(err: Error | null, duration: number, payload: Buffer) => {
clearTimeout(timeoutTImer);
}
);
} catch (e) {
// The ping can't be sent because the session is already closed
session.destroy();
}
}, this.keepaliveTimeMs).unref?.();
session.on('close', () => {
if (this.channelzEnabled) {
if (!sessionClosedByServer) {
this.channelzTrace.addTrace(
'CT_INFO',
'Connection dropped by client ' + clientAddress
);
}
this.sessionChildrenTracker.unrefChild(channelzRef);
unregisterChannelzRef(channelzRef);
}
if (connectionAgeTimer) {
clearTimeout(connectionAgeTimer);
}
if (connectionAgeGraceTimer) {
clearTimeout(connectionAgeGraceTimer);
}
if (keeapliveTimeTimer) {
clearTimeout(keeapliveTimeTimer);
}
this.http2Servers.get(http2Server)?.sessions.delete(session);
this.sessions.delete(session);
});
});
} }
} }

View File

@ -15,7 +15,7 @@
* *
*/ */
import { SubchannelRef } from './channelz'; import type { SubchannelRef } from './channelz';
import { ConnectivityState } from './connectivity-state'; import { ConnectivityState } from './connectivity-state';
import { Subchannel } from './subchannel'; import { Subchannel } from './subchannel';

View File

@ -31,10 +31,13 @@ import {
SubchannelRef, SubchannelRef,
ChannelzTrace, ChannelzTrace,
ChannelzChildrenTracker, ChannelzChildrenTracker,
ChannelzChildrenTrackerStub,
SubchannelInfo, SubchannelInfo,
registerChannelzSubchannel, registerChannelzSubchannel,
ChannelzCallTracker, ChannelzCallTracker,
ChannelzCallTrackerStub,
unregisterChannelzRef, unregisterChannelzRef,
ChannelzTraceStub,
} from './channelz'; } from './channelz';
import { import {
ConnectivityStateListener, ConnectivityStateListener,
@ -89,12 +92,15 @@ export class Subchannel {
// Channelz info // Channelz info
private readonly channelzEnabled: boolean = true; private readonly channelzEnabled: boolean = true;
private channelzRef: SubchannelRef; private channelzRef: SubchannelRef;
private channelzTrace: ChannelzTrace;
private callTracker = new ChannelzCallTracker(); private channelzTrace: ChannelzTrace | ChannelzTraceStub;
private childrenTracker = new ChannelzChildrenTracker(); private callTracker: ChannelzCallTracker | ChannelzCallTrackerStub;
private childrenTracker:
| ChannelzChildrenTracker
| ChannelzChildrenTrackerStub;
// Channelz socket info // Channelz socket info
private streamTracker = new ChannelzCallTracker(); private streamTracker: ChannelzCallTracker | ChannelzCallTrackerStub;
/** /**
* A class representing a connection to a single backend. * A class representing a connection to a single backend.
@ -127,16 +133,24 @@ export class Subchannel {
if (options['grpc.enable_channelz'] === 0) { if (options['grpc.enable_channelz'] === 0) {
this.channelzEnabled = false; this.channelzEnabled = false;
this.channelzTrace = new ChannelzTraceStub();
this.callTracker = new ChannelzCallTrackerStub();
this.childrenTracker = new ChannelzChildrenTrackerStub();
this.streamTracker = new ChannelzCallTrackerStub();
} else {
this.channelzTrace = new ChannelzTrace();
this.callTracker = new ChannelzCallTracker();
this.childrenTracker = new ChannelzChildrenTracker();
this.streamTracker = new ChannelzCallTracker();
} }
this.channelzTrace = new ChannelzTrace();
this.channelzRef = registerChannelzSubchannel( this.channelzRef = registerChannelzSubchannel(
this.subchannelAddressString, this.subchannelAddressString,
() => this.getChannelzInfo(), () => this.getChannelzInfo(),
this.channelzEnabled this.channelzEnabled
); );
if (this.channelzEnabled) {
this.channelzTrace.addTrace('CT_INFO', 'Subchannel created'); this.channelzTrace.addTrace('CT_INFO', 'Subchannel created');
}
this.trace( this.trace(
'Subchannel constructed with options ' + 'Subchannel constructed with options ' +
JSON.stringify(options, undefined, 2) JSON.stringify(options, undefined, 2)
@ -338,12 +352,8 @@ export class Subchannel {
this.refTrace('refcount ' + this.refcount + ' -> ' + (this.refcount - 1)); this.refTrace('refcount ' + this.refcount + ' -> ' + (this.refcount - 1));
this.refcount -= 1; this.refcount -= 1;
if (this.refcount === 0) { if (this.refcount === 0) {
if (this.channelzEnabled) { this.channelzTrace.addTrace('CT_INFO', 'Shutting down');
this.channelzTrace.addTrace('CT_INFO', 'Shutting down'); unregisterChannelzRef(this.channelzRef);
}
if (this.channelzEnabled) {
unregisterChannelzRef(this.channelzRef);
}
process.nextTick(() => { process.nextTick(() => {
this.transitionToState( this.transitionToState(
[ConnectivityState.CONNECTING, ConnectivityState.READY], [ConnectivityState.CONNECTING, ConnectivityState.READY],

View File

@ -28,6 +28,7 @@ import { ChannelCredentials } from './channel-credentials';
import { ChannelOptions } from './channel-options'; import { ChannelOptions } from './channel-options';
import { import {
ChannelzCallTracker, ChannelzCallTracker,
ChannelzCallTrackerStub,
registerChannelzSocket, registerChannelzSocket,
SocketInfo, SocketInfo,
SocketRef, SocketRef,
@ -136,7 +137,7 @@ class Http2Transport implements Transport {
// Channelz info // Channelz info
private channelzRef: SocketRef; private channelzRef: SocketRef;
private readonly channelzEnabled: boolean = true; private readonly channelzEnabled: boolean = true;
private streamTracker = new ChannelzCallTracker(); private streamTracker: ChannelzCallTracker | ChannelzCallTrackerStub;
private keepalivesSent = 0; private keepalivesSent = 0;
private messagesSent = 0; private messagesSent = 0;
private messagesReceived = 0; private messagesReceived = 0;
@ -159,12 +160,17 @@ class Http2Transport implements Transport {
if (options['grpc.enable_channelz'] === 0) { if (options['grpc.enable_channelz'] === 0) {
this.channelzEnabled = false; this.channelzEnabled = false;
this.streamTracker = new ChannelzCallTrackerStub();
} else {
this.streamTracker = new ChannelzCallTracker();
} }
this.channelzRef = registerChannelzSocket( this.channelzRef = registerChannelzSocket(
this.subchannelAddressString, this.subchannelAddressString,
() => this.getChannelzInfo(), () => this.getChannelzInfo(),
this.channelzEnabled this.channelzEnabled
); );
// Build user-agent string. // Build user-agent string.
this.userAgent = [ this.userAgent = [
options['grpc.primary_user_agent'], options['grpc.primary_user_agent'],
@ -192,6 +198,7 @@ class Http2Transport implements Transport {
this.stopKeepalivePings(); this.stopKeepalivePings();
this.handleDisconnect(); this.handleDisconnect();
}); });
session.once( session.once(
'goaway', 'goaway',
(errorCode: number, lastStreamID: number, opaqueData?: Buffer) => { (errorCode: number, lastStreamID: number, opaqueData?: Buffer) => {
@ -214,11 +221,13 @@ class Http2Transport implements Transport {
this.reportDisconnectToOwner(tooManyPings); this.reportDisconnectToOwner(tooManyPings);
} }
); );
session.once('error', error => { session.once('error', error => {
/* Do nothing here. Any error should also trigger a close event, which is /* Do nothing here. Any error should also trigger a close event, which is
* where we want to handle that. */ * where we want to handle that. */
this.trace('connection closed with error ' + (error as Error).message); this.trace('connection closed with error ' + (error as Error).message);
}); });
if (logging.isTracerEnabled(TRACER_NAME)) { if (logging.isTracerEnabled(TRACER_NAME)) {
session.on('remoteSettings', (settings: http2.Settings) => { session.on('remoteSettings', (settings: http2.Settings) => {
this.trace( this.trace(
@ -237,6 +246,7 @@ class Http2Transport implements Transport {
); );
}); });
} }
/* Start the keepalive timer last, because this can trigger trace logs, /* Start the keepalive timer last, because this can trigger trace logs,
* which should only happen after everything else is set up. */ * which should only happen after everything else is set up. */
if (this.keepaliveWithoutCalls) { if (this.keepaliveWithoutCalls) {
@ -625,6 +635,7 @@ export class Http2SubchannelConnector implements SubchannelConnector {
private session: http2.ClientHttp2Session | null = null; private session: http2.ClientHttp2Session | null = null;
private isShutdown = false; private isShutdown = false;
constructor(private channelTarget: GrpcUri) {} constructor(private channelTarget: GrpcUri) {}
private trace(text: string) { private trace(text: string) {
logging.trace( logging.trace(
LogVerbosity.DEBUG, LogVerbosity.DEBUG,
@ -632,6 +643,7 @@ export class Http2SubchannelConnector implements SubchannelConnector {
uriToString(this.channelTarget) + ' ' + text uriToString(this.channelTarget) + ' ' + text
); );
} }
private createSession( private createSession(
address: SubchannelAddress, address: SubchannelAddress,
credentials: ChannelCredentials, credentials: ChannelCredentials,
@ -641,6 +653,7 @@ export class Http2SubchannelConnector implements SubchannelConnector {
if (this.isShutdown) { if (this.isShutdown) {
return Promise.reject(); return Promise.reject();
} }
return new Promise<Http2Transport>((resolve, reject) => { return new Promise<Http2Transport>((resolve, reject) => {
let remoteName: string | null; let remoteName: string | null;
if (proxyConnectionResult.realTarget) { if (proxyConnectionResult.realTarget) {
@ -767,6 +780,7 @@ export class Http2SubchannelConnector implements SubchannelConnector {
}); });
}); });
} }
connect( connect(
address: SubchannelAddress, address: SubchannelAddress,
credentials: ChannelCredentials, credentials: ChannelCredentials,

View File

@ -31,7 +31,7 @@ import {
HealthListener, HealthListener,
SubchannelInterface, SubchannelInterface,
} from '../src/subchannel-interface'; } from '../src/subchannel-interface';
import { SubchannelRef } from '../src/channelz'; import { EntityTypes, SubchannelRef } from '../src/channelz';
import { Subchannel } from '../src/subchannel'; import { Subchannel } from '../src/subchannel';
import { ConnectivityState } from '../src/connectivity-state'; import { ConnectivityState } from '../src/connectivity-state';
@ -196,7 +196,7 @@ export class MockSubchannel implements SubchannelInterface {
unref(): void {} unref(): void {}
getChannelzRef(): SubchannelRef { getChannelzRef(): SubchannelRef {
return { return {
kind: 'subchannel', kind: EntityTypes.subchannel,
id: -1, id: -1,
name: this.address, name: this.address,
}; };