mesa
mesa copied to clipboard
Socket Closure
What's the problem this feature will solve?
Using ModularServer - When I run my code it works, when I run it again I get a socket error.
OSError: [WinError 10048] Only one usage of each socket address (protocol/network address/port) is normally permitt
Describe the solution you'd like
A function in the ModularServer that closes (if open
) any socket you intend to use.
server = ModularServer(...)
server.close()
server.launch()
Or
Maybe a relaunch option that closes then launches in one command
ModularServer(...).relaunch()
Or
A button on the website to close the socket.
Title | About| --- | Start | Step | Reset | **Exit**
Additional context I attempted to understand how to close tornado web applications by reading the literature, but I'm not experienced and couldn't find a simple solution.
@aw-west ty for the suggestion. It looks like something that we should definitely look into.
I can't recreate though. Can you be more explicit so someone can try to recreate it?
Here is a single file version of the MESA example of Conway's Game of Life.
# MESA ConwaysLife Example in a single file
from random import random
from mesa import Model
from mesa import Agent
from mesa.time import SimultaneousActivation
from mesa.space import Grid
from mesa.visualization.modules import CanvasGrid
from mesa.visualization.ModularVisualization import ModularServer
class ConwayAgent(Agent):
DEAD = 0
ALIVE = 1
def __init__(self, pos, model, init_state=DEAD):
super().__init__(pos, model)
self.x, self.y = pos
self.state = init_state
self._nextState = None
@property
def isAlive(self):
return self.state == self.ALIVE
@property
def neighbours(self):
return self.model.grid.neighbor_iter((self.x, self.y), moore=True)
def step(self):
live_neighbors = sum(neighbour.isAlive for neighbour in self.neighbours)
self._nextState = self.state
if self.isAlive:
if live_neighbors < 2 or 4 < live_neighbors:
self._nextState = self.DEAD
else:
if live_neighbors == 3:
self._nextState = self.ALIVE
possible_steps = self.model.grid.get_neighbors(
self.pos,
moore = True,
include_center = False)
print(possible_steps)
def advance(self):
self.state = self._nextState
class ConwayModel(Model):
def __init__(self, height, width):
self.schedule = SimultaneousActivation(self)
self.grid = Grid(height, width, torus=True)
for (contents, x, y) in self.grid.coord_iter():
cell = ConwayAgent((x, y), self)
if random() < .1:
cell.state = cell.ALIVE
self.grid.place_agent(cell, (x, y))
self.schedule.add(cell)
self.running = True
def step(self):
self.schedule.step()
def PortrayAgent(cell):
assert cell is not None
return {
"Shape": "rect",
"w": 1,
"h": 1,
"Filled": "true",
"Layer": 0,
"x": cell.x,
"y": cell.y,
"Color": "black" if cell.isAlive else "white"
}
canvas_element = CanvasGrid(PortrayAgent, 50, 50, 250, 250)
server = ModularServer(ConwayModel, [canvas_element], "Game of Life", {"height": 50, "width": 50})
server.launch()
I wasn't able to recreate, but I did a little more research. It looks like this is the result of a wait-type function that occurs after the socket is closed. The purpose is to wait for any remaining packets to be processed.
It looks like we could do something like.... SO_REUSEADDR
- see this post and I guess the way to test in tests would be to keep the socket open and then try to reuse it?
@dmasad thoughts?
Second step would be further discussion on the 'exit' button.
Quite easy to recreate for me.
System: Virtual machine running Ubuntu 20.04.2 LTS Python 3.8.5 Mesa 0.8.8.1
In the terminal for any of the models I have:
mesa runserver
Ctrl-Z
- to close the server
mesa runserver
Which results in:
^Z
[1]+ Stopped mesa runserver
(venv_path) uos@uos-VirtualBox:~/model_path$ mesa runserver
Interface starting at http://127.0.0.1:8521
Traceback (most recent call last):
File "/venv_path/bin/mesa", line 8, in <module>
sys.exit(cli())
File "/venv_path/lib/python3.8/site-packages/click/core.py", line 829, in __call__
return self.main(*args, **kwargs)
File "/venv_path/lib/python3.8/site-packages/click/core.py", line 782, in main
rv = self.invoke(ctx)
File "/venv_path/lib/python3.8/site-packages/click/core.py", line 1259, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
File "/venv_path/lib/python3.8/site-packages/click/core.py", line 1066, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/venv_path/lib/python3.8/site-packages/click/core.py", line 610, in invoke
return callback(*args, **kwargs)
File "/venv_path/lib/python3.8/site-packages/mesa/main.py", line 33, in runserver
exec(code, {}, {})
File "run.py", line 3, in <module>
server.launch()
File "/venv_path/lib/python3.8/site-packages/mesa/visualization/ModularVisualization.py", line 333, in launch
self.listen(self.port)
File "/venv_path/lib/python3.8/site-packages/tornado/web.py", line 2109, in listen
server.listen(port, address)
File "/venv_path/lib/python3.8/site-packages/tornado/tcpserver.py", line 151, in listen
sockets = bind_sockets(port, address=address)
File "/venv_path/lib/python3.8/site-packages/tornado/netutil.py", line 161, in bind_sockets
sock.bind(sockaddr)
OSError: [Errno 98] Address already in use
Which is quite frustrating because I then have to restart my interpreter to get it to connect again.
I would be in favor of an exit button or some way of cleanly closing the server rather than having to force it to quit (or is this existing functionality and just missing from the docs?)
EDIT:
Closing a running Mesa server using Ctrl-C
in the terminal rather than Ctrl-Z
avoids causing this issue so I highly recommend doing that.