- Memory leak identified: Drift SDK accumulates WebSocket subscriptions over time - Root cause: accountUnsubscribe errors pile up when connections close/reconnect - Symptom: Heap grows to 4GB+ after 10+ hours, eventual OOM crash - Solution: Automatic reconnection every 4 hours to clear subscriptions Changes: - lib/drift/client.ts: Add reconnectTimer and scheduleReconnection() - lib/drift/client.ts: Implement private reconnect() method - lib/drift/client.ts: Clear timer in disconnect() - app/api/drift/reconnect/route.ts: Manual reconnection endpoint (POST) - app/api/drift/reconnect/route.ts: Reconnection status endpoint (GET) Impact: - Prevents JavaScript heap out of memory crashes - Telegram bot timeouts resolved (was failing due to unresponsive bot) - System will auto-heal every 4 hours instead of requiring manual restart - Emergency manual reconnect available via API if needed Tested: Container restarted successfully, no more WebSocket accumulation expected
78 lines
2.2 KiB
TypeScript
78 lines
2.2 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
|
import { getDriftService } from '@/lib/drift/client'
|
|
|
|
/**
|
|
* Manual Drift reconnection endpoint
|
|
* Forces reconnection to clear WebSocket subscriptions and prevent memory leaks
|
|
*
|
|
* POST /api/drift/reconnect
|
|
* Authorization: Bearer <API_SECRET_KEY>
|
|
*/
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
// Verify API key
|
|
const authHeader = request.headers.get('authorization')
|
|
const apiKey = process.env.API_SECRET_KEY
|
|
|
|
if (!authHeader || !authHeader.startsWith('Bearer ') || authHeader.slice(7) !== apiKey) {
|
|
return NextResponse.json(
|
|
{ success: false, error: 'Unauthorized' },
|
|
{ status: 401 }
|
|
)
|
|
}
|
|
|
|
console.log('🔄 Manual reconnection requested via API...')
|
|
|
|
const driftService = getDriftService()
|
|
|
|
// Force reconnection by calling private method through type assertion
|
|
await (driftService as any).reconnect()
|
|
|
|
console.log('✅ Manual reconnection complete')
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
message: 'Drift connection refreshed successfully',
|
|
timestamp: new Date().toISOString()
|
|
})
|
|
|
|
} catch (error: any) {
|
|
console.error('❌ Manual reconnection failed:', error)
|
|
return NextResponse.json(
|
|
{
|
|
success: false,
|
|
error: error.message || 'Failed to reconnect to Drift Protocol',
|
|
timestamp: new Date().toISOString()
|
|
},
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get reconnection status and next scheduled reconnection time
|
|
*
|
|
* GET /api/drift/reconnect
|
|
*/
|
|
export async function GET(request: NextRequest) {
|
|
try {
|
|
const driftService = getDriftService()
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
status: {
|
|
initialized: (driftService as any).isInitialized,
|
|
hasReconnectTimer: !!(driftService as any).reconnectTimer,
|
|
reconnectIntervalHours: (driftService as any).reconnectIntervalMs / 1000 / 60 / 60,
|
|
message: 'Automatic reconnection runs every 4 hours to prevent memory leaks'
|
|
}
|
|
})
|
|
|
|
} catch (error: any) {
|
|
return NextResponse.json(
|
|
{ success: false, error: error.message },
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
}
|