From 127ad733fbf23b81b55b2c33501189f8cef382ab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Dec 2025 10:07:50 +0000 Subject: [PATCH 1/3] Initial plan From 51aa0ffcff9168beb6d0b64be7c61a3c4b82e760 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Dec 2025 10:16:11 +0000 Subject: [PATCH 2/3] Fix UDP server Ctrl+C shutdown - track and cancel background tasks Co-authored-by: shugonta <827026+shugonta@users.noreply.github.com> --- server/main.py | 1 + server/udp_server.py | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/server/main.py b/server/main.py index fd03420..ddbda78 100644 --- a/server/main.py +++ b/server/main.py @@ -8,6 +8,7 @@ import time import math import sys +import signal import uvicorn from dataclasses import dataclass, asdict from typing import Dict, Set diff --git a/server/udp_server.py b/server/udp_server.py index a397997..f1d06cd 100644 --- a/server/udp_server.py +++ b/server/udp_server.py @@ -52,7 +52,9 @@ def connection_made(self, transport: asyncio.DatagramTransport): def datagram_received(self, data: bytes, addr: tuple): """Handle incoming UDP datagram.""" # Schedule processing as a task to avoid blocking the protocol - asyncio.create_task(self.server.handle_packet(data, addr)) + task = asyncio.create_task(self.server.handle_packet(data, addr)) + self.server.background_tasks.add(task) + task.add_done_callback(self.server.background_tasks.discard) def error_received(self, exc: Exception): print(f"UDP Error: {exc}") @@ -98,6 +100,7 @@ def __init__( self.running = False self.transport: Optional[asyncio.DatagramTransport] = None self.protocol: Optional[UDPInputProtocol] = None + self.background_tasks: Set[asyncio.Task] = set() # Track background tasks for cleanup # Player tracking self.player_addresses: Dict[str, tuple] = {} # player_id -> (host, port) @@ -136,8 +139,19 @@ async def stop(self): """Stop the UDP server and cleanup.""" self.running = False + # Cancel all background tasks + for task in self.background_tasks: + task.cancel() + + # Wait for all background tasks to complete with a timeout + if self.background_tasks: + await asyncio.gather(*self.background_tasks, return_exceptions=True) + + # Close the transport if self.transport: self.transport.close() + # Give the transport a moment to flush and close cleanly + await asyncio.sleep(0.1) self.transport = None # Log final metrics From 37a906fa12ff6ea2ade00e8b420f373a8a3fffae Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Dec 2025 10:19:26 +0000 Subject: [PATCH 3/3] Fix callback and remove unused import per code review Co-authored-by: shugonta <827026+shugonta@users.noreply.github.com> --- server/main.py | 1 - server/udp_server.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/server/main.py b/server/main.py index ddbda78..fd03420 100644 --- a/server/main.py +++ b/server/main.py @@ -8,7 +8,6 @@ import time import math import sys -import signal import uvicorn from dataclasses import dataclass, asdict from typing import Dict, Set diff --git a/server/udp_server.py b/server/udp_server.py index f1d06cd..b69a1ff 100644 --- a/server/udp_server.py +++ b/server/udp_server.py @@ -54,7 +54,7 @@ def datagram_received(self, data: bytes, addr: tuple): # Schedule processing as a task to avoid blocking the protocol task = asyncio.create_task(self.server.handle_packet(data, addr)) self.server.background_tasks.add(task) - task.add_done_callback(self.server.background_tasks.discard) + task.add_done_callback(lambda t: self.server.background_tasks.discard(t)) def error_received(self, exc: Exception): print(f"UDP Error: {exc}")