Keepalive
Maintain persistent gRPC connections to NextBlock using JavaScript/TypeScript.
// Connection health tracking
interface ConnectionHealth {
isHealthy: boolean;
lastSuccessfulPing: Date;
consecutiveFailures: number;
totalPingsSent: number;
totalPingsSuccessful: number;
averagePingTime: number;
}
// Keepalive configuration
interface KeepaliveConfig {
pingInterval: number; // milliseconds
maxConsecutiveFailures: number;
reconnectDelay: number; // milliseconds
healthCheckEnabled: boolean;
timeout: number; // milliseconds
}
// Default keepalive configuration
const defaultKeepaliveConfig: KeepaliveConfig = {
pingInterval: 60000, // 60 seconds
maxConsecutiveFailures: 3,
reconnectDelay: 5000, // 5 seconds
healthCheckEnabled: true,
timeout: 15000, // 15 seconds
};
// Basic keepalive implementation
async function startKeepaliveTask(
// nextblockClient: any, // Your generated gRPC client
config: KeepaliveConfig = defaultKeepaliveConfig
): Promise<void> {
console.log(`Starting keepalive task with ${config.pingInterval}ms interval`);
const keepaliveInterval = setInterval(async () => {
try {
const startTime = Date.now();
/* Uncomment when you have the generated gRPC client
try {
await Promise.race([
nextblockClient.ping({}),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), config.timeout)
)
]);
const pingTime = Date.now() - startTime;
console.log(`Keepalive ping successful (${pingTime}ms) at ${new Date().toTimeString()}`);
} catch (error) {
console.error('Keepalive ping failed:', error);
// Optionally implement reconnection logic
clearInterval(keepaliveInterval);
return;
}
*/
// Mock ping for demonstration
await new Promise(resolve => setTimeout(resolve, 50 + Math.random() * 100)); // 50-150ms delay
const pingTime = Date.now() - startTime;
console.log(`Mock keepalive ping successful (${pingTime}ms) at ${new Date().toTimeString()}`);
} catch (error) {
console.error('Keepalive task error:', error);
clearInterval(keepaliveInterval);
}
}, config.pingInterval);
// Handle graceful shutdown
process.on('SIGINT', () => {
clearInterval(keepaliveInterval);
console.log('Keepalive task stopped');
process.exit(0);
});
// Return a promise that never resolves (keeps running)
return new Promise(() => {});
}
// Advanced keepalive manager
class KeepaliveManager {
private config: KeepaliveConfig;
private health: ConnectionHealth;
private keepaliveInterval?: NodeJS.Timeout;
private isRunning: boolean = false;
constructor(
// private nextblockClient: any, // Your generated gRPC client
config: KeepaliveConfig = defaultKeepaliveConfig
) {
this.config = config;
this.health = {
isHealthy: true,
lastSuccessfulPing: new Date(),
consecutiveFailures: 0,
totalPingsSent: 0,
totalPingsSuccessful: 0,
averagePingTime: 0,
};
}
async start(): Promise<void> {
if (this.isRunning) return;
this.isRunning = true;
this.keepaliveInterval = setInterval(
() => this.sendPing(),
this.config.pingInterval
);
console.log('Keepalive manager started');
}
async stop(): Promise<void> {
this.isRunning = false;
if (this.keepaliveInterval) {
clearInterval(this.keepaliveInterval);
this.keepaliveInterval = undefined;
}
console.log('Keepalive manager stopped');
}
private async sendPing(): Promise<void> {
const startTime = Date.now();
try {
/* Uncomment when you have the generated gRPC client
await Promise.race([
this.nextblockClient.ping({}),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), this.config.timeout)
)
]);
*/
// Mock ping delay
await new Promise(resolve =>
setTimeout(resolve, 50 + Math.random() * 100)
);
const pingTime = Date.now() - startTime;
this.updateHealthSuccess(pingTime);
console.log(
`Keepalive ping successful (${pingTime}ms) - ` +
`Health: ${this.getSuccessRate().toFixed(1)}%`
);
} catch (error) {
this.updateHealthFailure();
console.error(`Keepalive ping failed: ${error}`);
if (!this.health.isHealthy) {
await this.handleConnectionRecovery();
}
}
}
private updateHealthSuccess(pingTime: number): void {
this.health.isHealthy = true;
this.health.lastSuccessfulPing = new Date();
this.health.consecutiveFailures = 0;
this.health.totalPingsSent++;
this.health.totalPingsSuccessful++;
// Update average ping time
if (this.health.totalPingsSuccessful === 1) {
this.health.averagePingTime = pingTime;
} else {
this.health.averagePingTime = (
(this.health.averagePingTime * (this.health.totalPingsSuccessful - 1) + pingTime) /
this.health.totalPingsSuccessful
);
}
}
private updateHealthFailure(): void {
this.health.consecutiveFailures++;
this.health.totalPingsSent++;
if (this.health.consecutiveFailures >= this.config.maxConsecutiveFailures) {
this.health.isHealthy = false;
console.warn(
`Connection marked as unhealthy after ${this.health.consecutiveFailures} consecutive failures`
);
}
}
private async handleConnectionRecovery(): Promise<void> {
console.warn('Connection unhealthy, attempting recovery...');
// Wait before attempting recovery
await new Promise(resolve => setTimeout(resolve, this.config.reconnectDelay));
try {
// Implement connection recovery logic here
console.log('Connection recovery attempted');
// Reset some health metrics on successful recovery
// this.health.consecutiveFailures = 0;
} catch (error) {
console.error('Connection recovery failed:', error);
}
}
getHealth(): ConnectionHealth {
return { ...this.health };
}
isHealthy(): boolean {
return this.health.isHealthy;
}
private getSuccessRate(): number {
if (this.health.totalPingsSent === 0) return 100;
return (this.health.totalPingsSuccessful / this.health.totalPingsSent) * 100;
}
}
// Connection manager with integrated keepalive
class ConnectionManagerWithKeepalive {
private nextblockConfig: any;
private keepaliveConfig: KeepaliveConfig;
private keepaliveManager?: KeepaliveManager;
// private client?: any;
private isConnected: boolean = false;
constructor(
nextblockConfig: any,
keepaliveConfig: KeepaliveConfig = defaultKeepaliveConfig
) {
this.nextblockConfig = nextblockConfig;
this.keepaliveConfig = keepaliveConfig;
}
async connect(): Promise<boolean> {
try {
// Create connection (see connection.md)
// const connection = await createNextBlockClient(this.nextblockConfig);
// this.client = connection.client;
// Test connection
// await this.client.ping({});
this.isConnected = true;
console.log('Successfully connected to NextBlock');
// Start keepalive
this.keepaliveManager = new KeepaliveManager(
// this.client,
this.keepaliveConfig
);
await this.keepaliveManager.start();
return true;
} catch (error) {
console.error('Failed to connect:', error);
this.isConnected = false;
return false;
}
}
async disconnect(): Promise<void> {
if (this.keepaliveManager) {
await this.keepaliveManager.stop();
}
this.isConnected = false;
console.log('Disconnected from NextBlock');
}
getConnectionHealth(): ConnectionHealth | null {
return this.keepaliveManager?.getHealth() || null;
}
get connected(): boolean {
return this.isConnected;
}
// get grpcClient(): any {
// return this.client;
// }
}
// Health monitoring and alerting
class HealthMonitor {
private keepaliveManager: KeepaliveManager;
private alertThresholds = {
successRate: 90.0, // Alert if success rate < 90%
avgPingTime: 1000, // Alert if avg ping time > 1s
consecutiveFailures: 2, // Alert after 2 consecutive failures
};
private monitorInterval?: NodeJS.Timeout;
constructor(keepaliveManager: KeepaliveManager) {
this.keepaliveManager = keepaliveManager;
}
startMonitoring(checkInterval: number = 30000): void {
this.monitorInterval = setInterval(() => {
this.checkHealth();
}, checkInterval);
console.log(`Health monitoring started with ${checkInterval}ms interval`);
}
stopMonitoring(): void {
if (this.monitorInterval) {
clearInterval(this.monitorInterval);
this.monitorInterval = undefined;
}
console.log('Health monitoring stopped');
}
private checkHealth(): void {
const health = this.keepaliveManager.getHealth();
// Check success rate
const successRate = health.totalPingsSent > 0
? (health.totalPingsSuccessful / health.totalPingsSent) * 100
: 100;
if (successRate < this.alertThresholds.successRate) {
this.triggerAlert('Low success rate', `Success rate: ${successRate.toFixed(1)}%`);
}
// Check average ping time
if (health.averagePingTime > this.alertThresholds.avgPingTime) {
this.triggerAlert('High ping time', `Average ping time: ${health.averagePingTime.toFixed(1)}ms`);
}
// Check consecutive failures
if (health.consecutiveFailures >= this.alertThresholds.consecutiveFailures) {
this.triggerAlert('Connection issues', `Consecutive failures: ${health.consecutiveFailures}`);
}
}
private triggerAlert(alertType: string, details: string): void {
const timestamp = new Date().toISOString();
console.warn(`🚨 HEALTH ALERT [${timestamp}] ${alertType}: ${details}`);
// Implement additional alerting logic here
// - Send notifications
// - Post to monitoring systems
// - Trigger recovery procedures
}
}Usage Examples
Best Practices
Appropriate intervals: Use 30-60 second ping intervals for most applications
Health monitoring: Track connection health and implement alerting
Graceful recovery: Handle connection failures with exponential backoff
Resource cleanup: Always stop keepalive tasks when shutting down
Timeout handling: Set reasonable timeouts for ping requests
Logging: Log keepalive events for debugging and monitoring
Integration: Integrate keepalive with your connection management system
Connection pooling: Use connection pools for high-throughput applications
Last updated