Skip to content

Commit 95e6681

Browse files
committed
fix(indexing): WebSocket race condition, NaN stats, file streaming
Fixes: - Add delay before WS close to ensure client receives completion event - Align stat field names between backend and frontend - Add current_file to progress events for streaming display - Preserve completed state when WebSocket disconnects Root cause: Backend closed WebSocket immediately after sending completion event, causing race condition where frontend's onclose fired before onmessage could process the completion. Closes #154
1 parent d2f4527 commit 95e6681

4 files changed

Lines changed: 33 additions & 11 deletions

File tree

backend/routes/ws_playground.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@ async def websocket_playground_index(websocket: WebSocket, job_id: str):
152152
job_id=job_id[:12],
153153
event_type=event_type
154154
)
155+
# Small delay to ensure client processes message before close
156+
# This prevents race condition where onclose fires before onmessage
157+
await asyncio.sleep(0.2)
155158
break
156159

157160
except json.JSONDecodeError:

backend/services/anonymous_indexer.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ def to_dict(self) -> dict:
4343
@dataclass
4444
class JobStats:
4545
"""Final stats for completed job."""
46-
files_indexed: int = 0
47-
functions_found: int = 0
48-
time_taken_seconds: float = 0
46+
files_processed: int = 0
47+
functions_indexed: int = 0
48+
indexing_time_seconds: float = 0
4949

5050
def to_dict(self) -> dict:
5151
return asdict(self)
@@ -345,12 +345,18 @@ async def run_indexing_job(
345345
job_manager.update_status(job_id, JobStatus.PROCESSING)
346346

347347
# Progress callback for real-time updates
348-
async def progress_callback(files_processed: int, functions_found: int, total: int):
348+
async def progress_callback(
349+
files_processed: int,
350+
functions_found: int,
351+
total: int,
352+
current_file: Optional[str] = None
353+
):
349354
job_manager.update_progress(
350355
job_id,
351356
files_processed=files_processed,
352357
functions_found=functions_found,
353-
files_total=total
358+
files_total=total,
359+
current_file=current_file
354360
)
355361

356362
# Run indexing with timeout
@@ -370,9 +376,9 @@ async def progress_callback(files_processed: int, functions_found: int, total: i
370376
# --- Step 3: Mark complete ---
371377
elapsed = time.time() - start_time
372378
stats = JobStats(
373-
files_indexed=file_count,
374-
functions_found=total_functions,
375-
time_taken_seconds=round(elapsed, 2)
379+
files_processed=file_count,
380+
functions_indexed=total_functions,
381+
indexing_time_seconds=round(elapsed, 2)
376382
)
377383

378384
job_manager.update_status(

backend/services/indexer_optimized.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -672,8 +672,11 @@ async def index_repository_with_progress(
672672

673673
files_processed = min(i + self.FILE_BATCH_SIZE, total_files)
674674

675-
# Send progress update
676-
await progress_callback(files_processed, len(all_functions_data), total_files)
675+
# Get the last file in this batch for display
676+
current_file = batch[-1].name if batch else None
677+
678+
# Send progress update with current file
679+
await progress_callback(files_processed, len(all_functions_data), total_files, current_file)
677680

678681
logger.debug("Processing files",
679682
processed=files_processed,

frontend/src/hooks/useIndexingWebSocket.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,18 @@ export function useIndexingWebSocket(
248248
if (jobId) {
249249
connect(jobId);
250250
} else {
251+
// Only cleanup connection, DON'T reset state!
252+
// This preserves completedStats when jobId becomes null after completion
251253
cleanup();
252-
setState(INITIAL_STATE);
254+
// Only reset if we were never completed (e.g., user navigated away during indexing)
255+
setState(prev => {
256+
if (prev.phase === 'completed') {
257+
// Keep completed state - just disconnect
258+
return { ...prev, connectionState: 'disconnected' };
259+
}
260+
// Reset if we were mid-indexing (user cancelled, navigated away, etc.)
261+
return INITIAL_STATE;
262+
});
253263
}
254264
return cleanup;
255265
}, [jobId, connect, cleanup]);

0 commit comments

Comments
 (0)