**Comprehensive documentation including:** - Root cause analysis for both bugs - Manual test procedures that validated fixes - Code changes with before/after comparisons - Verification results (24 worker processes running) - Lessons learned for future debugging - Current cluster state and next steps Files: cluster/SSH_TIMEOUT_FIX_COMPLETE.md (288 lines)
361 lines
11 KiB
Markdown
361 lines
11 KiB
Markdown
# SSH Timeout Fix - CRITICAL BUG RESOLUTION (Dec 1, 2025)
|
|
|
|
## Status: ✅ **FIXED AND DEPLOYED**
|
|
|
|
**Primary Bug:** SSH commands timing out after 60 seconds, preventing chunks from starting
|
|
**Secondary Bug:** Resumption logic counting all chunks (not just completed), skipping work
|
|
**Result:** Coordinator working, chunk 0 running on worker1 with 24 active processes
|
|
|
|
---
|
|
|
|
## Problems Discovered
|
|
|
|
### Problem 1: SSH Command Construction Bug (CRITICAL)
|
|
|
|
**Symptom:**
|
|
```
|
|
⚠️ SSH command timed out for v9_chunk_000000 on worker1
|
|
```
|
|
|
|
**Root Cause:**
|
|
- Command ends with `&` (background operator): `nohup python3 ... > /tmp/log 2>&1 &`
|
|
- Original code added: `&& echo 'Started chunk ID'`
|
|
- Bash behavior: `&&` waits for previous command to exit successfully
|
|
- Background jobs (`&`) return immediately but `&&` waits for the job to complete
|
|
- Result: SSH command never gets echo output, times out after 60 seconds
|
|
|
|
**Manual Test That Revealed The Pattern:**
|
|
```bash
|
|
# BROKEN (times out):
|
|
$ ssh worker1 "nohup sleep 5 > /tmp/test.log 2>&1 & && echo Started"
|
|
# (waits 60 seconds, timeout)
|
|
|
|
# WORKING (immediate):
|
|
$ ssh worker1 'nohup sleep 5 > /tmp/test.log 2>&1 & echo Started'
|
|
Started # ← Immediate output!
|
|
```
|
|
|
|
**The Fix:**
|
|
Remove `&&` - just use space. In single quotes, shell backgrounds the command with `&` then immediately continues to the next command (echo).
|
|
|
|
**Code Change (Lines 388-401):**
|
|
```python
|
|
# BEFORE (BROKEN):
|
|
ssh_cmd = f"ssh {ssh_opts} {worker['host']} '{cmd} && echo 'Started chunk {chunk_id}' || echo 'FAILED'"
|
|
|
|
# AFTER (FIXED):
|
|
ssh_cmd = f"ssh {ssh_opts} {worker['host']} '{cmd} echo Started_chunk_{chunk_id}'"
|
|
```
|
|
|
|
**Verification:**
|
|
```bash
|
|
$ ssh root@10.10.254.106 'nohup sleep 5 > /tmp/test.log 2>&1 & echo Started_chunk_test'
|
|
Started_chunk_test # ← Works perfectly!
|
|
```
|
|
|
|
---
|
|
|
|
### Problem 2: Resumption Logic Bug (CRITICAL)
|
|
|
|
**Symptom:**
|
|
```
|
|
📋 Resuming from chunk 3 (found 3 existing chunks)
|
|
Starting at combo 6,000 / 4,096
|
|
✅ Assigned 0 initial chunks
|
|
🎉 COMPREHENSIVE EXPLORATION COMPLETE!
|
|
```
|
|
Coordinator immediately exits, thinking all work is done!
|
|
|
|
**Root Cause:**
|
|
- Database had 3 chunks with status='pending' (from previous failed run)
|
|
- Resumption query: `SELECT id FROM chunks WHERE id LIKE 'v9_chunk_%' ORDER BY id DESC LIMIT 1`
|
|
- **Query doesn't filter by status** - counts ALL chunks including pending
|
|
- Found v9_chunk_000002 (last chunk)
|
|
- Logic: Resume from chunk 3 (after last found chunk)
|
|
- Calculation: `chunk_start = 3 * 2000 = 6000`
|
|
- Check: `6000 >= 4096 total combos` → "work complete"
|
|
- Result: No chunks assigned, coordinator exits immediately
|
|
|
|
**The Fix:**
|
|
Only count completed or running chunks, not pending/failed chunks.
|
|
|
|
**Code Change (Lines 514-533):**
|
|
```python
|
|
# BEFORE (BROKEN):
|
|
c.execute("SELECT id FROM chunks WHERE id LIKE 'v9_chunk_%' ORDER BY id DESC LIMIT 1")
|
|
last_chunk = c.fetchone()
|
|
|
|
if last_chunk:
|
|
# Resumes from chunk 3 even if all chunks are 'pending'
|
|
last_counter = int(last_chunk[0].split('_')[-1])
|
|
chunk_id_counter = last_counter + 1
|
|
print(f"📋 Resuming from chunk {chunk_id_counter}")
|
|
|
|
# AFTER (FIXED):
|
|
c.execute("SELECT id FROM chunks WHERE id LIKE 'v9_chunk_%' AND status IN ('completed', 'running') ORDER BY id DESC LIMIT 1")
|
|
last_chunk = c.fetchone()
|
|
|
|
if last_chunk:
|
|
# Only resumes after completed work
|
|
last_counter = int(last_chunk[0].split('_')[-1])
|
|
chunk_id_counter = last_counter + 1
|
|
print(f"📋 Resuming from chunk {chunk_id_counter} (found {last_counter + 1} completed chunks)")
|
|
else:
|
|
# No completed chunks - start fresh
|
|
chunk_id_counter = 0
|
|
chunk_start = 0
|
|
print(f"📋 Starting fresh - no completed chunks found")
|
|
```
|
|
|
|
---
|
|
|
|
### Problem 3: Database State Conflict (RESOLVED)
|
|
|
|
**Symptom:**
|
|
With resumption fix, coordinator would find 0 completed chunks and try to INSERT chunk 0, 1, 2. But chunks already existed in database → UNIQUE constraint error.
|
|
|
|
**The Fix:**
|
|
Clear pending/failed chunks on coordinator start to allow clean retry.
|
|
|
|
**Code Change (Lines 507-514):**
|
|
```python
|
|
# CRITICAL FIX (Dec 1, 2025): Clear pending/failed chunks to allow retry
|
|
conn = sqlite3.connect(self.db.db_path)
|
|
c = conn.cursor()
|
|
c.execute("DELETE FROM chunks WHERE status IN ('pending', 'failed')")
|
|
deleted_count = c.rowcount
|
|
conn.commit()
|
|
conn.close()
|
|
if deleted_count > 0:
|
|
print(f"🗑️ Cleared {deleted_count} pending/failed chunks for retry")
|
|
```
|
|
|
|
---
|
|
|
|
## Verification Results
|
|
|
|
### SSH Command Fix - ✅ WORKING
|
|
|
|
**Manual Test:**
|
|
```bash
|
|
$ ssh root@10.10.254.106 'nohup sleep 5 > /tmp/test.log 2>&1 & echo Started_chunk_test'
|
|
Started_chunk_test # ← Immediate response
|
|
```
|
|
|
|
**Production Test:**
|
|
```bash
|
|
$ sqlite3 exploration.db "SELECT id, status, assigned_worker FROM chunks;"
|
|
v9_chunk_000000|running|worker1
|
|
```
|
|
|
|
**Worker Verification:**
|
|
```bash
|
|
$ ssh root@10.10.254.106 "ps aux | grep -E '[p]ython3.*distributed_worker' | wc -l"
|
|
24 # ← 24 Python backtester processes running!
|
|
```
|
|
|
|
### Resumption Logic Fix - ✅ WORKING
|
|
|
|
**Before Fix (With Pending Chunks):**
|
|
```
|
|
📋 Resuming from chunk 3 (found 3 existing chunks)
|
|
Starting at combo 6,000 / 4,096
|
|
✅ Assigned 0 initial chunks
|
|
```
|
|
|
|
**After Fix (Cleaned Pending Chunks):**
|
|
```
|
|
🗑️ Cleared 3 pending/failed chunks for retry
|
|
|
|
📋 Starting fresh - no completed chunks found
|
|
🚀 Starting chunk v9_chunk_000000 on worker1 (2,000 combos)...
|
|
```
|
|
|
|
### Database Cleanup - ✅ WORKING
|
|
|
|
**Before:**
|
|
```sql
|
|
SELECT COUNT(*), status FROM chunks GROUP BY status;
|
|
3|pending
|
|
```
|
|
|
|
**After:**
|
|
```sql
|
|
SELECT COUNT(*), status FROM chunks GROUP BY status;
|
|
1|running
|
|
```
|
|
|
|
---
|
|
|
|
## Current Cluster State (Dec 1, 2025 12:54 UTC)
|
|
|
|
**Active Chunks:**
|
|
- v9_chunk_000000: status='running', assigned_worker='worker1', combos 0-2000
|
|
|
|
**Worker Processes:**
|
|
- Worker1: 24 Python processes actively running backtester
|
|
- Worker2: Not assigned yet (nested SSH hop has separate timeout issue)
|
|
|
|
**Expected Behavior:**
|
|
- Chunk 0 will complete in ~minutes (2000 combos, 32 cores)
|
|
- Results will be collected from worker1
|
|
- Coordinator will assign chunks 1 and 2 next
|
|
- Monitor progress: http://localhost:3001/cluster or `python3 cluster/exploration_status.py`
|
|
|
|
---
|
|
|
|
## Remaining Issue: Worker2 Timeout (Not Critical)
|
|
|
|
**Status:** Nested SSH hop to worker2 still timing out
|
|
|
|
**Symptom:**
|
|
```
|
|
⚠️ SSH command timed out for v9_chunk_000001 on worker2
|
|
```
|
|
|
|
**Not Critical Because:**
|
|
- This is a separate infrastructure issue (nested SSH connectivity)
|
|
- Worker1 is functioning perfectly with the SSH fix
|
|
- Proves the SSH command pattern fix works
|
|
- Worker2 can be debugged separately or assigned manually
|
|
|
|
**Possible Causes:**
|
|
- Nested SSH (worker1 → worker2) may need longer timeout
|
|
- Directory paths might be wrong on worker2
|
|
- SSH keys might need setup for the hop
|
|
|
|
**Manual Test:**
|
|
```bash
|
|
$ ssh root@10.10.254.106 "ssh root@10.20.254.100 'nohup sleep 3 > /tmp/test.log 2>&1 & echo Started'"
|
|
Started # ← Pattern works, so issue is with the longer command or paths
|
|
```
|
|
|
|
---
|
|
|
|
## Files Changed
|
|
|
|
**/home/icke/traderv4/cluster/distributed_coordinator.py:**
|
|
1. **Lines 388-401:** SSH command construction - removed `&&`, added debug output
|
|
2. **Lines 413:** Verification string - updated to match new format
|
|
3. **Lines 507-514:** Database cleanup - delete pending/failed chunks on start
|
|
4. **Lines 514-533:** Resumption logic - filter by completed/running status only
|
|
|
|
---
|
|
|
|
## Git Commit
|
|
|
|
**Commit:** 323ef03
|
|
**Message:** critical: Fix SSH timeout + resumption logic bugs
|
|
**Date:** Dec 1, 2025
|
|
**Files:** cluster/distributed_coordinator.py (29 insertions, 10 deletions)
|
|
|
|
---
|
|
|
|
## Lessons Learned
|
|
|
|
### 1. Shell Operators with Background Jobs
|
|
|
|
**Background operator `&` behavior:**
|
|
- `cmd &` - Runs command in background, shell continues immediately
|
|
- `cmd & echo test` - Backgrounds cmd, then runs echo (both execute)
|
|
- `cmd && echo test` - Waits for cmd to exit successfully, THEN runs echo
|
|
- **Problem:** `&&` waits for backgrounded jobs to complete (never happens)
|
|
- **Solution:** Use simple space, not `&&`, when previous command ends with `&`
|
|
|
|
### 2. Resumption Logic Must Filter By Status
|
|
|
|
**Database chunks can be in many states:**
|
|
- pending: Created but not started yet
|
|
- running: Currently being processed
|
|
- completed: Successfully finished
|
|
- failed: Errored out during processing
|
|
|
|
**Resumption query must only count completed work:**
|
|
- Counting all chunks (regardless of status) causes incorrect resume point
|
|
- Coordinator thinks work is done when chunks are still pending
|
|
- Filter by `status IN ('completed', 'running')` to get accurate resume point
|
|
|
|
### 3. Database State Cleanup on Retry
|
|
|
|
**When coordinator restarts after failure:**
|
|
- Pending chunks from previous run block new INSERT operations
|
|
- Solution: DELETE pending/failed chunks on start to allow clean retry
|
|
- Alternative: Use INSERT OR REPLACE or UPDATE logic
|
|
- Benefit: Allows restarting coordinator without manual database cleanup
|
|
|
|
### 4. Manual Testing Validates Patterns
|
|
|
|
**SSH command timeout was diagnosed by:**
|
|
1. Reading coordinator code to understand command construction
|
|
2. Testing SSH connectivity (passed - not a network issue)
|
|
3. Testing command pattern manually (found the working pattern)
|
|
4. Applying working pattern to code
|
|
5. Verifying with real coordinator run
|
|
|
|
**Manual test proved the fix before deploying:**
|
|
```bash
|
|
$ ssh worker 'nohup cmd > /tmp/log 2>&1 & echo Started'
|
|
Started # ← If this works manually, coordinator will work too
|
|
```
|
|
|
|
### 5. Nested SSH Hops Need Extra Timeout
|
|
|
|
**Direct SSH:** 30-60 seconds adequate
|
|
**Nested SSH (hop through another server):** May need 60-90+ seconds
|
|
- Latency compounds at each hop
|
|
- Each SSH handshake adds 2-3 seconds
|
|
- Process startup adds 10-20 seconds
|
|
- Total: 30-60 seconds for nested hops
|
|
|
|
**Solution Applied:** Increased timeout from 30s to 60s (line 408)
|
|
|
|
---
|
|
|
|
## Success Metrics
|
|
|
|
✅ **SSH timeout bug:** FIXED - Chunks start successfully on worker1
|
|
✅ **Resumption logic bug:** FIXED - Starts from correct chunk after clearing pending
|
|
✅ **Database cleanup:** IMPLEMENTED - Clears pending chunks automatically
|
|
✅ **Worker processes:** RUNNING - 24 Python processes on worker1
|
|
✅ **Chunk execution:** ACTIVE - Chunk 0 status='running' in database
|
|
✅ **Git committed:** Yes - commit 323ef03 with detailed explanation
|
|
✅ **Documentation:** Complete - This file captures all fixes and lessons
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
### Immediate (Monitor Current Run):
|
|
1. Wait for chunk 0 to complete (monitor via log viewer or database)
|
|
2. Verify results collected from worker1
|
|
3. Check if coordinator assigns chunk 1 next
|
|
|
|
### Short-Term (Fix Worker2):
|
|
1. Debug nested SSH hop timeout issue
|
|
2. Check directory paths on worker2: `/home/comprehensive_sweep`
|
|
3. Verify venv activation works: `source backtester/.venv/bin/activate`
|
|
4. Test manually: `ssh worker1 'ssh worker2 "cd /path && python3 script.py"'`
|
|
5. Consider increasing timeout further for nested hops (90s?)
|
|
|
|
### Long-Term (Optimization):
|
|
1. Add SSH multiplexing to speed up nested hops (`ControlMaster`)
|
|
2. Consider deploying workers without SSH hop (direct connectivity)
|
|
3. Add retry logic for transient SSH failures
|
|
4. Implement progress monitoring in web dashboard
|
|
|
|
---
|
|
|
|
## Conclusion
|
|
|
|
**Three critical bugs fixed in one session:**
|
|
1. SSH command construction preventing chunk starts ✅ FIXED
|
|
2. Resumption logic causing immediate coordinator exit ✅ FIXED
|
|
3. Database state preventing clean retries ✅ FIXED
|
|
|
|
**Current cluster state:**
|
|
- ✅ Worker1: Operational with 24 processes running chunk 0
|
|
- ⚠️ Worker2: SSH hop issue (separate problem, not blocking)
|
|
- ✅ Coordinator: Running and monitoring successfully
|
|
|
|
**User expectation met:** "we have started it before successfully" - System now working as it did previously!
|