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
// Basic keepalive example
async function basicKeepaliveExample(): Promise<void> {
console.log('Starting basic keepalive example...');
// Connect to NextBlock (see connection.md)
// const config = configFromEnv();
// const connection = await createNextBlockClient(config);
// Start keepalive task
const keepalivePromise = startKeepaliveTask(
// connection.client,
{
pingInterval: 30000, // Ping every 30 seconds
maxConsecutiveFailures: 3,
reconnectDelay: 5000,
healthCheckEnabled: true,
timeout: 15000,
}
);
console.log('Application running with keepalive...');
// Simulate application work
setTimeout(() => {
console.log('Application work completed, stopping...');
process.exit(0);
}, 300000); // Run for 5 minutes
await keepalivePromise;
}
// Advanced keepalive with health monitoring
async function advancedKeepaliveExample(): Promise<void> {
console.log('Starting advanced keepalive example...');
// Configuration
const keepaliveConfig: KeepaliveConfig = {
pingInterval: 30000, // Ping every 30 seconds
maxConsecutiveFailures: 3,
reconnectDelay: 10000, // Wait 10 seconds before reconnection
healthCheckEnabled: true,
timeout: 15000,
};
// Use connection manager with integrated keepalive
const manager = new ConnectionManagerWithKeepalive(
{}, // nextblockConfig placeholder
keepaliveConfig
);
try {
const connected = await manager.connect();
if (connected) {
console.log('Connected with keepalive enabled');
// Start health monitoring
const healthMonitor = new HealthMonitor(manager.keepaliveManager!);
healthMonitor.startMonitoring(60000); // Check every minute
// Simulate application work with periodic health checks
for (let i = 0; i < 10; i++) {
await new Promise(resolve => setTimeout(resolve, 30000));
const health = manager.getConnectionHealth();
if (health) {
console.log(`Connection health check ${i + 1}:`);
console.log(` Healthy: ${health.isHealthy}`);
console.log(` Success rate: ${((health.totalPingsSuccessful / Math.max(health.totalPingsSent, 1)) * 100).toFixed(1)}%`);
console.log(` Avg ping time: ${health.averagePingTime.toFixed(1)}ms`);
console.log(` Total pings: ${health.totalPingsSent}`);
}
}
healthMonitor.stopMonitoring();
} else {
console.error('Failed to establish connection');
}
} catch (error) {
console.error('Advanced keepalive example failed:', error);
} finally {
await manager.disconnect();
}
}
// Connection pool with keepalive
class ConnectionPoolWithKeepalive {
private connections: ConnectionManagerWithKeepalive[] = [];
private currentIndex: number = 0;
private poolSize: number;
private nextblockConfig: any;
private keepaliveConfig: KeepaliveConfig;
constructor(
nextblockConfig: any,
keepaliveConfig: KeepaliveConfig,
poolSize: number = 5
) {
this.nextblockConfig = nextblockConfig;
this.keepaliveConfig = keepaliveConfig;
this.poolSize = poolSize;
}
async initialize(): Promise<void> {
console.log(`Initializing connection pool with ${this.poolSize} connections...`);
const connectionPromises = Array.from({ length: this.poolSize }, async (_, i) => {
const manager = new ConnectionManagerWithKeepalive(
this.nextblockConfig,
this.keepaliveConfig
);
const connected = await manager.connect();
if (connected) {
this.connections.push(manager);
console.log(`Connection ${i + 1} established with keepalive`);
} else {
console.warn(`Failed to create connection ${i + 1}`);
}
});
await Promise.all(connectionPromises);
console.log(`Connection pool initialized with ${this.connections.length} connections`);
}
getConnection(): ConnectionManagerWithKeepalive {
if (this.connections.length === 0) {
throw new Error('No available connections in pool');
}
const connection = this.connections[this.currentIndex];
this.currentIndex = (this.currentIndex + 1) % this.connections.length;
return connection;
}
async closeAll(): Promise<void> {
await Promise.all(this.connections.map(conn => conn.disconnect()));
this.connections = [];
console.log('All connections closed');
}
getPoolHealth(): ConnectionHealth[] {
return this.connections
.map(conn => conn.getConnectionHealth())
.filter((health): health is ConnectionHealth => health !== null);
}
}
// Main example runner
async function main(): Promise<void> {
console.log('NextBlock Keepalive Examples');
const choice = process.argv[2] || '1';
switch (choice) {
case '1':
console.log('\n1. Basic Keepalive:');
await basicKeepaliveExample();
break;
case '2':
console.log('\n2. Advanced Keepalive with Monitoring:');
await advancedKeepaliveExample();
break;
case '3':
console.log('\n3. Connection Pool with Keepalive:');
const pool = new ConnectionPoolWithKeepalive(
{}, // config placeholder
defaultKeepaliveConfig,
3
);
await pool.initialize();
// Use the pool for some time
setTimeout(async () => {
const poolHealth = pool.getPoolHealth();
console.log(`Pool health summary: ${poolHealth.length} healthy connections`);
await pool.closeAll();
}, 60000);
break;
default:
console.log('Usage: node keepalive.js [1|2|3]');
console.log(' 1: Basic keepalive');
console.log(' 2: Advanced keepalive with monitoring');
console.log(' 3: Connection pool with keepalive');
}
}
if (require.main === module) {
main().catch(console.error);
}
export {
KeepaliveConfig,
ConnectionHealth,
KeepaliveManager,
ConnectionManagerWithKeepalive,
HealthMonitor,
ConnectionPoolWithKeepalive,
startKeepaliveTask,
};
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