Skip to content
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
13 changes: 11 additions & 2 deletions src/plato_edge/keeper.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,18 @@ def start(self, bind: str = "0.0.0.0") -> None:
"""Start listening for pings."""
if self._running:
return
self._running = True
sock = self._get_socket()
sock.bind((bind, self._port))
try:
sock.bind((bind, self._port))
except (OSError, OverflowError):
# OverflowError (out-of-range port) is not a subclass of OSError.
self._sock = None
try:
sock.close()
except OSError:
pass
raise
self._running = True
self._thread = threading.Thread(target=self._loop, daemon=True)
self._thread.start()

Expand Down
19 changes: 19 additions & 0 deletions tests/test_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,25 @@ def test_bad_port(self):
with pytest.raises(OverflowError):
b.start()

def test_start_failure_does_not_latch(self):
# A failed start() must roll back partial state so the Beacon is
# not left latched as "running" with a dangling open socket and
# unable to be retried or cleanly discarded.
b = Beacon(port=99999)
with pytest.raises(OverflowError):
b.start()
assert b._running is False
assert b._sock is None
# stop() must remain a safe no-op after a failed start.
b.stop()
# And the instance must be reusable on a valid port.
b2 = Beacon(port=0)
try:
b2.start()
assert b2._running is True
finally:
b2.stop()


class TestExplain:
def test_trace_id(self):
Expand Down
Loading