-
Notifications
You must be signed in to change notification settings - Fork 3
feat: Add HTTP connection pooling for Lighthouse API calls #75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,13 +2,14 @@ import { EventEmitter } from "eventemitter3"; | |
| import lighthouse from "@lighthouse-web3/sdk"; | ||
| import { readFileSync, createWriteStream, promises as fsPromises } from "fs"; | ||
| import { dirname } from "path"; | ||
| import axios from "axios"; | ||
| import axios, { AxiosRequestConfig, AxiosResponse } from "axios"; | ||
| import { AuthenticationManager } from "./auth/AuthenticationManager"; | ||
| import { ProgressTracker } from "./progress/ProgressTracker"; | ||
| import { ErrorHandler } from "./errors/ErrorHandler"; | ||
| import { CircuitBreaker } from "./errors/CircuitBreaker"; | ||
| import { EncryptionManager } from "./encryption/EncryptionManager"; | ||
| import { RateLimiter } from "./utils/RateLimiter"; | ||
| import { ConnectionPool, ConnectionPoolConfig } from "./pool"; | ||
| import { | ||
| LighthouseConfig, | ||
| UploadOptions, | ||
|
|
@@ -78,6 +79,7 @@ export class LighthouseAISDK extends EventEmitter { | |
| private circuitBreaker: CircuitBreaker; | ||
| private encryption: EncryptionManager; | ||
| private rateLimiter: RateLimiter; | ||
| private connectionPool: ConnectionPool | null; | ||
| private memoryManager: MemoryManager; | ||
| private config: LighthouseConfig; | ||
|
|
||
|
|
@@ -101,6 +103,22 @@ export class LighthouseAISDK extends EventEmitter { | |
| autoCleanup: true, | ||
| }); | ||
|
|
||
| // Initialize connection pool (unless explicitly disabled) | ||
| if (config.pool === false) { | ||
| this.connectionPool = null; | ||
| } else { | ||
| const poolConfig: ConnectionPoolConfig = { | ||
| maxConnections: 10, | ||
| acquireTimeout: 5000, | ||
| idleTimeout: 60000, | ||
| requestTimeout: config.timeout || 30000, | ||
| keepAlive: true, | ||
| maxSockets: 50, | ||
| ...(typeof config.pool === "object" ? config.pool : {}), | ||
| }; | ||
| this.connectionPool = new ConnectionPool(poolConfig); | ||
| } | ||
|
|
||
| // Forward authentication events | ||
| this.auth.on("auth:error", (error) => this.emit("auth:error", error)); | ||
| this.auth.on("auth:refresh", () => this.emit("auth:refresh")); | ||
|
|
@@ -145,6 +163,15 @@ export class LighthouseAISDK extends EventEmitter { | |
| this.emit("encryption:access:control:error", event), | ||
| ); | ||
|
|
||
| // Forward connection pool events | ||
| if (this.connectionPool) { | ||
| this.connectionPool.on("acquire", (event) => this.emit("pool:acquire", event)); | ||
| this.connectionPool.on("create", (event) => this.emit("pool:create", event)); | ||
| this.connectionPool.on("queue", (event) => this.emit("pool:queue", event)); | ||
| this.connectionPool.on("release", (event) => this.emit("pool:release", event)); | ||
| this.connectionPool.on("cleanup", (event) => this.emit("pool:cleanup", event)); | ||
| } | ||
|
|
||
| // Forward memory manager events | ||
| this.memoryManager.on("backpressure:start", (event) => | ||
| this.emit("memory:backpressure:start", event), | ||
|
|
@@ -363,6 +390,25 @@ Maximum file size may be exceeded. Try uploading a smaller file.`); | |
| ]); | ||
| } | ||
|
|
||
| /** | ||
| * Execute an HTTP request using the connection pool if available, | ||
| * otherwise fall back to a direct axios call. | ||
| */ | ||
| private async executeHttpRequest<T = any>(config: AxiosRequestConfig): Promise<AxiosResponse<T>> { | ||
| if (this.connectionPool) { | ||
| const instance = await this.connectionPool.acquire(); | ||
| try { | ||
| return await instance.request<T>(config); | ||
| } finally { | ||
| this.connectionPool.release(instance); | ||
| } | ||
| } else { | ||
| const axiosLib: { request: (config: AxiosRequestConfig) => Promise<AxiosResponse<T>> } = | ||
|
Comment on lines
+397
to
+406
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (performance): Dynamic axios require in executeHttpRequest is repeated on every non-pooled call In the non-pooled branch, Suggested implementation: let cachedAxiosLib:
| { request: (config: AxiosRequestConfig) => Promise<AxiosResponse<any>> }
| null = null;
function getAxiosLib<T = any>(): {
request: (config: AxiosRequestConfig) => Promise<AxiosResponse<T>>;
} {
if (!cachedAxiosLib) {
// Use dynamic require to avoid bundler issues while caching the result
cachedAxiosLib = eval("require")("axios");
}
return cachedAxiosLib as {
request: (config: AxiosRequestConfig) => Promise<AxiosResponse<T>>;
};
}
/**
* Execute an HTTP request using the connection pool if available,
* otherwise fall back to a direct axios call.
*/
private async executeHttpRequest<T = any>(config: AxiosRequestConfig): Promise<AxiosResponse<T>> { } else {
const axiosLib = getAxiosLib<T>();
return axiosLib.request(config);
}None required, assuming |
||
| eval("require")("axios"); | ||
| return axiosLib.request(config); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Upload file via direct API call as fallback when SDK fails | ||
| */ | ||
|
|
@@ -375,12 +421,14 @@ Maximum file size may be exceeded. Try uploading a smaller file.`); | |
| // when the standard SDK fails (usually due to node.lighthouse.storage being down) | ||
|
|
||
| const FormData = eval("require")("form-data"); | ||
| const axios = eval("require")("axios"); | ||
|
|
||
| const formData = new FormData(); | ||
| formData.append("file", fileBuffer, fileName); | ||
|
|
||
| const response = await axios.post("https://api.lighthouse.storage/api/v0/add", formData, { | ||
| const response = await this.executeHttpRequest({ | ||
| method: "POST", | ||
| url: "https://api.lighthouse.storage/api/v0/add", | ||
| data: formData, | ||
| headers: { | ||
| ...formData.getHeaders(), | ||
| Authorization: `Bearer ${apiKey}`, | ||
|
|
@@ -932,6 +980,24 @@ Check your internet connection and try again.`); | |
| return this.circuitBreaker.getMetrics(); | ||
| } | ||
|
|
||
| /** | ||
| * Get connection pool statistics. | ||
| * Returns null if the connection pool is disabled. | ||
| */ | ||
| getConnectionPoolStats(): { | ||
| totalConnections: number; | ||
| activeConnections: number; | ||
| idleConnections: number; | ||
| queuedRequests: number; | ||
| totalRequests: number; | ||
| averageWaitTime: number; | ||
| } | null { | ||
| if (!this.connectionPool) { | ||
| return null; | ||
| } | ||
| return this.connectionPool.getStats(); | ||
| } | ||
|
|
||
| /** | ||
| * Reset error metrics | ||
| */ | ||
|
|
@@ -1627,6 +1693,9 @@ Check your internet connection and try again.`); | |
| this.auth.destroy(); | ||
| this.progress.cleanup(); | ||
| this.encryption.destroy(); | ||
| if (this.connectionPool) { | ||
| this.connectionPool.destroy(); | ||
| } | ||
| this.memoryManager.destroy(); | ||
| this.removeAllListeners(); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (bug_risk): ConnectionPoolServerConfig shape doesn’t align with ConnectionPoolConfig used by the SDK
This type uses
idleTimeoutMs/requestTimeoutMs, whileLighthouseConfig.poolisConnectionPoolConfig, which (perLighthouseAISDKusage) expectsidleTimeout,requestTimeout,acquireTimeout, andmaxSockets. IfServerConfig.connectionPoolis passed through toLighthouseService/SDK, your timeout values will be ignored and required fields may be missing. Please either align this interface withConnectionPoolConfigor explicitly map it to aConnectionPoolConfigbefore callingLighthouseAISDK.