|
24 | 24 | from pathlib import Path |
25 | 25 | from typing import TYPE_CHECKING, Any, Literal, TypeVar |
26 | 26 |
|
| 27 | +import psutil |
| 28 | + |
27 | 29 | import reflex |
28 | 30 | import reflex.reflex |
29 | 31 | import reflex.utils.exec |
@@ -103,24 +105,6 @@ class ReflexProcessExitNonZeroError(RuntimeError): |
103 | 105 | """Exception raised when the reflex process exits with a non-zero status.""" |
104 | 106 |
|
105 | 107 |
|
106 | | -def _is_port_responsive(port: int) -> bool: |
107 | | - """Check if a port is responsive. |
108 | | -
|
109 | | - Args: |
110 | | - port: the port to check |
111 | | -
|
112 | | - Returns: |
113 | | - True if the port is responsive, False otherwise |
114 | | - """ |
115 | | - try: |
116 | | - with contextlib.closing( |
117 | | - socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
118 | | - ) as sock: |
119 | | - return sock.connect_ex(("127.0.0.1", port)) == 0 |
120 | | - except (OverflowError, PermissionError, OSError): |
121 | | - return False |
122 | | - |
123 | | - |
124 | 108 | @dataclasses.dataclass |
125 | 109 | class AppHarness: |
126 | 110 | """AppHarness executes a reflex app in-process for testing.""" |
@@ -382,28 +366,29 @@ def _wait_for_servers(self, backend: bool, frontend: bool): |
382 | 366 | msg = "Reflex process has no pid." |
383 | 367 | raise RuntimeError(msg) |
384 | 368 |
|
385 | | - if backend and self.backend_port is None: |
386 | | - msg = "Backend port is not set." |
387 | | - raise RuntimeError(msg) |
388 | | - |
389 | | - if frontend and self.frontend_port is None: |
390 | | - msg = "Frontend port is not set." |
391 | | - raise RuntimeError(msg) |
392 | | - |
393 | 369 | frontend_ready = False |
394 | 370 | backend_ready = False |
| 371 | + timeout = 30 |
| 372 | + start_time = time.time() |
395 | 373 |
|
| 374 | + process = psutil.Process(self.reflex_process.pid) |
396 | 375 | while not ((not frontend or frontend_ready) and (not backend or backend_ready)): |
397 | | - if backend and self.backend_port and _is_port_responsive(self.backend_port): |
398 | | - backend_ready = True |
399 | | - if ( |
400 | | - frontend |
401 | | - and self.frontend_port |
402 | | - and _is_port_responsive(self.frontend_port) |
403 | | - ): |
404 | | - frontend_ready = True |
405 | | - self.frontend_url = f"http://localhost:{self.frontend_port}/" |
406 | | - time.sleep(POLL_INTERVAL) |
| 376 | + if time.time() - start_time > timeout: |
| 377 | + msg = f"Timeout waiting for servers. Frontend ready: {frontend_ready}, Backend ready: {backend_ready}" |
| 378 | + raise RuntimeError(msg) |
| 379 | + |
| 380 | + for proc in process.children(recursive=True): |
| 381 | + with contextlib.suppress(psutil.NoSuchProcess, psutil.AccessDenied): |
| 382 | + if ncs := proc.net_connections(): |
| 383 | + for net_conn in ncs: |
| 384 | + if net_conn.status == psutil.CONN_LISTEN: |
| 385 | + if net_conn.laddr.port == self.frontend_port: |
| 386 | + frontend_ready = True |
| 387 | + self.frontend_url = ( |
| 388 | + f"http://localhost:{self.frontend_port}/" |
| 389 | + ) |
| 390 | + elif net_conn.laddr.port == self.backend_port: |
| 391 | + backend_ready = True |
407 | 392 |
|
408 | 393 | async def _reset_backend_state_manager(self): |
409 | 394 | """Reset the StateManagerRedis event loop affinity. |
|
0 commit comments