Strategická: Lepší chybové chování
This commit is contained in:
parent
1808d89e14
commit
642f4389af
1 changed files with 88 additions and 72 deletions
160
klient/client.py
160
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)
|
||||||
|
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__":
|
||||||
|
|
Loading…
Reference in a new issue