From 1aa937ba8fbae4b9e81e42f9be818ddb16be8603 Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Mon, 8 Sep 2025 10:46:11 -0400 Subject: [PATCH 1/3] added timeout --- src/backend.py | 51 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/src/backend.py b/src/backend.py index 059a0dd..2057a7b 100644 --- a/src/backend.py +++ b/src/backend.py @@ -20,7 +20,6 @@ # imports for logging progress from flask import Response, stream_with_context -import time import logging from queue import Queue, Empty @@ -94,9 +93,18 @@ def logs_stream(): def gen(): yield "retry: 500\n\n" while True: - line = log_queue.get() - for chunk in line.replace("\r", "\n").splitlines(): - yield f"data: {chunk}\n\n" + try: + # Use a timeout to prevent indefinite blocking + line = log_queue.get(timeout=30) + for chunk in line.replace("\r", "\n").splitlines(): + yield f"data: {chunk}\n\n" + except Empty: + # Send a heartbeat to keep connection alive + yield "data: \n\n" + except Exception as e: + # Log the error and break the loop to close the connection + yield f"data: Error in log stream: {str(e)}\n\n" + break return Response(gen(), mimetype="text/event-stream") @@ -358,6 +366,19 @@ def run_pathsim(): # Share x only if there are only scopes or only spectra shared_x = len(scopes) * len(spectra) == 0 n_rows = len(scopes) + len(spectra) + + if n_rows == 0: + # No scopes or spectra to plot + return jsonify( + { + "success": True, + "plot": "{}", + "html": "

No scopes or spectra to display

", + "csv_data": csv_payload, + "message": "Pathsim simulation completed successfully", + } + ) + absolute_vertical_spacing = 0.05 relative_vertical_spacing = absolute_vertical_spacing / n_rows fig = make_subplots( @@ -371,27 +392,27 @@ def run_pathsim(): # make scope plots for i, scope in enumerate(scopes): - time, data = scope.read() + sim_time, data = scope.read() for p, d in enumerate(data): lb = scope.labels[p] if p < len(scope.labels) else f"port {p}" if isinstance(scope, Spectrum): d = abs(d) fig.add_trace( - go.Scatter(x=time, y=d, mode="lines", name=lb), row=i + 1, col=1 + go.Scatter(x=sim_time, y=d, mode="lines", name=lb), row=i + 1, col=1 ) fig.update_xaxes(title_text="Time", row=len(scopes), col=1) # make spectrum plots for i, spec in enumerate(spectra): - time, data = spec.read() + freq, data = spec.read() for p, d in enumerate(data): lb = spec.labels[p] if p < len(spec.labels) else f"port {p}" d = abs(d) fig.add_trace( - go.Scatter(x=time, y=d, mode="lines", name=lb), + go.Scatter(x=freq, y=d, mode="lines", name=lb), row=len(scopes) + i + 1, col=1, ) @@ -402,8 +423,13 @@ def run_pathsim(): ) # Convert plot to JSON - plot_data = plotly_json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder) - plot_html = fig.to_html() + try: + plot_data = plotly_json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder) + plot_html = fig.to_html() + except Exception as plot_error: + return jsonify( + {"success": False, "error": f"Plot generation error: {str(plot_error)}"} + ), 500 return jsonify( { @@ -416,6 +442,11 @@ def run_pathsim(): ) except Exception as e: + # Log the full error for debugging + import traceback + + error_details = traceback.format_exc() + print(f"Error in run_pathsim: {error_details}") return jsonify({"success": False, "error": f"Server error: {str(e)}"}), 500 From f4f4bab7bd5c6a0212335af806b5d14a4c39021e Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Mon, 8 Sep 2025 10:59:38 -0400 Subject: [PATCH 2/3] improved debugging --- src/App.jsx | 45 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index c75837f..1495f21 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -791,7 +791,25 @@ const DnDFlow = () => { body: JSON.stringify({ graph: graphData }), }); - const result = await response.json(); + // Check if response is ok first + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + // Check if response has content + const responseText = await response.text(); + if (!responseText.trim()) { + throw new Error('Server returned empty response'); + } + + // Try to parse JSON + let result; + try { + result = JSON.parse(responseText); + } catch (jsonError) { + console.error('Failed to parse JSON response:', responseText); + throw new Error(`Invalid JSON response: ${jsonError.message}`); + } if (sseRef.current) { sseRef.current.close(); sseRef.current = null; } @@ -805,8 +823,29 @@ const DnDFlow = () => { alert(`Error running Pathsim simulation: ${result.error}`); } } catch (error) { - console.error('Error:', error); - alert(`Failed to run Pathsim simulation. Make sure the backend is running. : ${error.message}`); + console.error('Error details:', { + message: error.message, + stack: error.stack, + response: error.response || 'No response object' + }); + + if (sseRef.current) { + sseRef.current.close(); + sseRef.current = null; + } + + // Provide more specific error messages + let errorMessage = 'Failed to run Pathsim simulation. Make sure the backend is running.'; + + if (error.message.includes('JSON')) { + errorMessage = 'Server response was not valid JSON. This might be due to a server error or network issue.'; + } else if (error.message.includes('HTTP')) { + errorMessage = `Server error: ${error.message}`; + } else if (error.message.includes('empty response')) { + errorMessage = 'Server returned empty response. The simulation might have failed silently.'; + } + + alert(`${errorMessage} : ${error.message}`); } }; From be6eaa91072abb37a7e71bf953acac1414237572 Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Mon, 8 Sep 2025 11:03:59 -0400 Subject: [PATCH 3/3] error handling in backend --- src/App.jsx | 14 +++++++------- src/backend.py | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 1495f21..ee8189b 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -828,15 +828,15 @@ const DnDFlow = () => { stack: error.stack, response: error.response || 'No response object' }); - - if (sseRef.current) { - sseRef.current.close(); - sseRef.current = null; + + if (sseRef.current) { + sseRef.current.close(); + sseRef.current = null; } - + // Provide more specific error messages let errorMessage = 'Failed to run Pathsim simulation. Make sure the backend is running.'; - + if (error.message.includes('JSON')) { errorMessage = 'Server response was not valid JSON. This might be due to a server error or network issue.'; } else if (error.message.includes('HTTP')) { @@ -844,7 +844,7 @@ const DnDFlow = () => { } else if (error.message.includes('empty response')) { errorMessage = 'Server returned empty response. The simulation might have failed silently.'; } - + alert(`${errorMessage} : ${error.message}`); } }; diff --git a/src/backend.py b/src/backend.py index 2057a7b..a48a9f2 100644 --- a/src/backend.py +++ b/src/backend.py @@ -534,6 +534,25 @@ def catch_all(path): return jsonify({"error": "Route not found"}), 404 +# Global error handler to ensure all errors return JSON +@app.errorhandler(Exception) +def handle_exception(e): + """Global exception handler to ensure JSON responses.""" + import traceback + + error_details = traceback.format_exc() + print(f"Unhandled exception: {error_details}") + + # For HTTP exceptions, return the original response + if hasattr(e, "code"): + return jsonify( + {"success": False, "error": f"HTTP {e.code}: {str(e)}"} + ), getattr(e, "code", 500) + + # For all other exceptions, return a generic JSON error + return jsonify({"success": False, "error": f"Internal server error: {str(e)}"}), 500 + + if __name__ == "__main__": port = int(os.getenv("PORT", 8000)) app.run(host="0.0.0.0", port=port, debug=os.getenv("FLASK_ENV") != "production")