Skip to content

Commit e37cee9

Browse files
feat: integrate HTTP connection pooling for Lighthouse API calls (Patrick-Ehimen#75)
Wire the existing ConnectionPool into LighthouseAISDK so that direct HTTP calls (e.g. the uploadViDirectAPI fallback) reuse pooled connections with keep-alive instead of creating new ones per request. - Add optional `pool` config to LighthouseConfig (default enabled, set false to disable) - Create ConnectionPool in SDK constructor with configurable max connections, timeouts, and keep-alive - Add executeHttpRequest helper that routes through pool or falls back to direct axios - Expose pool metrics via getConnectionPoolStats() - Add env var support (LIGHTHOUSE_POOL_MAX_CONNECTIONS, etc.) in MCP server config - Add 32 new tests covering pool lifecycle, queuing, and SDK integration Closes Patrick-Ehimen#54
1 parent e6484d1 commit e37cee9

6 files changed

Lines changed: 498 additions & 4 deletions

File tree

apps/mcp-server/src/config/server-config.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ import { MultiTenancyConfig, OrganizationSettings, UsageQuota } from "@lighthous
77
import * as path from "path";
88
import * as os from "os";
99

10+
export interface ConnectionPoolServerConfig {
11+
maxConnections: number;
12+
idleTimeoutMs: number;
13+
requestTimeoutMs: number;
14+
keepAlive: boolean;
15+
}
16+
1017
export interface ServerConfig {
1118
name: string;
1219
version: string;
@@ -19,6 +26,7 @@ export interface ServerConfig {
1926
authentication?: AuthConfig;
2027
performance?: PerformanceConfig;
2128
multiTenancy?: MultiTenancyConfig;
29+
connectionPool?: ConnectionPoolServerConfig;
2230
}
2331

2432
/**
@@ -73,6 +81,13 @@ export const DEFAULT_PERFORMANCE_CONFIG: PerformanceConfig = {
7381
concurrentRequestLimit: 100,
7482
};
7583

84+
export const DEFAULT_CONNECTION_POOL_CONFIG: ConnectionPoolServerConfig = {
85+
maxConnections: parseInt(process.env.LIGHTHOUSE_POOL_MAX_CONNECTIONS || "10", 10),
86+
idleTimeoutMs: parseInt(process.env.LIGHTHOUSE_POOL_IDLE_TIMEOUT || "60000", 10),
87+
requestTimeoutMs: parseInt(process.env.LIGHTHOUSE_POOL_REQUEST_TIMEOUT || "30000", 10),
88+
keepAlive: process.env.LIGHTHOUSE_POOL_KEEP_ALIVE !== "false",
89+
};
90+
7691
export const DEFAULT_ORGANIZATION_SETTINGS: OrganizationSettings = {
7792
defaultStorageQuota: 10 * 1024 * 1024 * 1024, // 10GB
7893
defaultRateLimit: 1000, // 1000 requests per minute
@@ -131,6 +146,7 @@ export function getDefaultServerConfig(): ServerConfig {
131146
authentication: getDefaultAuthConfig(),
132147
performance: DEFAULT_PERFORMANCE_CONFIG,
133148
multiTenancy: DEFAULT_MULTI_TENANCY_CONFIG,
149+
connectionPool: DEFAULT_CONNECTION_POOL_CONFIG,
134150
};
135151
}
136152

@@ -149,6 +165,7 @@ export const DEFAULT_SERVER_CONFIG: ServerConfig = {
149165
authentication: DEFAULT_AUTH_CONFIG,
150166
performance: DEFAULT_PERFORMANCE_CONFIG,
151167
multiTenancy: DEFAULT_MULTI_TENANCY_CONFIG,
168+
connectionPool: DEFAULT_CONNECTION_POOL_CONFIG,
152169
};
153170

154171
/**

apps/mcp-server/src/services/LighthouseService.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import {
66
LighthouseAISDK,
77
EnhancedAccessCondition,
8+
ConnectionPoolConfig,
89
BatchUploadOptions,
910
BatchDownloadOptions,
1011
BatchOperationResult,
@@ -29,7 +30,7 @@ export class LighthouseService implements ILighthouseService {
2930
private fileCache: Map<string, StoredFile> = new Map();
3031
private datasetCache: Map<string, Dataset> = new Map();
3132

32-
constructor(apiKey: string, logger?: Logger, dbPath?: string) {
33+
constructor(apiKey: string, logger?: Logger, dbPath?: string, poolConfig?: ConnectionPoolConfig) {
3334
this.logger = logger || Logger.getInstance({ level: "info", component: "LighthouseService" });
3435
this.dbPath = dbPath;
3536

@@ -41,6 +42,7 @@ export class LighthouseService implements ILighthouseService {
4142
timeout: 30000,
4243
maxRetries: 3,
4344
debug: false,
45+
pool: poolConfig,
4446
});
4547

4648
// Set up event listeners for progress tracking
@@ -450,6 +452,7 @@ export class LighthouseService implements ILighthouseService {
450452
activeOperations: this.sdk.getActiveOperations(),
451453
errorMetrics: this.sdk.getErrorMetrics(),
452454
circuitBreaker: this.sdk.getCircuitBreakerStatus(),
455+
connectionPool: this.sdk.getConnectionPoolStats(),
453456
};
454457
}
455458

packages/sdk-wrapper/src/LighthouseAISDK.ts

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ import { EventEmitter } from "eventemitter3";
22
import lighthouse from "@lighthouse-web3/sdk";
33
import { readFileSync, createWriteStream, promises as fsPromises } from "fs";
44
import { dirname } from "path";
5-
import axios from "axios";
5+
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
66
import { AuthenticationManager } from "./auth/AuthenticationManager";
77
import { ProgressTracker } from "./progress/ProgressTracker";
88
import { ErrorHandler } from "./errors/ErrorHandler";
99
import { CircuitBreaker } from "./errors/CircuitBreaker";
1010
import { EncryptionManager } from "./encryption/EncryptionManager";
1111
import { RateLimiter } from "./utils/RateLimiter";
12+
import { ConnectionPool, ConnectionPoolConfig } from "./pool";
1213
import {
1314
LighthouseConfig,
1415
UploadOptions,
@@ -78,6 +79,7 @@ export class LighthouseAISDK extends EventEmitter {
7879
private circuitBreaker: CircuitBreaker;
7980
private encryption: EncryptionManager;
8081
private rateLimiter: RateLimiter;
82+
private connectionPool: ConnectionPool | null;
8183
private memoryManager: MemoryManager;
8284
private config: LighthouseConfig;
8385

@@ -101,6 +103,22 @@ export class LighthouseAISDK extends EventEmitter {
101103
autoCleanup: true,
102104
});
103105

106+
// Initialize connection pool (unless explicitly disabled)
107+
if (config.pool === false) {
108+
this.connectionPool = null;
109+
} else {
110+
const poolConfig: ConnectionPoolConfig = {
111+
maxConnections: 10,
112+
acquireTimeout: 5000,
113+
idleTimeout: 60000,
114+
requestTimeout: config.timeout || 30000,
115+
keepAlive: true,
116+
maxSockets: 50,
117+
...(typeof config.pool === "object" ? config.pool : {}),
118+
};
119+
this.connectionPool = new ConnectionPool(poolConfig);
120+
}
121+
104122
// Forward authentication events
105123
this.auth.on("auth:error", (error) => this.emit("auth:error", error));
106124
this.auth.on("auth:refresh", () => this.emit("auth:refresh"));
@@ -145,6 +163,15 @@ export class LighthouseAISDK extends EventEmitter {
145163
this.emit("encryption:access:control:error", event),
146164
);
147165

166+
// Forward connection pool events
167+
if (this.connectionPool) {
168+
this.connectionPool.on("acquire", (event) => this.emit("pool:acquire", event));
169+
this.connectionPool.on("create", (event) => this.emit("pool:create", event));
170+
this.connectionPool.on("queue", (event) => this.emit("pool:queue", event));
171+
this.connectionPool.on("release", (event) => this.emit("pool:release", event));
172+
this.connectionPool.on("cleanup", (event) => this.emit("pool:cleanup", event));
173+
}
174+
148175
// Forward memory manager events
149176
this.memoryManager.on("backpressure:start", (event) =>
150177
this.emit("memory:backpressure:start", event),
@@ -363,6 +390,25 @@ Maximum file size may be exceeded. Try uploading a smaller file.`);
363390
]);
364391
}
365392

393+
/**
394+
* Execute an HTTP request using the connection pool if available,
395+
* otherwise fall back to a direct axios call.
396+
*/
397+
private async executeHttpRequest<T = any>(config: AxiosRequestConfig): Promise<AxiosResponse<T>> {
398+
if (this.connectionPool) {
399+
const instance = await this.connectionPool.acquire();
400+
try {
401+
return await instance.request<T>(config);
402+
} finally {
403+
this.connectionPool.release(instance);
404+
}
405+
} else {
406+
const axiosLib: { request: (config: AxiosRequestConfig) => Promise<AxiosResponse<T>> } =
407+
eval("require")("axios");
408+
return axiosLib.request(config);
409+
}
410+
}
411+
366412
/**
367413
* Upload file via direct API call as fallback when SDK fails
368414
*/
@@ -375,12 +421,14 @@ Maximum file size may be exceeded. Try uploading a smaller file.`);
375421
// when the standard SDK fails (usually due to node.lighthouse.storage being down)
376422

377423
const FormData = eval("require")("form-data");
378-
const axios = eval("require")("axios");
379424

380425
const formData = new FormData();
381426
formData.append("file", fileBuffer, fileName);
382427

383-
const response = await axios.post("https://api.lighthouse.storage/api/v0/add", formData, {
428+
const response = await this.executeHttpRequest({
429+
method: "POST",
430+
url: "https://api.lighthouse.storage/api/v0/add",
431+
data: formData,
384432
headers: {
385433
...formData.getHeaders(),
386434
Authorization: `Bearer ${apiKey}`,
@@ -932,6 +980,24 @@ Check your internet connection and try again.`);
932980
return this.circuitBreaker.getMetrics();
933981
}
934982

983+
/**
984+
* Get connection pool statistics.
985+
* Returns null if the connection pool is disabled.
986+
*/
987+
getConnectionPoolStats(): {
988+
totalConnections: number;
989+
activeConnections: number;
990+
idleConnections: number;
991+
queuedRequests: number;
992+
totalRequests: number;
993+
averageWaitTime: number;
994+
} | null {
995+
if (!this.connectionPool) {
996+
return null;
997+
}
998+
return this.connectionPool.getStats();
999+
}
1000+
9351001
/**
9361002
* Reset error metrics
9371003
*/
@@ -1627,6 +1693,9 @@ Check your internet connection and try again.`);
16271693
this.auth.destroy();
16281694
this.progress.cleanup();
16291695
this.encryption.destroy();
1696+
if (this.connectionPool) {
1697+
this.connectionPool.destroy();
1698+
}
16301699
this.memoryManager.destroy();
16311700
this.removeAllListeners();
16321701
}

0 commit comments

Comments
 (0)