@push.rocks/smartproxy 19.5.3 → 19.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/core/utils/async-utils.d.ts +81 -0
- package/dist_ts/core/utils/async-utils.js +216 -0
- package/dist_ts/core/utils/binary-heap.d.ts +73 -0
- package/dist_ts/core/utils/binary-heap.js +193 -0
- package/dist_ts/core/utils/enhanced-connection-pool.d.ts +110 -0
- package/dist_ts/core/utils/enhanced-connection-pool.js +320 -0
- package/dist_ts/core/utils/fs-utils.d.ts +144 -0
- package/dist_ts/core/utils/fs-utils.js +252 -0
- package/dist_ts/core/utils/index.d.ts +5 -2
- package/dist_ts/core/utils/index.js +6 -3
- package/dist_ts/core/utils/lifecycle-component.d.ts +59 -0
- package/dist_ts/core/utils/lifecycle-component.js +195 -0
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/dist_ts/proxies/http-proxy/certificate-manager.d.ts +15 -0
- package/dist_ts/proxies/http-proxy/certificate-manager.js +49 -2
- package/dist_ts/proxies/nftables-proxy/nftables-proxy.d.ts +10 -0
- package/dist_ts/proxies/nftables-proxy/nftables-proxy.js +53 -43
- package/dist_ts/proxies/smart-proxy/cert-store.js +22 -20
- package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +37 -7
- package/dist_ts/proxies/smart-proxy/connection-manager.js +257 -180
- package/package.json +2 -2
- package/readme.hints.md +96 -1
- package/readme.md +515 -301
- package/readme.plan.md +1135 -221
- package/readme.problems.md +167 -83
- package/ts/core/utils/async-utils.ts +275 -0
- package/ts/core/utils/binary-heap.ts +225 -0
- package/ts/core/utils/enhanced-connection-pool.ts +420 -0
- package/ts/core/utils/fs-utils.ts +270 -0
- package/ts/core/utils/index.ts +5 -2
- package/ts/core/utils/lifecycle-component.ts +231 -0
- package/ts/plugins.ts +2 -1
- package/ts/proxies/http-proxy/certificate-manager.ts +52 -1
- package/ts/proxies/nftables-proxy/nftables-proxy.ts +64 -79
- package/ts/proxies/smart-proxy/cert-store.ts +26 -20
- package/ts/proxies/smart-proxy/connection-manager.ts +291 -189
- package/readme.plan2.md +0 -764
- package/ts/common/eventUtils.ts +0 -34
- package/ts/common/types.ts +0 -91
- package/ts/core/utils/event-system.ts +0 -376
- package/ts/core/utils/event-utils.ts +0 -25
package/readme.plan.md
CHANGED
|
@@ -1,316 +1,1230 @@
|
|
|
1
|
-
# SmartProxy
|
|
1
|
+
# SmartProxy Performance Optimization Plan
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Executive Summary
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
Add support for custom socket handler functions with the simplest possible API - just pass a function that receives the socket.
|
|
5
|
+
This plan addresses critical performance issues in SmartProxy that impact scalability, responsiveness, and stability. The approach is phased, starting with critical event loop blockers and progressing to long-term architectural improvements.
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
## Phase 1: Critical Issues (Week 1) ✅ COMPLETE
|
|
8
|
+
|
|
9
|
+
### 1.1 Eliminate Busy Wait Loop ✅
|
|
10
|
+
|
|
11
|
+
**Issue**: `ts/proxies/nftables-proxy/nftables-proxy.ts:235-238` blocks the entire event loop
|
|
12
|
+
|
|
13
|
+
**Solution**:
|
|
9
14
|
```typescript
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
// Create utility function in ts/core/utils/async-utils.ts
|
|
16
|
+
export async function delay(ms: number): Promise<void> {
|
|
17
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Replace busy wait in nftables-proxy.ts
|
|
21
|
+
// OLD:
|
|
22
|
+
const waitUntil = Date.now() + retryDelayMs;
|
|
23
|
+
while (Date.now() < waitUntil) { }
|
|
24
|
+
|
|
25
|
+
// NEW:
|
|
26
|
+
await delay(retryDelayMs);
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Implementation**:
|
|
30
|
+
1. Create `async-utils.ts` with common async utilities
|
|
31
|
+
2. Replace all synchronous sleeps with async delay
|
|
32
|
+
3. Ensure all calling functions are async
|
|
33
|
+
|
|
34
|
+
### 1.2 Async Filesystem Operations ✅
|
|
35
|
+
|
|
36
|
+
**Issue**: Multiple synchronous filesystem operations blocking the event loop
|
|
37
|
+
|
|
38
|
+
**Solution Architecture**:
|
|
39
|
+
```typescript
|
|
40
|
+
// Create ts/core/utils/fs-utils.ts
|
|
41
|
+
import * as plugins from '../../plugins.js';
|
|
42
|
+
|
|
43
|
+
export class AsyncFileSystem {
|
|
44
|
+
static async exists(path: string): Promise<boolean> {
|
|
45
|
+
try {
|
|
46
|
+
await plugins.fs.promises.access(path);
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
23
50
|
}
|
|
24
|
-
}
|
|
25
|
-
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
static async ensureDir(path: string): Promise<void> {
|
|
54
|
+
await plugins.fs.promises.mkdir(path, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static async readFile(path: string): Promise<string> {
|
|
58
|
+
return plugins.fs.promises.readFile(path, 'utf8');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static async writeFile(path: string, data: string): Promise<void> {
|
|
62
|
+
// Ensure directory exists
|
|
63
|
+
const dir = plugins.path.dirname(path);
|
|
64
|
+
await this.ensureDir(dir);
|
|
65
|
+
await plugins.fs.promises.writeFile(path, data);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static async remove(path: string): Promise<void> {
|
|
69
|
+
try {
|
|
70
|
+
await plugins.fs.promises.unlink(path);
|
|
71
|
+
} catch (error: any) {
|
|
72
|
+
if (error.code !== 'ENOENT') throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static async readJSON<T>(path: string): Promise<T> {
|
|
77
|
+
const content = await this.readFile(path);
|
|
78
|
+
return JSON.parse(content);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static async writeJSON(path: string, data: any): Promise<void> {
|
|
82
|
+
await this.writeFile(path, JSON.stringify(data, null, 2));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
26
85
|
```
|
|
27
86
|
|
|
28
|
-
|
|
87
|
+
**Migration Strategy**:
|
|
88
|
+
|
|
89
|
+
1. **Certificate Manager** (`ts/proxies/http-proxy/certificate-manager.ts`)
|
|
90
|
+
```typescript
|
|
91
|
+
// OLD:
|
|
92
|
+
constructor(private options: IHttpProxyOptions) {
|
|
93
|
+
if (!fs.existsSync(this.certDir)) {
|
|
94
|
+
fs.mkdirSync(this.certDir, { recursive: true });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// NEW:
|
|
99
|
+
private initialized = false;
|
|
100
|
+
|
|
101
|
+
constructor(private options: IHttpProxyOptions) {}
|
|
102
|
+
|
|
103
|
+
async initialize(): Promise<void> {
|
|
104
|
+
if (this.initialized) return;
|
|
105
|
+
await AsyncFileSystem.ensureDir(this.certDir);
|
|
106
|
+
this.initialized = true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async getCertificate(domain: string): Promise<{ cert: string; key: string } | null> {
|
|
110
|
+
await this.initialize();
|
|
111
|
+
const certPath = path.join(this.certDir, `${domain}.crt`);
|
|
112
|
+
const keyPath = path.join(this.certDir, `${domain}.key`);
|
|
113
|
+
|
|
114
|
+
if (await AsyncFileSystem.exists(certPath) && await AsyncFileSystem.exists(keyPath)) {
|
|
115
|
+
const [cert, key] = await Promise.all([
|
|
116
|
+
AsyncFileSystem.readFile(certPath),
|
|
117
|
+
AsyncFileSystem.readFile(keyPath)
|
|
118
|
+
]);
|
|
119
|
+
return { cert, key };
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
```
|
|
29
124
|
|
|
30
|
-
|
|
125
|
+
2. **Certificate Store** (`ts/proxies/smart-proxy/cert-store.ts`)
|
|
126
|
+
```typescript
|
|
127
|
+
// Convert all methods to async
|
|
128
|
+
export class CertStore {
|
|
129
|
+
constructor(private storePath: string) {}
|
|
130
|
+
|
|
131
|
+
async init(): Promise<void> {
|
|
132
|
+
await AsyncFileSystem.ensureDir(this.storePath);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async hasCertificate(domain: string): Promise<boolean> {
|
|
136
|
+
const certPath = this.getCertificatePath(domain);
|
|
137
|
+
return AsyncFileSystem.exists(certPath);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async getCertificate(domain: string): Promise<ICertificateInfo | null> {
|
|
141
|
+
if (!await this.hasCertificate(domain)) return null;
|
|
142
|
+
|
|
143
|
+
const metaPath = path.join(this.getCertificatePath(domain), 'meta.json');
|
|
144
|
+
return AsyncFileSystem.readJSON<ICertificateInfo>(metaPath);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
31
148
|
|
|
32
|
-
|
|
149
|
+
3. **NFTables Proxy** (`ts/proxies/nftables-proxy/nftables-proxy.ts`)
|
|
150
|
+
```typescript
|
|
151
|
+
// Replace execSync with execAsync
|
|
152
|
+
private async execNftCommand(command: string): Promise<string> {
|
|
153
|
+
const maxRetries = 3;
|
|
154
|
+
let lastError: Error | null = null;
|
|
155
|
+
|
|
156
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
157
|
+
try {
|
|
158
|
+
const { stdout } = await this.execAsync(command);
|
|
159
|
+
return stdout;
|
|
160
|
+
} catch (err: any) {
|
|
161
|
+
lastError = err;
|
|
162
|
+
if (i < maxRetries - 1) {
|
|
163
|
+
await delay(this.retryDelayMs);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
throw new NftExecutionError(`Failed after ${maxRetries} attempts: ${lastError?.message}`);
|
|
169
|
+
}
|
|
170
|
+
```
|
|
33
171
|
|
|
34
|
-
|
|
35
|
-
**File:** `ts/proxies/smart-proxy/models/route-types.ts`
|
|
172
|
+
## Dependencies Between Phases
|
|
36
173
|
|
|
174
|
+
### Critical Path
|
|
175
|
+
```
|
|
176
|
+
Phase 1.1 (Busy Wait) ─┐
|
|
177
|
+
├─> Phase 2.1 (Timer Management) ─> Phase 3.2 (Worker Threads)
|
|
178
|
+
Phase 1.2 (Async FS) ──┘ │
|
|
179
|
+
├─> Phase 4.1 (Monitoring)
|
|
180
|
+
Phase 2.2 (Connection Pool) ────────────────────────────┘
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Phase Dependencies
|
|
184
|
+
- **Phase 1** must complete before Phase 2 (foundation for async operations)
|
|
185
|
+
- **Phase 2.1** enables proper cleanup for Phase 3.2 worker threads
|
|
186
|
+
- **Phase 3** optimizations depend on stable async foundation
|
|
187
|
+
- **Phase 4** monitoring requires all components to be instrumented
|
|
188
|
+
|
|
189
|
+
## Phase 2: Resource Management (Week 2) 🔨 IN PROGRESS
|
|
190
|
+
|
|
191
|
+
### 2.1 Timer Lifecycle Management
|
|
192
|
+
|
|
193
|
+
**Issue**: Timers created without cleanup references causing memory leaks
|
|
194
|
+
|
|
195
|
+
**Solution Pattern**:
|
|
37
196
|
```typescript
|
|
38
|
-
//
|
|
39
|
-
export
|
|
197
|
+
// Create base class in ts/core/utils/lifecycle-component.ts
|
|
198
|
+
export abstract class LifecycleComponent {
|
|
199
|
+
private timers: Set<NodeJS.Timeout> = new Set();
|
|
200
|
+
private listeners: Array<{ target: any, event: string, handler: Function }> = [];
|
|
201
|
+
protected isShuttingDown = false;
|
|
40
202
|
|
|
41
|
-
|
|
42
|
-
|
|
203
|
+
protected setInterval(handler: Function, timeout: number): NodeJS.Timeout {
|
|
204
|
+
const timer = setInterval(() => {
|
|
205
|
+
if (!this.isShuttingDown) {
|
|
206
|
+
handler();
|
|
207
|
+
}
|
|
208
|
+
}, timeout);
|
|
209
|
+
this.timers.add(timer);
|
|
210
|
+
return timer;
|
|
211
|
+
}
|
|
43
212
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
213
|
+
protected setTimeout(handler: Function, timeout: number): NodeJS.Timeout {
|
|
214
|
+
const timer = setTimeout(() => {
|
|
215
|
+
this.timers.delete(timer);
|
|
216
|
+
if (!this.isShuttingDown) {
|
|
217
|
+
handler();
|
|
218
|
+
}
|
|
219
|
+
}, timeout);
|
|
220
|
+
this.timers.add(timer);
|
|
221
|
+
return timer;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
protected addEventListener(target: any, event: string, handler: Function): void {
|
|
225
|
+
target.on(event, handler);
|
|
226
|
+
this.listeners.push({ target, event, handler });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
protected async cleanup(): Promise<void> {
|
|
230
|
+
this.isShuttingDown = true;
|
|
231
|
+
|
|
232
|
+
// Clear all timers
|
|
233
|
+
for (const timer of this.timers) {
|
|
234
|
+
clearInterval(timer);
|
|
235
|
+
clearTimeout(timer);
|
|
236
|
+
}
|
|
237
|
+
this.timers.clear();
|
|
238
|
+
|
|
239
|
+
// Remove all listeners
|
|
240
|
+
for (const { target, event, handler } of this.listeners) {
|
|
241
|
+
target.removeListener(event, handler);
|
|
242
|
+
}
|
|
243
|
+
this.listeners = [];
|
|
244
|
+
}
|
|
50
245
|
}
|
|
51
246
|
```
|
|
52
247
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
248
|
+
**Implementation**:
|
|
249
|
+
1. Extend LifecycleComponent in:
|
|
250
|
+
- `HttpProxy`
|
|
251
|
+
- `SmartProxy`
|
|
252
|
+
- `ConnectionManager`
|
|
253
|
+
- `RequestHandler`
|
|
254
|
+
- `SharedSecurityManager`
|
|
255
|
+
|
|
256
|
+
2. Replace direct timer/listener usage with lifecycle methods
|
|
56
257
|
|
|
57
|
-
### 2.
|
|
58
|
-
**File:** `ts/proxies/smart-proxy/route-connection-handler.ts`
|
|
258
|
+
### 2.2 Connection Pool Enhancement
|
|
59
259
|
|
|
60
|
-
|
|
260
|
+
**Issue**: No backpressure mechanism and synchronous operations
|
|
61
261
|
|
|
262
|
+
**Solution**:
|
|
62
263
|
```typescript
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
264
|
+
// First, implement efficient BinaryHeap for O(log n) operations
|
|
265
|
+
// Create ts/core/utils/binary-heap.ts
|
|
266
|
+
export class BinaryHeap<T> {
|
|
267
|
+
private heap: T[] = [];
|
|
66
268
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
269
|
+
constructor(
|
|
270
|
+
private compareFn: (a: T, b: T) => number,
|
|
271
|
+
private extractKey?: (item: T) => string
|
|
272
|
+
) {}
|
|
273
|
+
|
|
274
|
+
insert(item: T): void {
|
|
275
|
+
this.heap.push(item);
|
|
276
|
+
this.bubbleUp(this.heap.length - 1);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
extract(): T | undefined {
|
|
280
|
+
if (this.heap.length === 0) return undefined;
|
|
281
|
+
if (this.heap.length === 1) return this.heap.pop();
|
|
282
|
+
|
|
283
|
+
const result = this.heap[0];
|
|
284
|
+
this.heap[0] = this.heap.pop()!;
|
|
285
|
+
this.bubbleDown(0);
|
|
286
|
+
return result;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
extractIf(predicate: (item: T) => boolean): T | undefined {
|
|
290
|
+
const index = this.heap.findIndex(predicate);
|
|
291
|
+
if (index === -1) return undefined;
|
|
292
|
+
|
|
293
|
+
if (index === this.heap.length - 1) return this.heap.pop();
|
|
294
|
+
|
|
295
|
+
const result = this.heap[index];
|
|
296
|
+
this.heap[index] = this.heap.pop()!;
|
|
297
|
+
|
|
298
|
+
// Restore heap property
|
|
299
|
+
this.bubbleUp(index);
|
|
300
|
+
this.bubbleDown(index);
|
|
301
|
+
return result;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
sizeFor(key: string): number {
|
|
305
|
+
if (!this.extractKey) return this.heap.length;
|
|
306
|
+
return this.heap.filter(item => this.extractKey!(item) === key).length;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private bubbleUp(index: number): void {
|
|
310
|
+
while (index > 0) {
|
|
311
|
+
const parentIndex = Math.floor((index - 1) / 2);
|
|
312
|
+
if (this.compareFn(this.heap[index], this.heap[parentIndex]) >= 0) break;
|
|
313
|
+
|
|
314
|
+
[this.heap[index], this.heap[parentIndex]] =
|
|
315
|
+
[this.heap[parentIndex], this.heap[index]];
|
|
316
|
+
index = parentIndex;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private bubbleDown(index: number): void {
|
|
321
|
+
while (true) {
|
|
322
|
+
let minIndex = index;
|
|
323
|
+
const leftChild = 2 * index + 1;
|
|
324
|
+
const rightChild = 2 * index + 2;
|
|
325
|
+
|
|
326
|
+
if (leftChild < this.heap.length &&
|
|
327
|
+
this.compareFn(this.heap[leftChild], this.heap[minIndex]) < 0) {
|
|
328
|
+
minIndex = leftChild;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (rightChild < this.heap.length &&
|
|
332
|
+
this.compareFn(this.heap[rightChild], this.heap[minIndex]) < 0) {
|
|
333
|
+
minIndex = rightChild;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (minIndex === index) break;
|
|
337
|
+
|
|
338
|
+
[this.heap[index], this.heap[minIndex]] =
|
|
339
|
+
[this.heap[minIndex], this.heap[index]];
|
|
340
|
+
index = minIndex;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Enhanced connection pool with queue and heap
|
|
346
|
+
export class EnhancedConnectionPool extends LifecycleComponent {
|
|
347
|
+
private connectionQueue: Array<{
|
|
348
|
+
resolve: (socket: net.Socket) => void;
|
|
349
|
+
reject: (error: Error) => void;
|
|
350
|
+
host: string;
|
|
351
|
+
port: number;
|
|
352
|
+
timestamp: number;
|
|
353
|
+
}> = [];
|
|
354
|
+
|
|
355
|
+
private connectionHeap: BinaryHeap<IConnectionEntry>;
|
|
356
|
+
private metricsCollector: ConnectionMetrics;
|
|
357
|
+
|
|
358
|
+
constructor(options: IConnectionPoolOptions) {
|
|
359
|
+
super();
|
|
360
|
+
|
|
361
|
+
// Priority: least recently used connections first
|
|
362
|
+
this.connectionHeap = new BinaryHeap(
|
|
363
|
+
(a, b) => a.lastUsed - b.lastUsed,
|
|
364
|
+
(item) => item.poolKey
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
this.metricsCollector = new ConnectionMetrics();
|
|
368
|
+
this.startQueueProcessor();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
private startQueueProcessor(): void {
|
|
372
|
+
// Process queue periodically to handle timeouts and retries
|
|
373
|
+
this.setInterval(() => {
|
|
374
|
+
const now = Date.now();
|
|
375
|
+
const timeout = this.options.connectionQueueTimeout || 30000;
|
|
376
|
+
|
|
377
|
+
// Remove timed out requests
|
|
378
|
+
this.connectionQueue = this.connectionQueue.filter(item => {
|
|
379
|
+
if (now - item.timestamp > timeout) {
|
|
380
|
+
item.reject(new Error(`Connection pool timeout for ${item.host}:${item.port}`));
|
|
381
|
+
this.metricsCollector.recordTimeout();
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
return true;
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Try to fulfill queued requests
|
|
388
|
+
this.processQueue();
|
|
389
|
+
}, 1000);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private processQueue(): void {
|
|
393
|
+
if (this.connectionQueue.length === 0) return;
|
|
394
|
+
|
|
395
|
+
// Group by destination
|
|
396
|
+
const grouped = new Map<string, typeof this.connectionQueue>();
|
|
397
|
+
|
|
398
|
+
for (const item of this.connectionQueue) {
|
|
399
|
+
const key = `${item.host}:${item.port}`;
|
|
400
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
401
|
+
grouped.get(key)!.push(item);
|
|
72
402
|
}
|
|
73
403
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const
|
|
404
|
+
// Try to fulfill requests for each destination
|
|
405
|
+
for (const [poolKey, requests] of grouped) {
|
|
406
|
+
const available = this.connectionHeap.extractIf(
|
|
407
|
+
conn => conn.poolKey === poolKey && conn.isIdle && !conn.socket.destroyed
|
|
408
|
+
);
|
|
77
409
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
410
|
+
if (available) {
|
|
411
|
+
const request = requests.shift()!;
|
|
412
|
+
this.connectionQueue = this.connectionQueue.filter(item => item !== request);
|
|
413
|
+
|
|
414
|
+
available.isIdle = false;
|
|
415
|
+
available.lastUsed = Date.now();
|
|
416
|
+
request.resolve(available.socket);
|
|
417
|
+
|
|
418
|
+
this.metricsCollector.recordReuse();
|
|
86
419
|
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async getConnection(host: string, port: number): Promise<net.Socket> {
|
|
424
|
+
const poolKey = `${host}:${port}`;
|
|
425
|
+
|
|
426
|
+
// Try to get existing connection
|
|
427
|
+
let connection = this.connectionHeap.extractIf(
|
|
428
|
+
conn => conn.poolKey === poolKey && conn.isIdle && !conn.socket.destroyed
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
if (connection) {
|
|
432
|
+
connection.isIdle = false;
|
|
433
|
+
connection.lastUsed = Date.now();
|
|
434
|
+
this.metricsCollector.recordReuse();
|
|
435
|
+
|
|
436
|
+
// Validate connection is still alive
|
|
437
|
+
if (await this.validateConnection(connection.socket)) {
|
|
438
|
+
return connection.socket;
|
|
91
439
|
}
|
|
440
|
+
|
|
441
|
+
// Connection is dead, try another
|
|
442
|
+
connection.socket.destroy();
|
|
443
|
+
return this.getConnection(host, port);
|
|
92
444
|
}
|
|
445
|
+
|
|
446
|
+
// Check pool size
|
|
447
|
+
const poolSize = this.connectionHeap.sizeFor(poolKey);
|
|
448
|
+
if (poolSize < this.options.connectionPoolSize) {
|
|
449
|
+
return this.createConnection(host, port);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Queue the request
|
|
453
|
+
return this.queueConnectionRequest(host, port);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
private async validateConnection(socket: net.Socket): Promise<boolean> {
|
|
457
|
+
return new Promise((resolve) => {
|
|
458
|
+
if (socket.destroyed || !socket.readable || !socket.writable) {
|
|
459
|
+
resolve(false);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Try to write a TCP keepalive probe
|
|
464
|
+
const originalWrite = socket.write;
|
|
465
|
+
let writeError = false;
|
|
466
|
+
|
|
467
|
+
socket.write = function(data: any, encoding?: any, cb?: any) {
|
|
468
|
+
writeError = true;
|
|
469
|
+
return false;
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
socket.setNoDelay(true);
|
|
473
|
+
socket.setNoDelay(false);
|
|
474
|
+
|
|
475
|
+
socket.write = originalWrite;
|
|
476
|
+
|
|
477
|
+
resolve(!writeError);
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
returnConnection(socket: net.Socket, host: string, port: number): void {
|
|
482
|
+
const poolKey = `${host}:${port}`;
|
|
93
483
|
|
|
94
|
-
|
|
484
|
+
// Check for queued requests first
|
|
485
|
+
const queuedIndex = this.connectionQueue.findIndex(
|
|
486
|
+
item => item.host === host && item.port === port
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
if (queuedIndex >= 0) {
|
|
490
|
+
const queued = this.connectionQueue.splice(queuedIndex, 1)[0];
|
|
491
|
+
queued.resolve(socket);
|
|
492
|
+
this.metricsCollector.recordDirectHandoff();
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Return to pool
|
|
497
|
+
this.connectionHeap.insert({
|
|
498
|
+
socket,
|
|
499
|
+
poolKey,
|
|
500
|
+
lastUsed: Date.now(),
|
|
501
|
+
isIdle: true,
|
|
502
|
+
created: Date.now()
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
getMetrics(): IConnectionPoolMetrics {
|
|
507
|
+
return {
|
|
508
|
+
...this.metricsCollector.getMetrics(),
|
|
509
|
+
poolSize: this.connectionHeap.size(),
|
|
510
|
+
queueLength: this.connectionQueue.length
|
|
511
|
+
};
|
|
95
512
|
}
|
|
96
|
-
|
|
97
|
-
// ... rest of existing action handling
|
|
98
513
|
}
|
|
99
514
|
```
|
|
100
515
|
|
|
101
|
-
|
|
516
|
+
## Phase 3: Performance Optimizations (Week 3)
|
|
102
517
|
|
|
103
|
-
|
|
518
|
+
### 3.1 JSON Operations Optimization
|
|
104
519
|
|
|
105
|
-
|
|
520
|
+
**Issue**: Frequent JSON.stringify for cache keys
|
|
106
521
|
|
|
522
|
+
**Solution**:
|
|
107
523
|
```typescript
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
524
|
+
// Create ts/core/utils/hash-utils.ts
|
|
525
|
+
import * as crypto from 'crypto';
|
|
526
|
+
|
|
527
|
+
export class HashUtils {
|
|
528
|
+
private static readonly objectCache = new WeakMap<object, string>();
|
|
529
|
+
|
|
530
|
+
static hashObject(obj: any): string {
|
|
531
|
+
// Check cache first
|
|
532
|
+
if (typeof obj === 'object' && obj !== null) {
|
|
533
|
+
const cached = this.objectCache.get(obj);
|
|
534
|
+
if (cached) return cached;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Create stable string representation
|
|
538
|
+
const str = this.stableStringify(obj);
|
|
539
|
+
const hash = crypto.createHash('sha256').update(str).digest('hex').slice(0, 16);
|
|
540
|
+
|
|
541
|
+
// Cache if object
|
|
542
|
+
if (typeof obj === 'object' && obj !== null) {
|
|
543
|
+
this.objectCache.set(obj, hash);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return hash;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
private static stableStringify(obj: any): string {
|
|
550
|
+
if (obj === null || typeof obj !== 'object') {
|
|
551
|
+
return JSON.stringify(obj);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (Array.isArray(obj)) {
|
|
555
|
+
return '[' + obj.map(item => this.stableStringify(item)).join(',') + ']';
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const keys = Object.keys(obj).sort();
|
|
559
|
+
const pairs = keys.map(key => `"${key}":${this.stableStringify(obj[key])}`);
|
|
560
|
+
return '{' + pairs.join(',') + '}';
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Update function-cache.ts
|
|
565
|
+
private computeContextHash(context: IRouteContext): string {
|
|
566
|
+
return HashUtils.hashObject({
|
|
567
|
+
domain: context.domain,
|
|
568
|
+
path: context.path,
|
|
569
|
+
clientIp: context.clientIp
|
|
570
|
+
});
|
|
571
|
+
}
|
|
116
572
|
```
|
|
117
573
|
|
|
118
|
-
|
|
574
|
+
### 3.2 Worker Thread Integration
|
|
575
|
+
|
|
576
|
+
**Issue**: CPU-intensive operations blocking event loop
|
|
577
|
+
|
|
578
|
+
**Solution Architecture**:
|
|
119
579
|
```typescript
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
580
|
+
// Create ts/core/workers/worker-pool.ts
|
|
581
|
+
import { Worker } from 'worker_threads';
|
|
582
|
+
|
|
583
|
+
export class WorkerPool {
|
|
584
|
+
private workers: Worker[] = [];
|
|
585
|
+
private queue: Array<{
|
|
586
|
+
task: any;
|
|
587
|
+
resolve: Function;
|
|
588
|
+
reject: Function;
|
|
589
|
+
}> = [];
|
|
590
|
+
private busyWorkers = new Set<Worker>();
|
|
591
|
+
|
|
592
|
+
constructor(
|
|
593
|
+
private workerScript: string,
|
|
594
|
+
private poolSize: number = 4
|
|
595
|
+
) {
|
|
596
|
+
this.initializeWorkers();
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
async execute<T>(task: any): Promise<T> {
|
|
600
|
+
const worker = await this.getAvailableWorker();
|
|
601
|
+
|
|
602
|
+
return new Promise((resolve, reject) => {
|
|
603
|
+
const messageHandler = (result: any) => {
|
|
604
|
+
worker.off('message', messageHandler);
|
|
605
|
+
worker.off('error', errorHandler);
|
|
606
|
+
this.releaseWorker(worker);
|
|
607
|
+
resolve(result);
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
const errorHandler = (error: Error) => {
|
|
611
|
+
worker.off('message', messageHandler);
|
|
612
|
+
worker.off('error', errorHandler);
|
|
613
|
+
this.releaseWorker(worker);
|
|
614
|
+
reject(error);
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
worker.on('message', messageHandler);
|
|
618
|
+
worker.on('error', errorHandler);
|
|
619
|
+
worker.postMessage(task);
|
|
620
|
+
});
|
|
621
|
+
}
|
|
123
622
|
}
|
|
124
|
-
```
|
|
125
623
|
|
|
126
|
-
|
|
624
|
+
// Create ts/core/workers/nftables-worker.ts
|
|
625
|
+
import { parentPort } from 'worker_threads';
|
|
626
|
+
import { exec } from 'child_process';
|
|
627
|
+
import { promisify } from 'util';
|
|
127
628
|
|
|
128
|
-
|
|
629
|
+
const execAsync = promisify(exec);
|
|
129
630
|
|
|
130
|
-
|
|
131
|
-
|
|
631
|
+
parentPort?.on('message', async (task) => {
|
|
632
|
+
try {
|
|
633
|
+
const result = await execAsync(task.command);
|
|
634
|
+
parentPort?.postMessage({ success: true, result });
|
|
635
|
+
} catch (error) {
|
|
636
|
+
parentPort?.postMessage({ success: false, error: error.message });
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
## Phase 4: Monitoring & Metrics (Week 4)
|
|
642
|
+
|
|
643
|
+
### 4.1 Event Loop Monitoring
|
|
132
644
|
|
|
133
645
|
```typescript
|
|
134
|
-
//
|
|
135
|
-
export
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return {
|
|
142
|
-
name: options?.name || 'socket-handler-route',
|
|
143
|
-
priority: options?.priority || 50,
|
|
144
|
-
match: { domains, ports },
|
|
145
|
-
action: {
|
|
146
|
-
type: 'socket-handler',
|
|
147
|
-
socketHandler: handler
|
|
148
|
-
}
|
|
646
|
+
// Create ts/core/monitoring/performance-monitor.ts
|
|
647
|
+
export class PerformanceMonitor extends LifecycleComponent {
|
|
648
|
+
private metrics = {
|
|
649
|
+
eventLoopLag: [] as number[],
|
|
650
|
+
activeConnections: 0,
|
|
651
|
+
memoryUsage: {} as NodeJS.MemoryUsage,
|
|
652
|
+
cpuUsage: {} as NodeJS.CpuUsage
|
|
149
653
|
};
|
|
654
|
+
|
|
655
|
+
start() {
|
|
656
|
+
// Monitor event loop lag
|
|
657
|
+
let lastCheck = process.hrtime.bigint();
|
|
658
|
+
|
|
659
|
+
this.setInterval(() => {
|
|
660
|
+
const now = process.hrtime.bigint();
|
|
661
|
+
const expectedInterval = 100n * 1000000n; // 100ms in nanoseconds
|
|
662
|
+
const actualInterval = now - lastCheck;
|
|
663
|
+
const lag = Number(actualInterval - expectedInterval) / 1000000; // Convert to ms
|
|
664
|
+
|
|
665
|
+
this.metrics.eventLoopLag.push(lag);
|
|
666
|
+
if (this.metrics.eventLoopLag.length > 100) {
|
|
667
|
+
this.metrics.eventLoopLag.shift();
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
lastCheck = now;
|
|
671
|
+
}, 100);
|
|
672
|
+
|
|
673
|
+
// Monitor system resources
|
|
674
|
+
this.setInterval(() => {
|
|
675
|
+
this.metrics.memoryUsage = process.memoryUsage();
|
|
676
|
+
this.metrics.cpuUsage = process.cpuUsage();
|
|
677
|
+
}, 5000);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
getMetrics() {
|
|
681
|
+
const avgLag = this.metrics.eventLoopLag.reduce((a, b) => a + b, 0)
|
|
682
|
+
/ this.metrics.eventLoopLag.length;
|
|
683
|
+
|
|
684
|
+
return {
|
|
685
|
+
eventLoopLag: {
|
|
686
|
+
current: this.metrics.eventLoopLag[this.metrics.eventLoopLag.length - 1],
|
|
687
|
+
average: avgLag,
|
|
688
|
+
max: Math.max(...this.metrics.eventLoopLag)
|
|
689
|
+
},
|
|
690
|
+
memory: this.metrics.memoryUsage,
|
|
691
|
+
cpu: this.metrics.cpuUsage,
|
|
692
|
+
activeConnections: this.metrics.activeConnections
|
|
693
|
+
};
|
|
694
|
+
}
|
|
150
695
|
}
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
## Testing Strategy
|
|
699
|
+
|
|
700
|
+
### Unit Tests
|
|
701
|
+
1. Create tests for each new utility class
|
|
702
|
+
2. Mock filesystem and network operations
|
|
703
|
+
3. Test error scenarios and edge cases
|
|
704
|
+
|
|
705
|
+
### Integration Tests
|
|
706
|
+
1. Test async migration with real filesystem
|
|
707
|
+
2. Verify timer cleanup on shutdown
|
|
708
|
+
3. Test connection pool under load
|
|
151
709
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
710
|
+
### Performance Tests
|
|
711
|
+
```typescript
|
|
712
|
+
// Create test/performance/event-loop-test.ts
|
|
713
|
+
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
714
|
+
|
|
715
|
+
tap.test('should not block event loop', async () => {
|
|
716
|
+
const intervals: number[] = [];
|
|
717
|
+
let lastTime = Date.now();
|
|
158
718
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
719
|
+
const timer = setInterval(() => {
|
|
720
|
+
const now = Date.now();
|
|
721
|
+
intervals.push(now - lastTime);
|
|
722
|
+
lastTime = now;
|
|
723
|
+
}, 10);
|
|
724
|
+
|
|
725
|
+
// Run operations that might block
|
|
726
|
+
await runPotentiallyBlockingOperation();
|
|
727
|
+
|
|
728
|
+
clearInterval(timer);
|
|
167
729
|
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
730
|
+
// Check that no interval exceeded 50ms (allowing some tolerance)
|
|
731
|
+
const maxInterval = Math.max(...intervals);
|
|
732
|
+
expect(maxInterval).toBeLessThan(50);
|
|
733
|
+
});
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
## Migration Timeline
|
|
737
|
+
|
|
738
|
+
### Week 1: Critical Fixes
|
|
739
|
+
- Day 1-2: Fix busy wait loop
|
|
740
|
+
- Day 3-4: Convert critical sync operations
|
|
741
|
+
- Day 5: Testing and validation
|
|
742
|
+
|
|
743
|
+
### Week 2: Resource Management
|
|
744
|
+
- Day 1-2: Implement LifecycleComponent
|
|
745
|
+
- Day 3-4: Migrate components
|
|
746
|
+
- Day 5: Connection pool enhancement
|
|
747
|
+
|
|
748
|
+
### Week 3: Optimizations
|
|
749
|
+
- Day 1-2: JSON operation optimization
|
|
750
|
+
- Day 3-4: Worker thread integration
|
|
751
|
+
- Day 5: Performance testing
|
|
752
|
+
|
|
753
|
+
### Week 4: Monitoring & Polish
|
|
754
|
+
- Day 1-2: Performance monitoring
|
|
755
|
+
- Day 3-4: Load testing
|
|
756
|
+
- Day 5: Documentation and release
|
|
757
|
+
|
|
758
|
+
## Error Handling Strategy
|
|
759
|
+
|
|
760
|
+
### Graceful Degradation
|
|
761
|
+
```typescript
|
|
762
|
+
// Create ts/core/utils/error-handler.ts
|
|
763
|
+
export class ErrorHandler {
|
|
764
|
+
private static errorCounts = new Map<string, number>();
|
|
765
|
+
private static circuitBreakers = new Map<string, CircuitBreaker>();
|
|
766
|
+
|
|
767
|
+
static async withFallback<T>(
|
|
768
|
+
operation: () => Promise<T>,
|
|
769
|
+
fallback: () => Promise<T>,
|
|
770
|
+
context: string
|
|
771
|
+
): Promise<T> {
|
|
772
|
+
const breaker = this.getCircuitBreaker(context);
|
|
773
|
+
|
|
774
|
+
if (breaker.isOpen()) {
|
|
775
|
+
return fallback();
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
try {
|
|
779
|
+
const result = await operation();
|
|
780
|
+
breaker.recordSuccess();
|
|
781
|
+
return result;
|
|
782
|
+
} catch (error) {
|
|
783
|
+
breaker.recordFailure();
|
|
784
|
+
this.recordError(context, error);
|
|
785
|
+
|
|
786
|
+
if (breaker.isOpen()) {
|
|
787
|
+
logger.warn(`Circuit breaker opened for ${context}`);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
return fallback();
|
|
791
|
+
}
|
|
177
792
|
}
|
|
178
|
-
|
|
793
|
+
|
|
794
|
+
private static getCircuitBreaker(context: string): CircuitBreaker {
|
|
795
|
+
if (!this.circuitBreakers.has(context)) {
|
|
796
|
+
this.circuitBreakers.set(context, new CircuitBreaker({
|
|
797
|
+
failureThreshold: 5,
|
|
798
|
+
resetTimeout: 60000
|
|
799
|
+
}));
|
|
800
|
+
}
|
|
801
|
+
return this.circuitBreakers.get(context)!;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Usage example in Certificate Manager
|
|
806
|
+
async getCertificate(domain: string): Promise<CertificateInfo | null> {
|
|
807
|
+
return ErrorHandler.withFallback(
|
|
808
|
+
// Try async operation
|
|
809
|
+
async () => {
|
|
810
|
+
await this.initialize();
|
|
811
|
+
return this.loadCertificateAsync(domain);
|
|
812
|
+
},
|
|
813
|
+
// Fallback to sync if needed
|
|
814
|
+
async () => {
|
|
815
|
+
logger.warn(`Falling back to sync certificate load for ${domain}`);
|
|
816
|
+
return this.loadCertificateSync(domain);
|
|
817
|
+
},
|
|
818
|
+
'certificate-load'
|
|
819
|
+
);
|
|
820
|
+
}
|
|
179
821
|
```
|
|
180
822
|
|
|
181
|
-
|
|
823
|
+
## Backward Compatibility
|
|
182
824
|
|
|
183
|
-
|
|
825
|
+
### API Preservation
|
|
826
|
+
1. **Maintain existing interfaces** - All public APIs remain unchanged
|
|
827
|
+
2. **Progressive enhancement** - New features are opt-in via configuration
|
|
828
|
+
3. **Sync method wrappers** - Provide sync-looking APIs that use async internally
|
|
184
829
|
|
|
185
|
-
### Example 1: Custom Protocol
|
|
186
830
|
```typescript
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
831
|
+
// Example: Maintaining backward compatibility
|
|
832
|
+
export class CertStore {
|
|
833
|
+
// Old sync API (deprecated but maintained)
|
|
834
|
+
getCertificateSync(domain: string): ICertificateInfo | null {
|
|
835
|
+
console.warn('getCertificateSync is deprecated, use getCertificate');
|
|
836
|
+
return this.syncFallbackGetCertificate(domain);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// New async API
|
|
840
|
+
async getCertificate(domain: string): Promise<ICertificateInfo | null> {
|
|
841
|
+
return this.asyncGetCertificate(domain);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Smart detection for gradual migration
|
|
845
|
+
getCertificateAuto(domain: string, callback?: (err: Error | null, cert: ICertificateInfo | null) => void) {
|
|
846
|
+
if (callback) {
|
|
847
|
+
// Callback style for compatibility
|
|
848
|
+
this.getCertificate(domain)
|
|
849
|
+
.then(cert => callback(null, cert))
|
|
850
|
+
.catch(err => callback(err, null));
|
|
851
|
+
} else {
|
|
852
|
+
// Return promise for modern usage
|
|
853
|
+
return this.getCertificate(domain);
|
|
200
854
|
}
|
|
201
855
|
}
|
|
202
856
|
}
|
|
203
857
|
```
|
|
204
858
|
|
|
205
|
-
###
|
|
859
|
+
### Configuration Compatibility
|
|
206
860
|
```typescript
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
861
|
+
// Support both old and new configuration formats
|
|
862
|
+
interface SmartProxyOptions {
|
|
863
|
+
// Old options (maintained)
|
|
864
|
+
preserveSourceIP?: boolean;
|
|
865
|
+
defaultAllowedIPs?: string[];
|
|
866
|
+
|
|
867
|
+
// New performance options (added)
|
|
868
|
+
performance?: {
|
|
869
|
+
asyncFilesystem?: boolean;
|
|
870
|
+
enhancedConnectionPool?: boolean;
|
|
871
|
+
workerThreads?: boolean;
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
## Monitoring Dashboard
|
|
877
|
+
|
|
878
|
+
### Real-time Metrics Visualization
|
|
879
|
+
```typescript
|
|
880
|
+
// Create ts/core/monitoring/dashboard-server.ts
|
|
881
|
+
export class MonitoringDashboard {
|
|
882
|
+
private httpServer: http.Server;
|
|
883
|
+
private wsServer: WebSocket.Server;
|
|
884
|
+
private metricsHistory: MetricsHistory;
|
|
885
|
+
|
|
886
|
+
async start(port: number = 9090): Promise<void> {
|
|
887
|
+
this.httpServer = http.createServer(this.handleRequest.bind(this));
|
|
888
|
+
this.wsServer = new WebSocket.Server({ server: this.httpServer });
|
|
889
|
+
|
|
890
|
+
this.wsServer.on('connection', (ws) => {
|
|
891
|
+
// Send current metrics
|
|
892
|
+
ws.send(JSON.stringify({
|
|
893
|
+
type: 'initial',
|
|
894
|
+
data: this.metricsHistory.getLast(100)
|
|
895
|
+
}));
|
|
896
|
+
|
|
897
|
+
// Subscribe to updates
|
|
898
|
+
const interval = setInterval(() => {
|
|
899
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
900
|
+
ws.send(JSON.stringify({
|
|
901
|
+
type: 'update',
|
|
902
|
+
data: this.performanceMonitor.getMetrics()
|
|
903
|
+
}));
|
|
904
|
+
}
|
|
905
|
+
}, 1000);
|
|
906
|
+
|
|
907
|
+
ws.on('close', () => clearInterval(interval));
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
this.httpServer.listen(port);
|
|
911
|
+
logger.info(`Monitoring dashboard available at http://localhost:${port}`);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
private handleRequest(req: http.IncomingMessage, res: http.ServerResponse) {
|
|
915
|
+
if (req.url === '/') {
|
|
916
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
917
|
+
res.end(this.getDashboardHTML());
|
|
918
|
+
} else if (req.url === '/metrics') {
|
|
919
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
920
|
+
res.end(JSON.stringify(this.performanceMonitor.getMetrics()));
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
private getDashboardHTML(): string {
|
|
925
|
+
return `
|
|
926
|
+
<!DOCTYPE html>
|
|
927
|
+
<html>
|
|
928
|
+
<head>
|
|
929
|
+
<title>SmartProxy Performance Monitor</title>
|
|
930
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
931
|
+
<style>
|
|
932
|
+
body { font-family: Arial, sans-serif; padding: 20px; }
|
|
933
|
+
.metric { display: inline-block; margin: 10px; padding: 15px; background: #f0f0f0; }
|
|
934
|
+
.chart-container { width: 45%; display: inline-block; margin: 2%; }
|
|
935
|
+
</style>
|
|
936
|
+
</head>
|
|
937
|
+
<body>
|
|
938
|
+
<h1>SmartProxy Performance Monitor</h1>
|
|
939
|
+
|
|
940
|
+
<div id="metrics">
|
|
941
|
+
<div class="metric">
|
|
942
|
+
<h3>Event Loop Lag</h3>
|
|
943
|
+
<div id="eventLoopLag">--</div>
|
|
944
|
+
</div>
|
|
945
|
+
<div class="metric">
|
|
946
|
+
<h3>Active Connections</h3>
|
|
947
|
+
<div id="activeConnections">--</div>
|
|
948
|
+
</div>
|
|
949
|
+
<div class="metric">
|
|
950
|
+
<h3>Memory Usage</h3>
|
|
951
|
+
<div id="memoryUsage">--</div>
|
|
952
|
+
</div>
|
|
953
|
+
<div class="metric">
|
|
954
|
+
<h3>Connection Pool</h3>
|
|
955
|
+
<div id="connectionPool">--</div>
|
|
956
|
+
</div>
|
|
957
|
+
</div>
|
|
958
|
+
|
|
959
|
+
<div class="chart-container">
|
|
960
|
+
<canvas id="eventLoopChart"></canvas>
|
|
961
|
+
</div>
|
|
962
|
+
<div class="chart-container">
|
|
963
|
+
<canvas id="connectionChart"></canvas>
|
|
964
|
+
</div>
|
|
965
|
+
|
|
966
|
+
<script>
|
|
967
|
+
const ws = new WebSocket('ws://localhost:9090');
|
|
968
|
+
const eventLoopData = [];
|
|
969
|
+
const connectionData = [];
|
|
970
|
+
|
|
971
|
+
// Initialize charts
|
|
972
|
+
const eventLoopChart = new Chart(document.getElementById('eventLoopChart'), {
|
|
973
|
+
type: 'line',
|
|
974
|
+
data: {
|
|
975
|
+
labels: [],
|
|
976
|
+
datasets: [{
|
|
977
|
+
label: 'Event Loop Lag (ms)',
|
|
978
|
+
data: eventLoopData,
|
|
979
|
+
borderColor: 'rgb(255, 99, 132)',
|
|
980
|
+
tension: 0.1
|
|
981
|
+
}]
|
|
982
|
+
},
|
|
983
|
+
options: {
|
|
984
|
+
responsive: true,
|
|
985
|
+
scales: { y: { beginAtZero: true } }
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
ws.onmessage = (event) => {
|
|
990
|
+
const message = JSON.parse(event.data);
|
|
991
|
+
updateMetrics(message.data);
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
function updateMetrics(metrics) {
|
|
995
|
+
document.getElementById('eventLoopLag').textContent =
|
|
996
|
+
metrics.eventLoopLag.current.toFixed(2) + ' ms';
|
|
997
|
+
document.getElementById('activeConnections').textContent =
|
|
998
|
+
metrics.activeConnections;
|
|
999
|
+
document.getElementById('memoryUsage').textContent =
|
|
1000
|
+
(metrics.memory.heapUsed / 1024 / 1024).toFixed(2) + ' MB';
|
|
1001
|
+
|
|
1002
|
+
// Update charts
|
|
1003
|
+
const now = new Date().toLocaleTimeString();
|
|
1004
|
+
eventLoopData.push(metrics.eventLoopLag.current);
|
|
1005
|
+
eventLoopChart.data.labels.push(now);
|
|
1006
|
+
|
|
1007
|
+
if (eventLoopData.length > 60) {
|
|
1008
|
+
eventLoopData.shift();
|
|
1009
|
+
eventLoopChart.data.labels.shift();
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
eventLoopChart.update();
|
|
1013
|
+
}
|
|
1014
|
+
</script>
|
|
1015
|
+
</body>
|
|
1016
|
+
</html>
|
|
1017
|
+
`;
|
|
213
1018
|
}
|
|
214
1019
|
}
|
|
215
1020
|
```
|
|
216
1021
|
|
|
217
|
-
|
|
1022
|
+
## Performance Benchmarking
|
|
1023
|
+
|
|
1024
|
+
### Benchmark Suite
|
|
218
1025
|
```typescript
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
1026
|
+
// Create test/performance/benchmark.ts
|
|
1027
|
+
import { SmartProxy } from '../../ts/index.js';
|
|
1028
|
+
|
|
1029
|
+
export class PerformanceBenchmark {
|
|
1030
|
+
async runConnectionStresTest(): Promise<BenchmarkResult> {
|
|
1031
|
+
const proxy = new SmartProxy({ /* config */ });
|
|
1032
|
+
await proxy.start();
|
|
1033
|
+
|
|
1034
|
+
const results = {
|
|
1035
|
+
connectionRate: 0,
|
|
1036
|
+
avgLatency: 0,
|
|
1037
|
+
maxConnections: 0,
|
|
1038
|
+
eventLoopLag: []
|
|
1039
|
+
};
|
|
1040
|
+
|
|
1041
|
+
// Monitor event loop during test
|
|
1042
|
+
const lagSamples: number[] = [];
|
|
1043
|
+
let lastCheck = process.hrtime.bigint();
|
|
1044
|
+
const monitor = setInterval(() => {
|
|
1045
|
+
const now = process.hrtime.bigint();
|
|
1046
|
+
const lag = Number(now - lastCheck - 100_000_000n) / 1_000_000;
|
|
1047
|
+
lagSamples.push(lag);
|
|
1048
|
+
lastCheck = now;
|
|
1049
|
+
}, 100);
|
|
1050
|
+
|
|
1051
|
+
// Create connections with increasing rate
|
|
1052
|
+
const startTime = Date.now();
|
|
1053
|
+
let connectionCount = 0;
|
|
1054
|
+
|
|
1055
|
+
for (let rate = 100; rate <= 10000; rate += 100) {
|
|
1056
|
+
const connections = await this.createConnections(rate);
|
|
1057
|
+
connectionCount += connections.length;
|
|
227
1058
|
|
|
228
|
-
//
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
1059
|
+
// Check if performance degrades
|
|
1060
|
+
const avgLag = lagSamples.slice(-10).reduce((a, b) => a + b) / 10;
|
|
1061
|
+
if (avgLag > 50) {
|
|
1062
|
+
results.maxConnections = connectionCount;
|
|
1063
|
+
break;
|
|
233
1064
|
}
|
|
234
1065
|
|
|
235
|
-
|
|
236
|
-
const ws = new WebSocket(socket, headers);
|
|
237
|
-
// ... handle WebSocket
|
|
1066
|
+
await this.delay(1000);
|
|
238
1067
|
}
|
|
1068
|
+
|
|
1069
|
+
clearInterval(monitor);
|
|
1070
|
+
await proxy.stop();
|
|
1071
|
+
|
|
1072
|
+
results.connectionRate = connectionCount / ((Date.now() - startTime) / 1000);
|
|
1073
|
+
results.avgLatency = this.calculateAvgLatency();
|
|
1074
|
+
results.eventLoopLag = lagSamples;
|
|
1075
|
+
|
|
1076
|
+
return results;
|
|
239
1077
|
}
|
|
240
1078
|
}
|
|
241
1079
|
```
|
|
242
1080
|
|
|
243
|
-
|
|
1081
|
+
## Documentation Updates
|
|
1082
|
+
|
|
1083
|
+
### API Documentation
|
|
1084
|
+
1. **Update JSDoc comments** for all modified methods
|
|
1085
|
+
2. **Add migration guide** for async transitions
|
|
1086
|
+
3. **Performance tuning guide** with recommended settings
|
|
1087
|
+
|
|
1088
|
+
### Example Updates
|
|
1089
|
+
```typescript
|
|
1090
|
+
/**
|
|
1091
|
+
* Gets a certificate for the specified domain
|
|
1092
|
+
* @param domain - The domain to get certificate for
|
|
1093
|
+
* @returns Promise resolving to certificate info or null
|
|
1094
|
+
* @since v20.0.0 - Now returns Promise (breaking change)
|
|
1095
|
+
* @example
|
|
1096
|
+
* // Old way (deprecated)
|
|
1097
|
+
* const cert = certStore.getCertificateSync('example.com');
|
|
1098
|
+
*
|
|
1099
|
+
* // New way
|
|
1100
|
+
* const cert = await certStore.getCertificate('example.com');
|
|
1101
|
+
*
|
|
1102
|
+
* // Compatibility mode
|
|
1103
|
+
* certStore.getCertificateAuto('example.com', (err, cert) => {
|
|
1104
|
+
* if (err) console.error(err);
|
|
1105
|
+
* else console.log(cert);
|
|
1106
|
+
* });
|
|
1107
|
+
*/
|
|
1108
|
+
async getCertificate(domain: string): Promise<ICertificateInfo | null> {
|
|
1109
|
+
// Implementation
|
|
1110
|
+
}
|
|
1111
|
+
```
|
|
1112
|
+
|
|
1113
|
+
## Rollback Strategy
|
|
1114
|
+
|
|
1115
|
+
Each phase is designed to be independently deployable with feature flags:
|
|
1116
|
+
|
|
1117
|
+
```typescript
|
|
1118
|
+
export const PerformanceFlags = {
|
|
1119
|
+
useAsyncFilesystem: process.env.SMARTPROXY_ASYNC_FS !== 'false',
|
|
1120
|
+
useEnhancedPool: process.env.SMARTPROXY_ENHANCED_POOL === 'true',
|
|
1121
|
+
useWorkerThreads: process.env.SMARTPROXY_WORKERS === 'true',
|
|
1122
|
+
enableMonitoring: process.env.SMARTPROXY_MONITORING === 'true'
|
|
1123
|
+
};
|
|
1124
|
+
```
|
|
1125
|
+
|
|
1126
|
+
### Gradual Rollout Plan
|
|
1127
|
+
1. **Development**: All flags enabled
|
|
1128
|
+
2. **Staging**: Monitor metrics for 1 week
|
|
1129
|
+
3. **Production**:
|
|
1130
|
+
- 10% traffic → 25% → 50% → 100%
|
|
1131
|
+
- Monitor key metrics at each stage
|
|
1132
|
+
- Rollback if metrics degrade
|
|
244
1133
|
|
|
245
|
-
##
|
|
1134
|
+
## Success Metrics
|
|
246
1135
|
|
|
247
|
-
1. **
|
|
248
|
-
2. **
|
|
249
|
-
3. **
|
|
250
|
-
4. **
|
|
251
|
-
5. **
|
|
252
|
-
6. **Easy to Test**: Just test the socket handler functions directly
|
|
1136
|
+
1. **Event Loop Lag**: < 10ms average, < 50ms max
|
|
1137
|
+
2. **Connection Handling**: Support 10k+ concurrent connections
|
|
1138
|
+
3. **Memory Usage**: Stable under sustained load
|
|
1139
|
+
4. **CPU Usage**: Efficient utilization across cores
|
|
1140
|
+
5. **Response Time**: < 5ms overhead for proxy operations
|
|
253
1141
|
|
|
254
|
-
|
|
1142
|
+
## Risk Mitigation
|
|
255
1143
|
|
|
256
|
-
|
|
1144
|
+
1. **Backward Compatibility**: Maintain existing APIs
|
|
1145
|
+
2. **Gradual Rollout**: Use feature flags
|
|
1146
|
+
3. **Monitoring**: Track metrics before/after changes
|
|
1147
|
+
4. **Testing**: Comprehensive test coverage
|
|
1148
|
+
5. **Documentation**: Update all API docs
|
|
257
1149
|
|
|
258
|
-
|
|
259
|
-
2. Add `socketHandler?: TSocketHandler` to `IRouteAction` (5 minutes)
|
|
260
|
-
3. Add socket-handler case in `RouteConnectionHandler.handleConnection()` (15 minutes)
|
|
261
|
-
4. Add helper functions (optional, 30 minutes)
|
|
262
|
-
5. Write tests (2 hours)
|
|
263
|
-
6. Update documentation (1 hour)
|
|
1150
|
+
## Summary of Key Optimizations
|
|
264
1151
|
|
|
265
|
-
|
|
1152
|
+
### Immediate Impact (Phase 1)
|
|
1153
|
+
1. **Eliminate busy wait loop** - Unblocks event loop immediately
|
|
1154
|
+
2. **Async filesystem operations** - Prevents I/O blocking
|
|
1155
|
+
3. **Proper error handling** - Graceful degradation with fallbacks
|
|
266
1156
|
|
|
267
|
-
|
|
1157
|
+
### Performance Enhancements (Phase 2-3)
|
|
1158
|
+
1. **Enhanced connection pooling** - O(log n) operations with BinaryHeap
|
|
1159
|
+
2. **Resource lifecycle management** - Prevents memory leaks
|
|
1160
|
+
3. **Worker threads** - Offloads CPU-intensive operations
|
|
1161
|
+
4. **Optimized JSON operations** - Reduces parsing overhead
|
|
268
1162
|
|
|
269
|
-
|
|
1163
|
+
### Monitoring & Validation (Phase 4)
|
|
1164
|
+
1. **Real-time dashboard** - Visual performance monitoring
|
|
1165
|
+
2. **Event loop lag tracking** - Early warning system
|
|
1166
|
+
3. **Automated benchmarking** - Regression prevention
|
|
270
1167
|
|
|
271
|
-
|
|
272
|
-
- ❌ Complex context objects with utils
|
|
273
|
-
- ❌ HTTP request handling for socket handlers
|
|
274
|
-
- ❌ Complex protocol detection mechanisms
|
|
275
|
-
- ❌ Middleware patterns
|
|
276
|
-
- ❌ Lifecycle hooks
|
|
1168
|
+
## Implementation Checklist
|
|
277
1169
|
|
|
278
|
-
|
|
1170
|
+
### Phase 1: Critical Fixes (Priority: URGENT)
|
|
1171
|
+
- [ ] Create `ts/core/utils/async-utils.ts` with delay function
|
|
1172
|
+
- [ ] Fix busy wait loop in `nftables-proxy.ts`
|
|
1173
|
+
- [ ] Create `ts/core/utils/fs-utils.ts` with AsyncFileSystem class
|
|
1174
|
+
- [ ] Migrate `certificate-manager.ts` to async operations
|
|
1175
|
+
- [ ] Migrate `cert-store.ts` to async operations
|
|
1176
|
+
- [ ] Replace `execSync` with `execAsync` in `nftables-proxy.ts`
|
|
1177
|
+
- [ ] Add comprehensive unit tests for async operations
|
|
1178
|
+
- [ ] Performance test to verify event loop improvements
|
|
279
1179
|
|
|
280
|
-
|
|
1180
|
+
### Phase 2: Resource Management
|
|
1181
|
+
- [x] Implement LifecycleComponent base class
|
|
1182
|
+
- [ ] Migrate components to extend LifecycleComponent
|
|
1183
|
+
- [x] Implement BinaryHeap data structure
|
|
1184
|
+
- [x] Create EnhancedConnectionPool with queue support
|
|
1185
|
+
- [x] Add connection validation and health checks
|
|
1186
|
+
- [ ] Implement proper timer cleanup in all components
|
|
1187
|
+
- [ ] Add integration tests for resource management
|
|
1188
|
+
- [x] Clean up legacy code (removed ts/common/, event-utils.ts, event-system.ts)
|
|
281
1189
|
|
|
282
|
-
|
|
1190
|
+
### Phase 3: Performance Optimizations
|
|
1191
|
+
- [ ] Implement HashUtils for efficient object hashing
|
|
1192
|
+
- [ ] Create WorkerPool for CPU-intensive operations
|
|
1193
|
+
- [ ] Migrate NFTables operations to worker threads
|
|
1194
|
+
- [ ] Optimize JSON operations with caching
|
|
1195
|
+
- [ ] Add performance benchmarks
|
|
283
1196
|
|
|
284
|
-
|
|
285
|
-
-
|
|
286
|
-
-
|
|
287
|
-
-
|
|
288
|
-
-
|
|
289
|
-
-
|
|
1197
|
+
### Phase 4: Monitoring & Polish
|
|
1198
|
+
- [ ] Implement PerformanceMonitor class
|
|
1199
|
+
- [ ] Create monitoring dashboard with WebSocket updates
|
|
1200
|
+
- [ ] Add comprehensive metrics collection
|
|
1201
|
+
- [ ] Document all API changes
|
|
1202
|
+
- [ ] Create migration guide
|
|
1203
|
+
- [ ] Update examples and tutorials
|
|
290
1204
|
|
|
291
|
-
|
|
1205
|
+
## Next Steps
|
|
292
1206
|
|
|
293
|
-
|
|
1207
|
+
1. **Immediate Action**: Fix the busy wait loop (blocks entire event loop)
|
|
1208
|
+
2. **Code Review**: Review this plan with the team
|
|
1209
|
+
3. **Feature Branch**: Create `feature/performance-optimization`
|
|
1210
|
+
4. **Phase 1 Implementation**: Complete within 1 week
|
|
1211
|
+
5. **Staging Deployment**: Test with real traffic patterns
|
|
1212
|
+
6. **Gradual Rollout**: Use feature flags for production
|
|
1213
|
+
7. **Monitor & Iterate**: Track metrics and adjust as needed
|
|
294
1214
|
|
|
295
|
-
|
|
296
|
-
1. **Type Definitions** - Added 'socket-handler' to TRouteActionType and TSocketHandler type
|
|
297
|
-
2. **Route Handler** - Added socket-handler case in RouteConnectionHandler switch statement
|
|
298
|
-
3. **Error Handling** - Both sync and async errors are caught and logged
|
|
299
|
-
4. **Initial Data Handling** - Initial chunks are re-emitted to handler's listeners
|
|
300
|
-
5. **Helper Functions** - Added createSocketHandlerRoute and pre-built handlers (echo, proxy, etc.)
|
|
301
|
-
6. **Full Test Coverage** - All test cases pass including async handlers and error handling
|
|
1215
|
+
## Expected Outcomes
|
|
302
1216
|
|
|
303
|
-
|
|
304
|
-
-
|
|
305
|
-
-
|
|
306
|
-
-
|
|
307
|
-
-
|
|
308
|
-
-
|
|
1217
|
+
After implementing all phases:
|
|
1218
|
+
- **10x improvement** in concurrent connection handling
|
|
1219
|
+
- **90% reduction** in event loop blocking
|
|
1220
|
+
- **50% reduction** in memory usage under load
|
|
1221
|
+
- **Zero memory leaks** with proper resource cleanup
|
|
1222
|
+
- **Real-time visibility** into performance metrics
|
|
1223
|
+
- **Graceful degradation** under extreme load
|
|
309
1224
|
|
|
310
|
-
|
|
311
|
-
- Clients must send initial data to trigger the handler (even just a newline)
|
|
312
|
-
- The socket is passed directly to the handler function
|
|
313
|
-
- Handler has complete control over the socket lifecycle
|
|
314
|
-
- No special context object needed - keeps it simple
|
|
1225
|
+
## Version Plan
|
|
315
1226
|
|
|
316
|
-
**
|
|
1227
|
+
- **v19.6.0**: Phase 1 (Critical fixes) - Backward compatible
|
|
1228
|
+
- **v19.7.0**: Phase 2 (Resource management) - Backward compatible
|
|
1229
|
+
- **v19.8.0**: Phase 3 (Optimizations) - Backward compatible
|
|
1230
|
+
- **v20.0.0**: Phase 4 (Full async) - Breaking changes with migration path
|