Browse Source

Strategická: Lepší chybové chování

master
David Klement 2 years ago
parent
commit
642f4389af
  1. 166
      klient/client.py

166
klient/client.py

@ -4,6 +4,7 @@ import json
import logging import logging
import requests import requests
import shutil import shutil
import sys
import tempfile import tempfile
import time import time
from subprocess import Popen, PIPE, TimeoutExpired from subprocess import Popen, PIPE, TimeoutExpired
@ -36,12 +37,7 @@ def main(args: argparse.Namespace) -> None:
min_round = 0 min_round = 0
while True: while True:
state, wait_time = get_state(min_round, args) state = get_state(min_round, args)
if state is None:
# retry later
time.sleep(wait_time)
continue
round = state["round"] round = state["round"]
min_round = round + 1 min_round = round + 1
# if requested, dump state to file instead # if requested, dump state to file instead
@ -59,16 +55,14 @@ def main(args: argparse.Namespace) -> None:
turn = json.loads(turn_json) turn = json.loads(turn_json)
except json.JSONDecodeError: except json.JSONDecodeError:
logger.error(f"Invalid JSON returned by strategy code.") logger.error(f"Invalid JSON returned by strategy code.")
continue sys.exit(1)
while not send_turn(turn, round, args): send_turn(turn, round, args)
# if there was a connection error, retry later
time.sleep(TIME_BEFORE_RETRY)
def run_subprocess( def run_subprocess(
program: str, state_json: str, timeout: Optional[float] program: str, state_json: str, timeout: Optional[float]
) -> Optional[str]: ) -> Optional[str]:
"""Run user program and return its output.""" """Run user program and return its output."""
proc = Popen([program], encoding="utf-8", proc = Popen([program], encoding="utf-8",
@ -83,82 +77,104 @@ def run_subprocess(
logger.error(f"Strategy code stderr:\n{stderr}") logger.error(f"Strategy code stderr:\n{stderr}")
if proc.returncode != 0: if proc.returncode != 0:
logger.error("Strategy code exited with non-zero exit code.") logger.error("Strategy code exited with non-zero exit code.")
return None sys.exit(1)
return stdout return stdout
def get_state(min_round: int, args) -> Tuple[Optional[dict], float]: def get_state(min_round: int, args) -> dict:
"""Get game data from the server. """Iteratively try to get game data from the server."""
Returns None and wait time if there was an error.""" state, wait_time = get_state_once()
while state is None:
time.sleep(wait_time)
state, wait_time = get_state_once()
try: def get_state_once() -> Tuple[Optional[dict], float]:
r = requests.get(f"{args.server}/api/state", params={ try:
"game": args.game, res = requests.get(f"{args.server}/api/state", params={
"token": args.token, "game": args.game,
"min_round": min_round "token": args.token,
}) "min_round": min_round
# retry later if there was an error })
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
logger.warning(f"Request error: {e}") # server might be down, retry later
return None, TIME_BEFORE_RETRY logger.warning(f"Request error: {e}")
if not r.ok: return None, TIME_BEFORE_RETRY
logger.warning(f"Server error: {r.status_code} {r.reason}") if not res.ok:
return None, TIME_BEFORE_RETRY logger.error(f"Server error: {res.status_code} {res.reason}")
log_server_error(res)
state = r.json() sys.exit(1)
# also retry if the server is not willing to give us the state yet
if state["status"] == "waiting": state = res.json()
logger.info("Server is busy.") if state["status"] == "ok":
return None, state["wait"] return state, 0
if state["status"] == "too_early":
logger.info("Round didn't start yet.") # retry after some time
if state["status"] == "waiting":
logger.info("Server is busy.")
if state["status"] == "too_early":
logger.info("Round didn't start yet.")
return None, state["wait"] return None, state["wait"]
logger.info("Received new state.") logger.info("Received new state.")
return state, 0 return state, 0
def send_turn(turn: dict, round: int, args) -> bool: def send_turn(turn: dict, round: int, args) -> None:
"""Send turn to the server. """Iteratively try to send turn to the server."""
Returns True if the server received the request.""" while not send_turn_once():
time.sleep(TIME_BEFORE_RETRY)
try: def send_turn_once() -> bool:
r = requests.post( """Returns True if server answered."""
f"{args.server}/api/action",
params={ try:
"game": args.game, res = requests.post(
"token": args.token, f"{args.server}/api/action",
"round": round params={
}, "game": args.game,
json=turn "token": args.token,
) "round": round
except requests.exceptions.RequestException as e: },
logger.warning(f"Request error: {e}") json=turn
return False )
if not r.ok: except requests.exceptions.RequestException as e:
logger.warning(f"Server error: {r.status_code} {r.reason}") # server might be down, retry later
return False logger.warning(f"Request error: {e}")
return False
# print errors if not res.ok:
# because such errors are caused by the submitted turn, log_server_error(res)
# retrying will not help, so return True sys.exit(1)
response = r.json()
if response["status"] == "ok": # print errors
logger.info("Turn accepted.") # because such errors are caused by the submitted turn,
elif response["status"] == "too_late": # retrying will not help, so return True
logger.error("Turn submitted too late.") response = res.json()
elif response["status"] == "error": if response["status"] == "ok":
logger.error(f"Turn error: {response['description']}") logger.info("Turn accepted.")
elif response["status"] == "warning": elif response["status"] == "too_late":
member_warns = [ logger.error("Turn submitted too late.")
f" {member['id']}: {member['description']}" elif response["status"] == "error":
for member in response["members"] logger.error(f"Turn error: {response['description']}")
] elif response["status"] == "warning":
logger.warn("Member warnings:\n" + "\n".join(member_warns)) member_warns = [
f" {member['id']}: {member['description']}"
return True for member in response["members"]
]
logger.warning(
"Member warnings:\n"
"\n".join(member_warns)
)
return True
def log_server_error(res: requests.Response) -> None:
logger.error(
f"Server error: {res.status_code} {res.reason}\n"
f"{res['description']}"
)
if __name__ == "__main__": if __name__ == "__main__":

Loading…
Cancel
Save