Skip to content

Commit c034c85

Browse files
committed
fix: improve graceful shutdown and null safety
- Add dynamic wait times for event queue shutdown (5s if events pending, 1s if empty) - Fix signal handler to prevent recursive calls and ensure clean exit - Add null check for request parameters in MCP server - Update mcpcat-api dependency to 0.1.4
1 parent f17a572 commit c034c85

File tree

4 files changed

+43
-15
lines changed

4 files changed

+43
-15
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ classifiers = [
1919
]
2020
dependencies = [
2121
"mcp>=1.2.0",
22-
"mcpcat-api==0.1.3",
22+
"mcpcat-api==0.1.4",
2323
"pydantic>=2.0.0",
2424
]
2525

src/mcpcat/modules/event_queue.py

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import atexit
44
import queue
55
import signal
6+
import os
67
import threading
78
import time
89
from datetime import datetime, timezone
@@ -141,28 +142,46 @@ def destroy(self) -> None:
141142
self._shutdown = True
142143
self._shutdown_event.set()
143144

144-
# Wait for queue to drain (with timeout)
145-
timeout = 5.0 # 5 seconds
146-
start = time.time()
145+
# Determine wait time based on queue state
146+
if self.queue.qsize() > 0:
147+
# If there are events in queue, wait 5 seconds
148+
wait_time = 5.0
149+
write_to_log(f"Shutting down with {self.queue.qsize()} events in queue, waiting up to {wait_time}s")
150+
else:
151+
# If queue is empty, just wait 1 second for in-flight requests
152+
wait_time = 1.0
153+
write_to_log(f"Queue empty, waiting {wait_time}s for in-flight requests")
147154

148-
while self.queue.qsize() > 0 and time.time() - start < timeout:
149-
time.sleep(0.1)
155+
# Wait for the specified time
156+
time.sleep(wait_time)
150157

151-
# Shutdown executor
152-
# Note: timeout parameter was added in Python 3.9 but removed in later versions
158+
# Shutdown executor (this will wait for running tasks to complete)
153159
self.executor.shutdown()
154160

155-
if self.queue.qsize() > 0:
156-
write_to_log(f"Shutting down with {self.queue.qsize()} events still in queue")
161+
# Log final status
162+
remaining = self.queue.qsize()
163+
if remaining > 0:
164+
write_to_log(f"Shutdown complete. {remaining} events were not processed.")
157165

158166

159167
# Global event queue instance
160168
event_queue = EventQueue()
161169

162170

163-
def _shutdown_handler(*_):
171+
def _shutdown_handler(signum, frame):
164172
"""Handle shutdown signals."""
173+
174+
write_to_log("Received shutdown signal, gracefully shutting down...")
175+
176+
# Reset signal handlers to default behavior to avoid recursive calls
177+
signal.signal(signal.SIGINT, signal.SIG_DFL)
178+
signal.signal(signal.SIGTERM, signal.SIG_DFL)
179+
180+
# Perform graceful shutdown
165181
event_queue.destroy()
182+
183+
# Force exit after graceful shutdown
184+
os._exit(0)
166185

167186

168187
def set_event_queue(new_queue: EventQueue) -> None:

src/mcpcat/modules/overrides/mcp_server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ async def wrapped_list_tools_handler(request: ListToolsRequest) -> ServerResult:
6565
event = UnredactedEvent(
6666
session_id=session_id,
6767
timestamp=datetime.now(timezone.utc),
68-
parameters=request.params.model_dump() if request.params else {},
68+
parameters=request.params.model_dump() if request and request.params else {},
6969
event_type=EventType.MCP_TOOLS_LIST.value,
7070
)
7171

tests/test_event_queue.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -602,16 +602,25 @@ def test_shutdown_handlers_registered(mock_atexit, mock_signal):
602602
assert mock_atexit.called
603603

604604

605+
@patch('os._exit')
606+
@patch('mcpcat.modules.event_queue.signal.signal')
605607
@patch('mcpcat.modules.event_queue.event_queue')
606-
def test_shutdown_handler_function(mock_event_queue):
608+
def test_shutdown_handler_function(mock_event_queue, mock_signal, mock_exit):
607609
"""Test the _shutdown_handler function."""
608610
from mcpcat.modules.event_queue import _shutdown_handler
609611

610-
# Call the shutdown handler
611-
_shutdown_handler()
612+
# Call the shutdown handler with proper signal handler arguments
613+
_shutdown_handler(signal.SIGINT, None)
614+
615+
# Verify signal handlers are reset to default
616+
mock_signal.assert_any_call(signal.SIGINT, signal.SIG_DFL)
617+
mock_signal.assert_any_call(signal.SIGTERM, signal.SIG_DFL)
612618

613619
# Verify it calls destroy on the event queue
614620
mock_event_queue.destroy.assert_called_once()
621+
622+
# Verify it exits with code 0
623+
mock_exit.assert_called_once_with(0)
615624

616625

617626
@patch('sys.version_info', (3, 8, 0))

0 commit comments

Comments
 (0)