Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve performance of running games by about 45% in nodocker mode #147

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions battlecode-manager/battlecode_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from player_sandboxed import SandboxedPlayer
import server
import battlecode as bc
import threading

try:
import ujson as json
except:
Expand Down Expand Up @@ -148,9 +150,12 @@ def cleanup(dockers, args, sock_file):
'''
Clean up that needs to be done at the end of a game
'''
for player_key in dockers:
docker_inst = dockers[player_key]
docker_inst.destroy()
threads = [threading.Thread(target=dockers[key].destroy) for key in dockers]
for t in threads:
t.start()

for t in threads:
t.join()

if isinstance(sock_file, str) or isinstance(sock_file, bytes):
# only unlink unix sockets
Expand Down
3 changes: 3 additions & 0 deletions battlecode-manager/player_abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ def _detect_platform(self):
else:
raise Exception("Unknown os: " + sys.platform)

def refreshProcessChildren(self):
pass

def guess_language(self):
return "?"

Expand Down
28 changes: 19 additions & 9 deletions battlecode-manager/player_plain.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@ def __init__(self, socket_file, working_dir, local_dir=None,
self.paused = False
self.streaming = False
self.process = None
self.recursiveProcs = []
self.nonRecursiveProcs = []

super().__init__(socket_file, working_dir, local_dir, None, None, player_key, player_mem_limit, player_cpu)

def refreshProcessChildren(self):
self.recursiveProcs = self.process.children(recursive=True)
self.nonRecursiveProcs = self.process.children(recursive=False)

def stream_logs(self, stdout=True, stderr=True, line_action=lambda line: print(line.decode())):
assert not self.streaming
self.streaming = True
Expand Down Expand Up @@ -72,16 +78,23 @@ def guess_language(self):

def pause(self):
# pausing too slow on windows
if sys.platform == 'win32': return
if sys.platform == 'win32':
return

if not self.paused:
self.paused = True
suspend(self.process)
suspend(self.nonRecursiveProcs)

def unpause(self, timeout=None):
# pausing too slow on windows
if sys.platform == 'win32': return
if sys.platform == 'win32':
return

# This assert should ideally be tested for Java and Python too, but I think it should hold unless the player goes out of its way to defeat the pausing mechanism
# assert(len(self.recursiveProcs) == len(self.process.children(recursive=True)))

if self.paused:
resume(self.process)
resume(self.recursiveProcs)
self.paused = False

def destroy(self):
Expand Down Expand Up @@ -121,9 +134,7 @@ def on_terminate(proc):
except:
print("Killing failed; assuming process exited early.")

def suspend(process):

procs = process.children(recursive=False)
def suspend(procs):
# to enterprising players reading this code:
# yes, it is possible to escape the pausing using e.g. `nohup` when running without docker.
# however, that won't work while running inside docker. Sorry.
Expand All @@ -137,8 +148,7 @@ def suspend(process):
except:
pass

def resume(process):
procs = process.children(recursive=True)
def resume(procs):
for p in procs:
try:
p.resume()
Expand Down
52 changes: 23 additions & 29 deletions battlecode-manager/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,6 @@ def end_turn(self):
# print(line)

if self.extra_delay:
import time
time.sleep(self.extra_delay / 1000.)

# Increment to the next player
Expand Down Expand Up @@ -340,6 +339,7 @@ def __init__(self, *args, **kwargs):
self.error = ""
self.logged_in = False
self.is_unix_stream = is_unix_stream

super(ReceiveHandler, self).__init__(*args, **kwargs)

def get_next_message(self) -> object:
Expand All @@ -352,20 +352,16 @@ def get_next_message(self) -> object:
loaded string
'''

recv_socket = self.request
game = self.game

wrapped_socket = recv_socket.makefile('rwb', 1)
logging.debug("Client %s: Waiting for next message", self.client_id)
try:
data = next(wrapped_socket)
data = next(self.wrapped_socket)
except (StopIteration, IOError):
print("{} has not sent message for {} seconds, assuming they're dead".format(
self.game.get_player(self.client_id)['player'],
TIMEOUT
))
wrapped_socket.close()
recv_socket.close()
self.wrapped_socket.close()
self.request.close()
if bc.Team.Red == self.game.get_player(self.client_id)['player'].team:
self.game.winner = 'player2'
elif bc.Team.Blue == self.game.get_player(self.client_id)['player'].team:
Expand All @@ -377,8 +373,8 @@ def get_next_message(self) -> object:
self.game.game_over = True
raise TimeoutError()
except KeyboardInterrupt:
wrapped_socket.close()
recv_socket.close()
self.wrapped_socket.close()
self.request.close()
if bc.Team.Red == self.game.get_player(self.client_id)['player'].team:
self.game.winner = 'player2'
elif bc.Team.Blue == self.game.get_player(self.client_id)['player'].team:
Expand All @@ -389,13 +385,8 @@ def get_next_message(self) -> object:
self.game.disconnected = True
self.game.game_over = True
raise KeyboardInterrupt()
finally:
wrapped_socket.close()

data = data.decode("utf-8").strip()
return data
#unpacked_data = json.loads(data)
#return unpacked_data
return data.decode("utf-8").strip()

def send_message(self, obj: object) -> None:
'''
Expand All @@ -410,22 +401,21 @@ def send_message(self, obj: object) -> None:
None
'''


send_socket = self.request
if isinstance(obj, bytes):
obj = obj.decode()
encoded_message = obj
encoded_message.append(b'\n')
else:
message = obj + "\n"
encoded_message = message.encode()

message = obj + "\n"
encoded_message = message.encode()
logging.debug("Client %s: Sending message %s", self.client_id,
encoded_message)

wrapped_socket = send_socket.makefile('rwb', 1)
try:
wrapped_socket.write(encoded_message)
self.wrapped_socket.write(encoded_message)
except IOError:
wrapped_socket.close()
send_socket.close()
self.wrapped_socket.close()
self.request.close()
print("{} has not accepted message for {} seconds, assuming they're dead".format(
[p for p in self.game.players if p['id'] == self.client_id][0]['player'],
TIMEOUT
Expand All @@ -441,8 +431,8 @@ def send_message(self, obj: object) -> None:
self.game.game_over = True
raise TimeoutError()
except KeyboardInterrupt:
wrapped_socket.close()
send_socket.close()
self.wrapped_socket.close()
self.request.close()
if bc.Team.Red == self.game.get_player(self.client_id)['player'].team:
self.game.winner = 'player2'
elif bc.Team.Blue ==self.game.get_player(self.client_id)['player'].team:
Expand All @@ -453,8 +443,6 @@ def send_message(self, obj: object) -> None:
self.game.disconnected = True
self.game.game_over = True
raise KeyboardInterrupt()
finally:
wrapped_socket.close()
return

def message(self, state_diff):
Expand Down Expand Up @@ -487,6 +475,7 @@ def player_handler(self):
self.logged_in = False
logging.debug("Client connected to server")
self.request.settimeout(TIMEOUT)
self.wrapped_socket = self.request.makefile('rwb', 1)

TIMEDOUTLOG = False

Expand All @@ -513,6 +502,7 @@ def player_handler(self):
self.send_message(log_success)

if self.game.game_over:
self.wrapped_socket.close()
return

logging.debug("Client %s: Spinning waiting for game to start",
Expand All @@ -534,12 +524,14 @@ def player_handler(self):
# This is the loop that the code will always remain in
# Blocks until it this clients turn
if not self.game.start_turn(self.client_id):
self.wrapped_socket.close()
self.request.close()
return

if self.game.manager.is_over():
self.game.game_over = True
self.game.end_turn()
self.wrapped_socket.close()
self.request.close()
return

Expand All @@ -554,6 +546,8 @@ def player_handler(self):
running_stats["bld"] = False

if self.game.initialized <= 3:
# Refresh the process tree here, then assume it is going to stay the same for performance reasons
my_sandbox.refreshProcessChildren()
my_sandbox.unpause()
self.send_message(start_turn_msg)
self.game.initialized += 1
Expand Down