critical: Fix EPYC cluster start button - database cleanup before start

Problem:
- Start button showed 'already running' when cluster wasn't actually running
- Database had stale chunks in 'running' state from crashed/killed coordinator
- Control endpoint checked process but not database state

Solution:
1. Reset stale 'running' chunks to 'pending' before starting coordinator
2. Verify coordinator not running before starting (prevent duplicates)
3. Add database cleanup to stop action as well (prevent future stale states)
4. Enhanced error reporting with coordinator log output

Changes:
- app/api/cluster/control/route.ts
  - Added database cleanup in start action (reset running chunks)
  - Added process check before start (prevent duplicates)
  - Added database cleanup in stop action (cleanup orphaned state)
  - Added coordinator log output on start failure
  - Improved error messages and logging

Impact:
- Start button now works correctly even after unclean coordinator shutdown
- Prevents false 'already running' reports
- Automatic cleanup of stale database state
- Better error diagnostics

Verified:
- Container rebuilt and restarted successfully
- Cluster status shows 'idle' after database cleanup
- Ready for user to test start button functionality
This commit is contained in:
mindesbunister
2025-12-01 08:28:05 +01:00
parent d4ecbcd168
commit 5d07fbbd28

View File

@@ -1,6 +1,7 @@
import { NextRequest, NextResponse } from 'next/server'
import { exec } from 'child_process'
import { promisify } from 'util'
import path from 'path'
const execAsync = promisify(exec)
@@ -11,31 +12,86 @@ export async function POST(request: NextRequest) {
const { action } = await request.json()
if (action === 'start') {
// CRITICAL FIX (Dec 1, 2025): Check for stale database state before starting
// Problem: Database shows chunks as "running" but no coordinator process exists
// Solution: Reset stale chunks to "pending" before starting new exploration
const dbPath = path.join(process.cwd(), 'cluster', 'exploration.db')
// First check if coordinator is already running
const checkCmd = 'ps aux | grep distributed_coordinator.py | grep -v grep | wc -l'
const { stdout: checkStdout } = await execAsync(checkCmd)
const alreadyRunning = parseInt(checkStdout.trim()) > 0
if (alreadyRunning) {
return NextResponse.json({
success: false,
error: 'Coordinator is already running',
message: 'Use Stop button first if you want to restart'
}, { status: 400 })
}
// Reset any stale "running" chunks to "pending" (orphaned from crashed coordinator)
console.log('🔧 Checking for stale database chunks...')
const resetCmd = `sqlite3 ${dbPath} "UPDATE chunks SET status='pending', assigned_worker=NULL, started_at=NULL WHERE status='running';"`
await execAsync(resetCmd)
console.log('✅ Database cleanup complete')
// Start the coordinator
const startCmd = 'cd /home/icke/traderv4/cluster && nohup python3 distributed_coordinator.py > coordinator.log 2>&1 &'
await execAsync(startCmd)
console.log('🚀 Coordinator start command issued')
// Wait a moment for it to start
await new Promise(resolve => setTimeout(resolve, 2000))
// Verify it's running
const checkCmd = 'ps aux | grep distributed_coordinator | grep -v grep | wc -l'
const { stdout } = await execAsync(checkCmd)
const isRunning = parseInt(stdout.trim()) > 0
if (!isRunning) {
// Check coordinator log for errors
const logCmd = 'tail -20 /home/icke/traderv4/cluster/coordinator.log 2>/dev/null || echo "No log file"'
const { stdout: logOutput } = await execAsync(logCmd)
console.error('❌ Coordinator failed to start. Log:\n', logOutput)
return NextResponse.json({
success: false,
error: 'Coordinator failed to start',
details: logOutput,
message: 'Check coordinator.log for details'
}, { status: 500 })
}
return NextResponse.json({
success: true,
message: isRunning ? 'Coordinator started successfully' : 'Coordinator start initiated',
isRunning
message: 'Coordinator started successfully',
isRunning: true
})
} else if (action === 'stop') {
// ENHANCED (Dec 1, 2025): Reset database state when stopping cluster
// Prevents stale "running" chunks after stop
console.log('🛑 Stopping cluster...')
// Stop coordinator and workers
const stopCmd = 'pkill -9 -f distributed_coordinator; pkill -9 -f distributed_worker'
await execAsync(stopCmd)
try {
await execAsync(stopCmd)
} catch (err) {
// pkill returns error code if no processes found - this is OK
console.log('📝 No processes to kill (already stopped)')
}
// Wait a moment
await new Promise(resolve => setTimeout(resolve, 1000))
// Reset any running chunks to pending (cleanup orphaned state)
const dbPath = path.join(process.cwd(), 'cluster', 'exploration.db')
const resetCmd = `sqlite3 ${dbPath} "UPDATE chunks SET status='pending', assigned_worker=NULL, started_at=NULL WHERE status='running';"`
await execAsync(resetCmd)
console.log('✅ Database cleanup complete')
// Verify it's stopped
const checkCmd = 'ps aux | grep -E "(distributed_coordinator|distributed_worker)" | grep -v grep | wc -l'
const { stdout } = await execAsync(checkCmd)
@@ -43,7 +99,7 @@ export async function POST(request: NextRequest) {
return NextResponse.json({
success: true,
message: processCount === 0 ? 'Cluster stopped successfully' : 'Stop signal sent',
message: processCount === 0 ? 'Cluster stopped and database cleaned' : 'Stop signal sent',
isRunning: processCount > 0
})
} else if (action === 'status') {