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

This commit is contained in:
David Klement 2022-09-18 14:17:30 +02:00
parent 1808d89e14
commit 642f4389af

View file

@ -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)
sys.exit(1)
state = r.json() state = res.json()
# also retry if the server is not willing to give us the state yet if state["status"] == "ok":
if state["status"] == "waiting": return state, 0
logger.info("Server is busy.")
return None, state["wait"] # retry after some time
if state["status"] == "too_early": if state["status"] == "waiting":
logger.info("Round didn't start yet.") 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={
"game": args.game,
"token": args.token,
"round": round
},
json=turn
)
except requests.exceptions.RequestException as e:
logger.warning(f"Request error: {e}")
return False
if not r.ok:
logger.warning(f"Server error: {r.status_code} {r.reason}")
return False
# print errors try:
# because such errors are caused by the submitted turn, res = requests.post(
# retrying will not help, so return True f"{args.server}/api/action",
response = r.json() params={
if response["status"] == "ok": "game": args.game,
logger.info("Turn accepted.") "token": args.token,
elif response["status"] == "too_late": "round": round
logger.error("Turn submitted too late.") },
elif response["status"] == "error": json=turn
logger.error(f"Turn error: {response['description']}") )
elif response["status"] == "warning": except requests.exceptions.RequestException as e:
member_warns = [ # server might be down, retry later
f" {member['id']}: {member['description']}" logger.warning(f"Request error: {e}")
for member in response["members"] return False
] if not res.ok:
logger.warn("Member warnings:\n" + "\n".join(member_warns)) log_server_error(res)
sys.exit(1)
return True # print errors
# because such errors are caused by the submitted turn,
# retrying will not help, so return True
response = res.json()
if response["status"] == "ok":
logger.info("Turn accepted.")
elif response["status"] == "too_late":
logger.error("Turn submitted too late.")
elif response["status"] == "error":
logger.error(f"Turn error: {response['description']}")
elif response["status"] == "warning":
member_warns = [
f" {member['id']}: {member['description']}"
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__":