YueGuobin b1514edf88
fix: add MCP server ready check to prevent initialization errors
Add server ready state tracking for MCP service to prevent
"Received request before initialization was complete" errors
when clients connect before GNS3 server completes startup.

Changes:
- Add MCP server ready state management with wait/notify mechanism
- Modify auth wrapper to wait for server initialization before accepting connections
- Set MCP server ready flag after GNS3 startup completes

This ensures MCP protocol initialization handshake only occurs
after GNS3 server is fully initialized, preventing race
conditions during server startup.
2026-06-06 15:21:17 +08:00

115 lines
3.6 KiB
Python

#!/usr/bin/env python
#
# Copyright (C) 2020 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import asyncio
from fastapi import FastAPI
from contextlib import asynccontextmanager
from gns3server.controller import Controller
from gns3server.config import Config
from gns3server.compute import MODULES
from gns3server.compute.port_manager import PortManager
from gns3server.utils.http_client import HTTPClient
from gns3server.db.tasks import connect_to_db, get_computes, disconnect_from_db, discover_images_on_filesystem
import logging
log = logging.getLogger(__name__)
auto_discover_images_task_handle = None
@asynccontextmanager
async def lifespan(app: FastAPI):
await startup(app)
yield
await shutdown(app)
async def startup(app: FastAPI) -> None:
"""
Tasks to be performed when the server is starting.
"""
loop = asyncio.get_event_loop()
logger = logging.getLogger("asyncio")
logger.setLevel(logging.ERROR)
if log.getEffectiveLevel() == logging.DEBUG:
# On debug version we enable info that
# coroutine is not called in a way await/await
loop.set_debug(True)
# connect to the database
await connect_to_db(app)
# retrieve the computes from the database
computes = await get_computes(app)
await Controller.instance().start(computes)
# Because with a large image collection
# without md5sum already computed we start the
# computing with server start
from gns3server.compute.qemu import Qemu
if Config.instance().settings.Server.auto_discover_images is True:
# Start the discovering new images on file system 5 seconds after the server has started
# to give it a chance to process API requests
global auto_discover_images_task_handle
auto_discover_images_task_handle = asyncio.get_event_loop().call_later(
5,
lambda: asyncio.create_task(discover_images_on_filesystem(app))
)
for module in MODULES:
log.debug(f"Loading module {module.__name__}")
m = module.instance()
m.port_manager = PortManager.instance()
# Mark MCP server as ready to accept connections
from gns3server.api.routes.mcp import set_mcp_server_ready
set_mcp_server_ready(True)
log.info("GNS3 server startup completed")
async def shutdown(app: FastAPI) -> None:
"""
Tasks to be performed when the server is exiting.
"""
if auto_discover_images_task_handle is not None and not auto_discover_images_task_handle.cancelled():
auto_discover_images_task_handle.cancel()
await HTTPClient.close_session()
await Controller.instance().stop()
for module in MODULES:
log.debug(f"Unloading module {module.__name__}")
m = module.instance()
await m.unload()
if PortManager.instance().tcp_ports:
log.warning(f"TCP ports are still used {PortManager.instance().tcp_ports}")
if PortManager.instance().udp_ports:
log.warning(f"UDP ports are still used {PortManager.instance().udp_ports}")
await disconnect_from_db(app)