Fix polling slowness by removing multiprocessing
parent
815f2d0d35
commit
dcc8bb9636
4
main.py
4
main.py
|
|
@ -82,8 +82,8 @@ if __name__ == "__main__":
|
||||||
raise SystemExit(main())
|
raise SystemExit(main())
|
||||||
|
|
||||||
c = PyClient([
|
c = PyClient([
|
||||||
"http://localhost:5001",
|
"http://127.0.0.1:5001",
|
||||||
"http://localhost:5002"
|
"http://127.0.0.1:5002"
|
||||||
], debug=True)
|
], debug=True)
|
||||||
|
|
||||||
out = c.play_game("/tic-tac-toe", TicTacToe)
|
out = c.play_game("/tic-tac-toe", TicTacToe)
|
||||||
|
|
|
||||||
|
|
@ -1,63 +1,9 @@
|
||||||
"""Discovery and polling helpers for Bot-Man-Toe servers."""
|
"""Discovery and polling helpers for Bot-Man-Toe servers."""
|
||||||
|
|
||||||
import multiprocessing
|
|
||||||
from queue import Empty
|
|
||||||
from typing import Any, Dict, Optional, Tuple, Union
|
from typing import Any, Dict, Optional, Tuple, Union
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
import time
|
||||||
|
|
||||||
def _poll_worker(url: str, payload: Dict[str, Any], timeout: Optional[Union[float, Tuple[float, float]]], queue: multiprocessing.Queue) -> None:
|
|
||||||
"""Run a polling request in a separate process."""
|
|
||||||
try:
|
|
||||||
response = requests.get(url, json=payload, timeout=timeout)
|
|
||||||
response.raise_for_status()
|
|
||||||
content = response.json()
|
|
||||||
if isinstance(content, dict):
|
|
||||||
queue.put(("ok", content))
|
|
||||||
return
|
|
||||||
except requests.exceptions.RequestException:
|
|
||||||
queue.put(("request_error", None))
|
|
||||||
return
|
|
||||||
except ValueError:
|
|
||||||
queue.put(("invalid", None))
|
|
||||||
return
|
|
||||||
|
|
||||||
queue.put(("invalid", None))
|
|
||||||
|
|
||||||
|
|
||||||
def _poll_request(
|
|
||||||
url: str,
|
|
||||||
payload: Dict[str, Any],
|
|
||||||
request_timeout: Optional[Union[float, Tuple[float, float]]],
|
|
||||||
deadline: float,
|
|
||||||
) -> tuple[str, Optional[Dict[str, Any]]]:
|
|
||||||
"""Execute a bounded JSON request and classify the result."""
|
|
||||||
context = multiprocessing.get_context("spawn")
|
|
||||||
queue: multiprocessing.Queue = context.Queue(maxsize=1)
|
|
||||||
process = context.Process(target=_poll_worker, args=(url, payload, request_timeout, queue))
|
|
||||||
process.daemon = True
|
|
||||||
process.start()
|
|
||||||
|
|
||||||
process.join(deadline)
|
|
||||||
if process.is_alive():
|
|
||||||
process.terminate()
|
|
||||||
process.join()
|
|
||||||
queue.close()
|
|
||||||
queue.join_thread()
|
|
||||||
return ("timeout", None)
|
|
||||||
|
|
||||||
try:
|
|
||||||
status, content = queue.get(timeout=0.1)
|
|
||||||
except Empty:
|
|
||||||
queue.close()
|
|
||||||
queue.join_thread()
|
|
||||||
return ("invalid", None)
|
|
||||||
|
|
||||||
queue.close()
|
|
||||||
queue.join_thread()
|
|
||||||
return status, content
|
|
||||||
|
|
||||||
|
|
||||||
class ServerAgent:
|
class ServerAgent:
|
||||||
"""Representation of a server that can host one or more games."""
|
"""Representation of a server that can host one or more games."""
|
||||||
|
|
@ -104,19 +50,11 @@ class ServerAgent:
|
||||||
:raises ValueError: If the response body is not a JSON object or if the
|
:raises ValueError: If the response body is not a JSON object or if the
|
||||||
payload contains malformed discovery fields.
|
payload contains malformed discovery fields.
|
||||||
"""
|
"""
|
||||||
if timeout is None:
|
response = requests.get(url.rstrip("/") + "/", timeout=timeout)
|
||||||
deadline = 10.0
|
response.raise_for_status()
|
||||||
elif isinstance(timeout, (int, float)):
|
content = response.json()
|
||||||
deadline = float(timeout)
|
|
||||||
else:
|
|
||||||
deadline = sum(timeout)
|
|
||||||
status, content = _poll_request(url.rstrip("/") + "/", {}, timeout, deadline)
|
|
||||||
|
|
||||||
if status == "timeout":
|
if not isinstance(content, dict):
|
||||||
raise requests.exceptions.RequestException("Server discovery request timed out.")
|
|
||||||
if status == "request_error":
|
|
||||||
raise requests.exceptions.RequestException("Server discovery request failed.")
|
|
||||||
if status != "ok" or content is None:
|
|
||||||
raise ValueError("Server discovery responses must be JSON objects.")
|
raise ValueError("Server discovery responses must be JSON objects.")
|
||||||
|
|
||||||
raw_name = content.get("name", "")
|
raw_name = content.get("name", "")
|
||||||
|
|
@ -150,13 +88,15 @@ class ServerAgent:
|
||||||
"""
|
"""
|
||||||
url = f"{self.url.rstrip('/')}/{game.lstrip('/')}"
|
url = f"{self.url.rstrip('/')}/{game.lstrip('/')}"
|
||||||
|
|
||||||
status, content = _poll_request(url, payload, timeout, 3.0)
|
try:
|
||||||
|
response = requests.get(url, json=payload, timeout=timeout)
|
||||||
|
response.raise_for_status()
|
||||||
|
content = response.json()
|
||||||
|
except (requests.exceptions.RequestException, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
if self.debug:
|
if self.debug:
|
||||||
print(f"[DBG] Agent `{self.name}` returned:")
|
print(f"[DBG] Agent `{self.name}` returned:")
|
||||||
print(( status, content ))
|
print(content)
|
||||||
|
|
||||||
if status != "ok":
|
return content if isinstance(content, dict) else None
|
||||||
return None
|
|
||||||
|
|
||||||
return content
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue