mirror of https://github.com/grpc/grpc-node.git
feat: channelz improvements, idle timeout implementation
This commit is contained in:
parent
210967ffa3
commit
e0b900dd69
|
@ -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",
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue