Fix server initialization race condition with proper event signaling
This commit is contained in:
@@ -61,25 +61,34 @@ def start_embedded_server() -> tuple[SimulationServer, threading.Thread]:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
server_ready = threading.Event()
|
||||||
|
|
||||||
def run_server() -> None:
|
def run_server() -> None:
|
||||||
"""Run the async server in a new event loop."""
|
"""Run the async server in a new event loop."""
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
asyncio.set_event_loop(loop)
|
asyncio.set_event_loop(loop)
|
||||||
try:
|
try:
|
||||||
loop.run_until_complete(server.start())
|
loop.run_until_complete(server.start())
|
||||||
|
# Signal that server is ready
|
||||||
|
server_ready.set()
|
||||||
# Keep the event loop running
|
# Keep the event loop running
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
st.error(f"Server error: {e}")
|
st.error(f"Server error: {e}")
|
||||||
|
server_ready.set() # Signal even on error
|
||||||
finally:
|
finally:
|
||||||
loop.run_until_complete(server.stop())
|
try:
|
||||||
|
loop.run_until_complete(server.stop())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
loop.close()
|
loop.close()
|
||||||
|
|
||||||
thread = threading.Thread(target=run_server, daemon=True)
|
thread = threading.Thread(target=run_server, daemon=True)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
# Wait a moment for server to start
|
# Wait for server to be fully started (up to 5 seconds)
|
||||||
time.sleep(0.5)
|
if not server_ready.wait(timeout=5.0):
|
||||||
|
st.error("Server failed to start within timeout")
|
||||||
|
|
||||||
return server, thread
|
return server, thread
|
||||||
|
|
||||||
@@ -87,13 +96,22 @@ def start_embedded_server() -> tuple[SimulationServer, threading.Thread]:
|
|||||||
def init_session_state() -> None:
|
def init_session_state() -> None:
|
||||||
"""Initialise Streamlit session state."""
|
"""Initialise Streamlit session state."""
|
||||||
if "server" not in st.session_state:
|
if "server" not in st.session_state:
|
||||||
st.session_state.server, st.session_state.server_thread = start_embedded_server()
|
with st.spinner("Starting simulation server..."):
|
||||||
|
st.session_state.server, st.session_state.server_thread = start_embedded_server()
|
||||||
|
|
||||||
|
# Verify server started correctly
|
||||||
|
if st.session_state.server.physics_engine is None:
|
||||||
|
st.error("Failed to start simulation server. Please refresh the page.")
|
||||||
|
st.stop()
|
||||||
|
|
||||||
# Register cleanup
|
# Register cleanup
|
||||||
def cleanup() -> None:
|
def cleanup() -> None:
|
||||||
if hasattr(st.session_state, "server") and st.session_state.server is not None:
|
if hasattr(st.session_state, "server") and st.session_state.server is not None:
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
loop.run_until_complete(st.session_state.server.stop())
|
try:
|
||||||
|
loop.run_until_complete(st.session_state.server.stop())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
loop.close()
|
loop.close()
|
||||||
atexit.register(cleanup)
|
atexit.register(cleanup)
|
||||||
|
|
||||||
@@ -308,7 +326,24 @@ def display_controls() -> None:
|
|||||||
@st.fragment(run_every=0.1)
|
@st.fragment(run_every=0.1)
|
||||||
def simulation_display() -> None:
|
def simulation_display() -> None:
|
||||||
"""Fragment that displays and updates simulation state."""
|
"""Fragment that displays and updates simulation state."""
|
||||||
|
# Check if server is initialized
|
||||||
|
if "server" not in st.session_state:
|
||||||
|
st.warning("⏳ Initializing simulation server...")
|
||||||
|
return
|
||||||
|
|
||||||
server: SimulationServer = st.session_state.server
|
server: SimulationServer = st.session_state.server
|
||||||
|
|
||||||
|
# Get current state from physics engine (for visualization)
|
||||||
|
engine: PhysicsEngine | None = server.physics_engine
|
||||||
|
if engine is None:
|
||||||
|
st.error("❌ Physics engine not available. The server may not have started correctly. Try refreshing the page.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if server is running
|
||||||
|
if not server.is_running:
|
||||||
|
st.warning("⚠️ Server is not running. Try refreshing the page.")
|
||||||
|
return
|
||||||
|
|
||||||
instruments: InstrumentSet = st.session_state.instruments
|
instruments: InstrumentSet = st.session_state.instruments
|
||||||
history: SimulationHistory = st.session_state.history
|
history: SimulationHistory = st.session_state.history
|
||||||
|
|
||||||
@@ -319,12 +354,6 @@ def simulation_display() -> None:
|
|||||||
if st.session_state.running:
|
if st.session_state.running:
|
||||||
step_simulation()
|
step_simulation()
|
||||||
|
|
||||||
# Get current state from physics engine (for visualization)
|
|
||||||
engine: PhysicsEngine | None = server.physics_engine
|
|
||||||
if engine is None:
|
|
||||||
st.error("Physics engine not available")
|
|
||||||
return
|
|
||||||
|
|
||||||
thermal = engine.get_thermal_state()
|
thermal = engine.get_thermal_state()
|
||||||
electrical = engine.get_electrical_state()
|
electrical = engine.get_electrical_state()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user