Skip to content

Commit 3c03c3a

Browse files
committed
feat(core): optimize string reuse in high traffic
1 parent be9bef7 commit 3c03c3a

18 files changed

+215
-123
lines changed

client/lib/Handler.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ export default class Handler {
150150
const dispatches = [...this.dispatches];
151151
// clear out dispatches everytime you check it
152152
this.dispatches.length = 0;
153-
const startStack = new Error('').stack.split(/\r?\n/).slice(1).join('\n');
153+
const startStack = new Error('').stack.slice(8); // "Error: \n" is 8 chars
154154
await Promise.all(
155155
dispatches.map(async dispatch => {
156156
const err = await dispatch.resolution;
@@ -200,7 +200,9 @@ export default class Handler {
200200
}
201201

202202
private getConnection(): ConnectionToCore {
203-
return pickRandom(this.getAvailableConnections());
203+
const connections = this.getAvailableConnections();
204+
if (!connections.length) throw new Error('There are no active Core connections available.');
205+
return pickRandom(connections);
204206
}
205207

206208
private registerUnhandledExceptionHandlers(): void {

commons/Queue.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export default class Queue {
2525
this.queue.push({
2626
promise,
2727
cb,
28-
startStack: new Error('').stack.split(/\r?\n/).slice(1).join('\n'),
28+
startStack: new Error('').stack.slice(8), // "Error: \n" is 8 chars
2929
});
3030

3131
this.next().catch(() => null);

commons/Resolvable.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ export default class Resolvable<T = any> implements IResolvablePromise<T>, Promi
1414

1515
constructor(timeoutMillis?: number, timeoutMessage?: string) {
1616
// get parent stack
17-
const error = new TimeoutError(timeoutMessage);
18-
this.stack = error.stack.split(/\r?\n/).slice(2).join('\n');
17+
this.stack = new Error('').stack.slice(8);
1918

2019
if (timeoutMillis !== undefined && timeoutMillis !== null) {
21-
this.timeout = setTimeout(this.reject.bind(this, error), timeoutMillis).unref();
20+
this.timeout = setTimeout(
21+
this.rejectWithTimeout.bind(this, timeoutMessage),
22+
timeoutMillis,
23+
).unref();
2224
}
2325
this.promise = new Promise<T>((resolve, reject) => {
2426
this.resolveFn = resolve;
@@ -70,4 +72,10 @@ export default class Resolvable<T = any> implements IResolvablePromise<T>, Promi
7072
public finally(onfinally?: () => void): Promise<T> {
7173
return this.promise.finally(onfinally);
7274
}
75+
76+
private rejectWithTimeout(message: string): void {
77+
const error = new TimeoutError(message);
78+
error.stack = `TimeoutError: ${message}\n${this.stack}`;
79+
this.reject(error);
80+
}
7381
}

commons/TypeSerializer.ts

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
const Types = {
2+
Number: 'number',
3+
String: 'string',
4+
Boolean: 'boolean',
5+
Object: 'object',
6+
BigInt: 'bigint',
7+
NaN: 'NaN',
8+
Infinity: 'Infinity',
9+
NegativeInfinity: '-Infinity',
10+
DateIso: 'DateIso',
11+
Buffer64: 'Buffer64',
12+
ArrayBuffer64: 'ArrayBuffer64',
13+
RegExp: 'RegExp',
14+
Map: 'Map',
15+
Set: 'Set',
16+
Error: 'Error',
17+
};
18+
119
export default class TypeSerializer {
220
public static errorTypes = new Map<string, { new (message?: string): Error }>();
321
private static isNodejs = typeof process !== 'undefined' && process.release.name === 'node';
@@ -8,12 +26,13 @@ export default class TypeSerializer {
826

927
const { value, type } = entry;
1028

11-
if (type === 'BigInt') return BigInt(value);
12-
if (type === 'NaN') return Number.NaN;
13-
if (type === 'Infinity') return Number.POSITIVE_INFINITY;
14-
if (type === '-Infinity') return Number.NEGATIVE_INFINITY;
15-
if (type === 'DateIso') return new Date(value);
16-
if (type === 'Buffer64' || type === 'ArrayBuffer64') {
29+
if (type === Types.Number || type === Types.String || type === Types.Boolean) return value;
30+
if (type === Types.BigInt) return BigInt(value);
31+
if (type === Types.NaN) return Number.NaN;
32+
if (type === Types.Infinity) return Number.POSITIVE_INFINITY;
33+
if (type === Types.NegativeInfinity) return Number.NEGATIVE_INFINITY;
34+
if (type === Types.DateIso) return new Date(value);
35+
if (type === Types.Buffer64 || type === Types.ArrayBuffer64) {
1736
if (this.isNodejs) {
1837
return Buffer.from(value, 'base64');
1938
}
@@ -27,10 +46,10 @@ export default class TypeSerializer {
2746

2847
return new globalThis[arrayType](uint8Array.buffer, byteOffset, byteLength);
2948
}
30-
if (type === 'RegExp') return new RegExp(value[0], value[1]);
31-
if (type === 'Map') return new Map(value);
32-
if (type === 'Set') return new Set(value);
33-
if (type === 'Error') {
49+
if (type === Types.RegExp) return new RegExp(value[0], value[1]);
50+
if (type === Types.Map) return new Map(value);
51+
if (type === Types.Set) return new Set(value);
52+
if (type === Types.Error) {
3453
const { name, message, stack, ...data } = value;
3554
let Constructor = this.errorTypes && this.errorTypes.get(name);
3655
if (!Constructor) {
@@ -41,7 +60,8 @@ export default class TypeSerializer {
4160
}
4261
}
4362

44-
const startStack = new Error('').stack.split(/\r?\n/).slice(1).join('\n');
63+
const startStack = new Error('').stack.slice(8); // "Error: \n" is 8 chars
64+
4565
const e = new Constructor(message);
4666
e.name = name;
4767
Object.assign(e, data);
@@ -57,7 +77,7 @@ export default class TypeSerializer {
5777

5878
public static stringify(object: any): string {
5979
return JSON.stringify(object, (key, value) => {
60-
if (value && typeof value === 'object' && !Array.isArray(value)) {
80+
if (value && typeof value === Types.Object && !Array.isArray(value)) {
6181
const resultObject = {};
6282
for (const [k, v] of Object.entries(value)) {
6383
resultObject[k] = this.convertKeyValue(k, v);
@@ -69,57 +89,57 @@ export default class TypeSerializer {
6989
}
7090

7191
private static convertKeyValue(_: string, value: any): any {
92+
if (value === null || value === undefined) return value;
93+
7294
if (Number.isNaN(value)) {
73-
return { type: 'NaN' };
95+
return { type: Types.NaN };
7496
}
7597

7698
if (value === Number.POSITIVE_INFINITY) {
77-
return { type: 'Infinity' };
99+
return { type: Types.Infinity };
78100
}
79101

80102
if (value === Number.NEGATIVE_INFINITY) {
81-
return { type: '-Infinity' };
103+
return { type: Types.NegativeInfinity };
82104
}
83105

84-
if (value === null || value === undefined) return value;
85-
86106
const type = typeof value;
87-
if (type === 'boolean' || type === 'string' || type === 'number') return value;
88-
if (type === 'bigint') {
89-
return { type: 'BigInt', value: value.toString() };
107+
if (type === Types.Boolean || type === Types.String || type === Types.Number) return value;
108+
if (type === Types.BigInt) {
109+
return { type: Types.BigInt, value: value.toString() };
90110
}
91111

92112
if (value instanceof Date) {
93-
return { type: 'DateIso', value: value.toISOString() };
113+
return { type: Types.DateIso, value: value.toISOString() };
94114
}
95115

96116
if (value instanceof RegExp) {
97-
return { type: 'RegExp', value: [value.source, value.flags] };
117+
return { type: Types.RegExp, value: [value.source, value.flags] };
98118
}
99119

100120
if (value instanceof Error) {
101121
const { name, message, stack, ...data } = value;
102-
return { type: 'Error', value: { name, message, stack, ...data } };
122+
return { type: Types.Error, value: { name, message, stack, ...data } };
103123
}
104124

105125
if (value instanceof Map) {
106-
return { type: 'Map', value: [...value.entries()] };
126+
return { type: Types.Map, value: [...value.entries()] };
107127
}
108128

109129
if (value instanceof Set) {
110-
return { type: 'Set', value: [...value] };
130+
return { type: Types.Set, value: [...value] };
111131
}
112132

113133
if (this.isNodejs) {
114134
if (value instanceof Buffer || Buffer.isBuffer(value)) {
115-
return { type: 'Buffer64', value: value.toString('base64') };
135+
return { type: Types.Buffer64, value: value.toString('base64') };
116136
}
117137
} else {
118138
if (ArrayBuffer.isView(value)) {
119139
// @ts-ignore
120140
const binary = new TextDecoder('utf8').decode(value.buffer);
121141
return {
122-
type: 'ArrayBuffer64',
142+
type: Types.ArrayBuffer64,
123143
value: globalThis.btoa(binary),
124144
args: {
125145
arrayType: value[Symbol.toStringTag],
@@ -132,7 +152,7 @@ export default class TypeSerializer {
132152
// @ts-ignore
133153
const binary = new TextDecoder('utf8').decode(value);
134154
return {
135-
type: 'ArrayBuffer64',
155+
type: Types.ArrayBuffer64,
136156
value: globalThis.btoa(binary),
137157
};
138158
}
@@ -148,4 +168,6 @@ export function registerSerializableErrorType(errorConstructor: {
148168
TypeSerializer.errorTypes.set(errorConstructor.name, errorConstructor);
149169
}
150170

151-
export const stringifiedTypeSerializerClass = TypeSerializer.toString();
171+
export const stringifiedTypeSerializerClass = `const Types = ${JSON.stringify(
172+
Types,
173+
)};\n${TypeSerializer.toString()}`;

core-interfaces/IDomChangeEvent.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@ import type { UnixTimestamp } from './GenericTypes';
33

44
type Idx = number;
55

6-
export type IDomChangeEvent = [
7-
'newDocument' | 'location' | 'added' | 'removed' | 'text' | 'attribute' | 'property',
8-
INodeData,
9-
UnixTimestamp,
10-
Idx,
11-
];
6+
export type IDomChangeEvent = [DomActionType, INodeData, UnixTimestamp, Idx];
7+
8+
export enum DomActionType {
9+
newDocument = 0,
10+
location = 1,
11+
added = 2,
12+
removed = 3,
13+
text = 4,
14+
attribute = 5,
15+
property = 6,
16+
}
1217

1318
export interface INodeData {
1419
id: number;

core-interfaces/IFocusEvent.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import { UnixTimestamp } from './GenericTypes';
33

44
type NodeId = number;
55
type RelatedNodeId = NodeId;
6-
type FocusEventType = 'in' | 'out';
6+
7+
export enum FocusEventType {
8+
IN = 0,
9+
OUT = 1,
10+
}
711

812
export type IFocusEvent = [FocusEventType, NodeId, RelatedNodeId, UnixTimestamp];

core-interfaces/INavigation.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,18 @@ export default interface INavigation {
1515
stateChanges: Map<NavigationState, Date>;
1616
}
1717

18-
export type NavigationState = Exclude<IPipelineStatus, 'PaintingStable'> | 'Load' | 'ContentPaint';
18+
export enum LoadStatus {
19+
NavigationRequested = 'NavigationRequested',
20+
HttpRequested = 'HttpRequested',
21+
HttpRedirected = 'HttpRedirected',
22+
HttpResponded = 'HttpResponded',
23+
24+
DomContentLoaded = 'DomContentLoaded',
25+
Load = 'Load',
26+
ContentPaint = 'ContentPaint',
27+
}
28+
29+
export type NavigationState = keyof typeof LoadStatus;
1930

2031
export type NavigationReason =
2132
| DevToolsNavigationReason

0 commit comments

Comments
 (0)