Browse Source

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

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

80
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,34 +77,41 @@ 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()
def get_state_once() -> Tuple[Optional[dict], float]:
try: try:
r = requests.get(f"{args.server}/api/state", params={ res = requests.get(f"{args.server}/api/state", params={
"game": args.game, "game": args.game,
"token": args.token, "token": args.token,
"min_round": min_round "min_round": min_round
}) })
# retry later if there was an error
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
# server might be down, retry later
logger.warning(f"Request error: {e}") logger.warning(f"Request error: {e}")
return None, TIME_BEFORE_RETRY return None, TIME_BEFORE_RETRY
if not r.ok: if not res.ok:
logger.warning(f"Server error: {r.status_code} {r.reason}") logger.error(f"Server error: {res.status_code} {res.reason}")
return None, TIME_BEFORE_RETRY log_server_error(res)
sys.exit(1)
state = res.json()
if state["status"] == "ok":
return state, 0
state = r.json() # retry after some time
# also retry if the server is not willing to give us the state yet
if state["status"] == "waiting": if state["status"] == "waiting":
logger.info("Server is busy.") logger.info("Server is busy.")
return None, state["wait"]
if state["status"] == "too_early": if state["status"] == "too_early":
logger.info("Round didn't start yet.") logger.info("Round didn't start yet.")
return None, state["wait"] return None, state["wait"]
@ -119,13 +120,17 @@ def get_state(min_round: int, args) -> Tuple[Optional[dict], float]:
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)
def send_turn_once() -> bool:
"""Returns True if server answered."""
try: try:
r = requests.post( res = requests.post(
f"{args.server}/api/action", f"{args.server}/api/action",
params={ params={
"game": args.game, "game": args.game,
@ -135,16 +140,17 @@ def send_turn(turn: dict, round: int, args) -> bool:
json=turn json=turn
) )
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
# server might be down, retry later
logger.warning(f"Request error: {e}") logger.warning(f"Request error: {e}")
return False return False
if not r.ok: if not res.ok:
logger.warning(f"Server error: {r.status_code} {r.reason}") log_server_error(res)
return False sys.exit(1)
# print errors # print errors
# because such errors are caused by the submitted turn, # because such errors are caused by the submitted turn,
# retrying will not help, so return True # retrying will not help, so return True
response = r.json() response = res.json()
if response["status"] == "ok": if response["status"] == "ok":
logger.info("Turn accepted.") logger.info("Turn accepted.")
elif response["status"] == "too_late": elif response["status"] == "too_late":
@ -156,11 +162,21 @@ def send_turn(turn: dict, round: int, args) -> bool:
f" {member['id']}: {member['description']}" f" {member['id']}: {member['description']}"
for member in response["members"] for member in response["members"]
] ]
logger.warn("Member warnings:\n" + "\n".join(member_warns)) logger.warning(
"Member warnings:\n"
"\n".join(member_warns)
)
return True 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__":
args = parser.parse_args() args = parser.parse_args()
main(args) main(args)

Loading…
Cancel
Save