diff --git a/Release/database/XNSim.db b/Release/database/XNSim.db index 308d816..131dcc0 100644 Binary files a/Release/database/XNSim.db and b/Release/database/XNSim.db differ diff --git a/XNMonitorServer/TopicManager.h b/XNMonitorServer/TopicManager.h index 822d870..5119c8c 100755 --- a/XNMonitorServer/TopicManager.h +++ b/XNMonitorServer/TopicManager.h @@ -145,6 +145,15 @@ public: void unregisterPublisher(const std::string &topicName) { std::lock_guard locker(m_Mutex); + unregisterPublisherWithoutLock(topicName); + } + + /** + * @brief 注销发布者, 不带锁 + * @param topicName: 主题名称 + */ + void unregisterPublisherWithoutLock(const std::string &topicName) + { auto it = topics_.find(topicName); if (it != topics_.end()) { MonitorTopicInfo &topicInfo = it->second; // 获取主题信息 @@ -221,12 +230,21 @@ public: } /** - * @brief 注销订阅者 + * @brief 注销订阅者, 带锁 * @param topicName: 主题名称 */ void unregisterSubscriber(const std::string &topicName) { std::lock_guard locker(m_Mutex); + unregisterSubscriberWithoutLock(topicName); + } + + /** + * @brief 注销订阅者, 不带锁 + * @param topicName: 主题名称 + */ + void unregisterSubscriberWithoutLock(const std::string &topicName) + { auto it = topics_.find(topicName); if (it != topics_.end()) { MonitorTopicInfo &topicInfo = it->second; // 获取主题信息 @@ -260,8 +278,8 @@ private: std::lock_guard locker(m_Mutex); if (m_Participant != nullptr) { while (!topics_.empty()) { - unregisterPublisher(topics_.begin()->first); // 注销发布者 - unregisterSubscriber(topics_.begin()->first); // 注销订阅者 + unregisterPublisherWithoutLock(topics_.begin()->first); // 注销发布者 + unregisterSubscriberWithoutLock(topics_.begin()->first); // 注销订阅者 } } } diff --git a/XNSimHtml/components/run-sim.js b/XNSimHtml/components/run-sim.js index 850259a..7a6c928 100644 --- a/XNSimHtml/components/run-sim.js +++ b/XNSimHtml/components/run-sim.js @@ -315,6 +315,45 @@ class RunSim extends HTMLElement { // 使用进程ID作为仿真ID this.currentSimulationId = data.pid.toString(); + + // 获取构型ID + const savedSelection = localStorage.getItem('xnsim-selection'); + const selection = savedSelection ? JSON.parse(savedSelection) : {}; + const confID = selection.configurationId; + + // 获取构型参数 + const configResponse = await fetch(`/api/configurations/${confID}`); + if (!configResponse.ok) { + throw new Error('获取构型参数失败'); + } + const configData = await configResponse.json(); + + // 从构型参数中提取域ID + const domainId = configData.DomainID; + if (!domainId) { + throw new Error('构型参数中未找到有效的域ID'); + } + + // 初始化DDS监控 + const ddsInitResponse = await fetch('/api/dds-monitor/initialize', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + domainId: domainId, + }) + }); + + if (!ddsInitResponse.ok) { + console.warn('初始化DDS监控失败,但继续连接仿真'); + } else { + const ddsInitResult = await ddsInitResponse.json(); + if (ddsInitResult.error) { + console.warn('初始化DDS监控出错:', ddsInitResult.error); + } + } + // 清空并初始化输出框 const outputContent = this.shadowRoot.querySelector('#output-content'); @@ -524,16 +563,13 @@ class RunSim extends HTMLElement { throw new Error('构型参数中未找到有效的域ID'); } - // 生成唯一的监控器ID - const monitorId = `sim_${Date.now()}`; - // 初始化DDS监控 const ddsInitResponse = await fetch('/api/dds-monitor/initialize', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ domainId, monitorId }) + body: JSON.stringify({ domainId }) }); if (!ddsInitResponse.ok) { @@ -624,16 +660,42 @@ class RunSim extends HTMLElement { // 在组件销毁时清理资源 disconnectedCallback() { + // 清理日志文件轮询 if (this.logFilePollingInterval) { clearInterval(this.logFilePollingInterval); this.logFilePollingInterval = null; } + + // 清理 SSE 连接 if (this.eventSource) { this.eventSource.close(); this.eventSource = null; } + + // 清理其他资源 this.currentSimulationId = null; this.reconnectAttempts = 0; + this.isPaused = false; + this.modelGroups = []; + this.services = []; + + // 清理 DOM 事件监听器 + const runButton = this.shadowRoot.querySelector('#run-button'); + const runSimulationButton = this.shadowRoot.querySelector('#run-simulation-button'); + const pauseSimulationButton = this.shadowRoot.querySelector('#pause-simulation-button'); + const stopSimulationButton = this.shadowRoot.querySelector('#stop-simulation-button'); + + if (runButton) runButton.removeEventListener('click', () => this.runTest()); + if (runSimulationButton) runSimulationButton.removeEventListener('click', () => this.runSimulation()); + if (pauseSimulationButton) pauseSimulationButton.removeEventListener('click', () => this.pauseSimulation()); + if (stopSimulationButton) stopSimulationButton.removeEventListener('click', () => this.stopSimulation()); + + // 清理 Shadow DOM + if (this.shadowRoot) { + this.shadowRoot.innerHTML = ''; + } + + // 注意:不在这里关闭 DDS 监控,因为其他组件可能正在使用它 } showError(message) { @@ -1136,23 +1198,8 @@ class RunSim extends HTMLElement { throw new Error(result.message || '停止引擎失败'); } - // 等待5秒 - await new Promise(resolve => setTimeout(resolve, 5000)); - - // 调用老接口确保完全停止 - const fallbackResponse = await fetch('/api/stop-simulation', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - id: simulationId - }) - }); - - if (!fallbackResponse.ok) { - console.warn('调用老接口停止仿真失败,但引擎已停止'); - } + // 等待1秒确保引擎完全停止 + await new Promise(resolve => setTimeout(resolve, 1000)); // 关闭SSE连接 if (this.eventSource) { @@ -1160,13 +1207,53 @@ class RunSim extends HTMLElement { this.eventSource = null; } + // 调用停止仿真接口清理数据库记录和发送终止事件 + const stopResponse = await fetch('/api/stop-simulation', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ id: simulationId }) + }); + + if (!stopResponse.ok) { + console.warn('调用停止仿真接口失败,但引擎已停止'); + } + // 重置按钮状态 this.resetSimulationButtons(); this.currentSimulationId = null; this.showSuccess('仿真已停止'); + + // 关闭系统监控 + try { + await fetch('/api/system-monitor/stop', { + method: 'POST' + }); + } catch (error) { + + } + + // 关闭DDS监控 + try { + await fetch('/api/dds-monitor/unregister', { + method: 'POST' + }); + } catch (error) { + console.warn('关闭DDS监控失败,但这不影响仿真停止'); + } + } catch (error) { console.error('停止仿真失败:', error); this.showError('停止仿真失败: ' + error.message); + + // 即使出错也尝试清理资源 + if (this.eventSource) { + this.eventSource.close(); + this.eventSource = null; + } + this.resetSimulationButtons(); + this.currentSimulationId = null; } } diff --git a/XNSimHtml/components/simulation-monitor.js b/XNSimHtml/components/simulation-monitor.js index b3f40a5..75c23be 100644 --- a/XNSimHtml/components/simulation-monitor.js +++ b/XNSimHtml/components/simulation-monitor.js @@ -20,9 +20,14 @@ class SimulationMonitor extends HTMLElement { connectedCallback() { this.render(); this.isActive = true; // 设置初始状态为激活 - // 等待组件完全加载 + + // 等待组件完全加载后初始化 setTimeout(() => { this.initializeComponent(); + // 初始化完成后再启动定时器,给服务器一些准备时间 + setTimeout(() => { + this.startStatusCheck(); + }, 1000); // 延迟1秒启动定时器 }, 100); } @@ -45,10 +50,10 @@ class SimulationMonitor extends HTMLElement { // 设置定时器,每秒执行一次 this.statusCheckInterval = setInterval(() => { - if (this.isActive && this.monitorStatus.isMonitoring) { + if (this.isActive) { this.checkMonitorStatus(); } else { - // 如果监控已停止,清除定时器 + // 如果组件不再激活,清除定时器 this.stopStatusCheck(); } }, 1000); @@ -56,8 +61,6 @@ class SimulationMonitor extends HTMLElement { // 修改 checkMonitorStatus 方法 async checkMonitorStatus() { - if (!this.monitorStatus.isMonitoring) return; - try { // 获取监控状态 const statusResponse = await fetch('/api/system-monitor/status'); @@ -89,20 +92,13 @@ class SimulationMonitor extends HTMLElement { // 只在成功获取数据后更新UI this.updateUI(); } else { - // 如果监控已停止,清空数据 - this.systemInfo = null; - this.threadInfo = null; - // 停止状态检查 - this.stopStatusCheck(); - // 更新UI显示停止状态 - this.updateUI(); + // 如果监控已停止,尝试启动监控 + this.startMonitoring(); } } catch (error) { console.error('获取监控状态失败:', error); this.monitorStatus.lastError = error.message; this.updateUI(); - // 发生错误时也停止状态检查 - this.stopStatusCheck(); } } @@ -145,24 +141,10 @@ class SimulationMonitor extends HTMLElement { const ddsStatusResponse = await fetch('/api/dds-monitor/status'); const ddsStatusData = await ddsStatusResponse.json(); - // 如果DDS监控未初始化,先初始化DDS监控 + // 如果DDS监控未初始化,直接返回,等待下次定时器运行时再检查 if (!ddsStatusData.isInitialized) { - const ddsInitResponse = await fetch('/api/dds-monitor/initialize', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - domainId, - monitorId: this.monitorId - }) - }); - - if (!ddsInitResponse.ok) { - const errorData = await ddsInitResponse.json(); - console.error('DDS监控初始化失败:', errorData.error); - return; - } + console.log('DDS监控未初始化,等待下次检查'); + return; } // 启动系统监控 @@ -192,17 +174,9 @@ class SimulationMonitor extends HTMLElement { disconnectedCallback() { this.isActive = false; this.stopStatusCheck(); - // 注销监控器 - fetch('/api/dds-monitor/unregister', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ monitorId: this.monitorId }) - }).catch(error => { - console.error('注销监控器失败:', error); - }); - // 不要在这里销毁图表实例,让它在重新激活时重新创建 + // 自动停止监控 + this.stopMonitoring(); + // 注意:不再在这里注销 DDS 监控,因为其他组件可能正在使用它 } async initializeComponent() { @@ -224,9 +198,9 @@ class SimulationMonitor extends HTMLElement { const statusData = await statusResponse.json(); this.monitorStatus = statusData; - // 如果已经在监控中,开始状态检查 - if (this.monitorStatus.isMonitoring && !this.statusCheckInterval) { - this.startStatusCheck(); + // 如果监控未运行,尝试启动监控 + if (!this.monitorStatus.isMonitoring) { + this.startMonitoring(); } this.chartInitialized = true; @@ -285,21 +259,11 @@ class SimulationMonitor extends HTMLElement { } updateUI() { - const startButton = this.shadowRoot.querySelector('.start-button'); - const stopButton = this.shadowRoot.querySelector('.stop-button'); const statusDisplay = this.shadowRoot.querySelector('.status-display'); const engineInfo = this.shadowRoot.querySelector('#engine-info'); const coreStatus = this.shadowRoot.querySelector('#core-status'); const threadTableBody = this.shadowRoot.querySelector('#thread-table-body'); - if (this.monitorStatus.isMonitoring) { - startButton.disabled = true; - stopButton.disabled = false; - } else { - startButton.disabled = false; - stopButton.disabled = true; - } - // 更新状态显示 statusDisplay.textContent = `监控状态: ${this.monitorStatus.isMonitoring ? '运行中' : '已停止'}`; if (this.monitorStatus.lastError) { @@ -404,6 +368,17 @@ class SimulationMonitor extends HTMLElement { async stopMonitoring() { try { + // 首先检查监控状态 + const statusResponse = await fetch('/api/system-monitor/status'); + const statusData = await statusResponse.json(); + + // 如果监控未运行,直接返回 + if (!statusData.isMonitoring) { + console.log('监控未运行,无需关闭'); + return; + } + + // 执行关闭操作 const response = await fetch('/api/system-monitor/stop', { method: 'POST' }); @@ -495,63 +470,6 @@ class SimulationMonitor extends HTMLElement { padding: 16px; } - .toolbar { - display: flex; - gap: 12px; - align-items: center; - justify-content: space-between; - } - - .toolbar-left { - display: flex; - gap: 12px; - align-items: center; - } - - .input-group { - display: flex; - align-items: center; - gap: 8px; - } - - .input-label { - font-size: 14px; - color: #333; - white-space: nowrap; - } - - .control-button { - padding: 8px 16px; - border: none; - border-radius: 4px; - font-size: 14px; - cursor: pointer; - transition: background-color 0.2s; - } - - .control-button:disabled { - opacity: 0.6; - cursor: not-allowed; - } - - .start-button { - background-color: #4CAF50; - color: white; - } - - .start-button:hover:not(:disabled) { - background-color: #45a049; - } - - .stop-button { - background-color: #f44336; - color: white; - } - - .stop-button:hover:not(:disabled) { - background-color: #da190b; - } - .status-display { padding: 8px 12px; background-color: #f5f5f5; @@ -712,13 +630,7 @@ class SimulationMonitor extends HTMLElement {
-
-
- - -
-
监控状态: 未启动
-
+
监控状态: 未启动
@@ -824,7 +736,7 @@ class SimulationMonitor extends HTMLElement { - 频率 + 周期
diff --git a/XNSimHtml/routes/DDSMonitor.js b/XNSimHtml/routes/DDSMonitor.js index 3ed5ee7..6082681 100644 --- a/XNSimHtml/routes/DDSMonitor.js +++ b/XNSimHtml/routes/DDSMonitor.js @@ -6,27 +6,25 @@ const { initializeMonitor, cleanupMonitor } = require('../utils/systemMonitor'); let monitorStatus = { isInitialized: false, domainId: null, - lastError: null, - activeMonitors: new Set() // 添加活跃监控器集合 + lastError: null }; // 初始化监控服务 router.post('/initialize', async (req, res) => { try { - const { domainId, monitorId } = req.body; + const { domainId } = req.body; - if (!domainId || !monitorId) { + if (!domainId) { return res.status(400).json({ error: '缺少必要的参数' }); } - // 如果已经初始化,检查是否是新的监控器 + // 如果已经初始化,检查域ID是否匹配 if (monitorStatus.isInitialized) { if (monitorStatus.domainId !== domainId) { return res.status(400).json({ error: 'DDS域ID不匹配' }); } - monitorStatus.activeMonitors.add(monitorId); return res.json({ - message: '监控器已注册', + message: '监控服务已初始化', status: monitorStatus }); } @@ -41,7 +39,6 @@ router.post('/initialize', async (req, res) => { monitorStatus.isInitialized = true; monitorStatus.domainId = domainId; monitorStatus.lastError = null; - monitorStatus.activeMonitors.add(monitorId); res.json({ message: '监控服务初始化成功', @@ -57,32 +54,21 @@ router.post('/initialize', async (req, res) => { // 注销监控器 router.post('/unregister', async (req, res) => { try { - const { monitorId } = req.body; - - if (!monitorId) { - return res.status(400).json({ error: '缺少必要的monitorId参数' }); - } - - monitorStatus.activeMonitors.delete(monitorId); - - // 如果没有活跃的监控器了,清理资源 - if (monitorStatus.activeMonitors.size === 0) { - cleanupMonitor(); - monitorStatus = { - isInitialized: false, - domainId: null, - lastError: null, - activeMonitors: new Set() - }; - } + // 清理资源 + await cleanupMonitor(); + monitorStatus = { + isInitialized: false, + domainId: null, + lastError: null + }; res.json({ - message: '监控器注销成功', + message: '监控服务注销成功', status: monitorStatus }); } catch (error) { - console.error('注销监控器失败:', error); - res.status(500).json({ error: '注销监控器失败', message: error.message }); + console.error('注销监控服务失败:', error); + res.status(500).json({ error: '注销监控服务失败', message: error.message }); } }); diff --git a/XNSimHtml/routes/SystemControl.js b/XNSimHtml/routes/SystemControl.js index a537588..d8c0625 100644 --- a/XNSimHtml/routes/SystemControl.js +++ b/XNSimHtml/routes/SystemControl.js @@ -83,7 +83,7 @@ router.post('/resume', async (req, res) => { */ router.post('/stop', async (req, res) => { try { - const result = stopEngine(); + const result = await stopEngine(); res.json({ success: !result.includes('失败'), message: result diff --git a/XNSimHtml/routes/run-simulation.js b/XNSimHtml/routes/run-simulation.js index aa6ac25..946a78f 100644 --- a/XNSimHtml/routes/run-simulation.js +++ b/XNSimHtml/routes/run-simulation.js @@ -385,16 +385,36 @@ router.post('/stop-simulation', async (req, res) => { if (isRunning && isXNEngine) { // 终止进程 try { + // 使用 SIGTERM 信号终止进程 process.kill(processInfo.pid, 'SIGTERM'); - // 等待进程终止 - await new Promise(resolve => setTimeout(resolve, 1000)); - // 检查进程是否真的终止了 - const stillRunning = await isProcessRunning(processInfo.pid); - if (stillRunning) { - // 如果还在运行,强制终止 - process.kill(processInfo.pid, 'SIGKILL'); - } + // 使用 Promise.race 添加超时机制 + const killPromise = new Promise(async (resolve) => { + let attempts = 0; + const maxAttempts = 5; + + while (attempts < maxAttempts) { + const stillRunning = await isProcessRunning(processInfo.pid); + if (!stillRunning) { + resolve(); + return; + } + await new Promise(r => setTimeout(r, 200)); + attempts++; + } + + // 如果进程还在运行,使用 SIGKILL 强制终止 + if (await isProcessRunning(processInfo.pid)) { + process.kill(processInfo.pid, 'SIGKILL'); + } + resolve(); + }); + + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('终止进程超时')), 3000) + ); + + await Promise.race([killPromise, timeoutPromise]); // 删除数据库中的进程记录 await deleteXNEngineProcess(id); diff --git a/XNSimHtml/utils/systemMonitor.js b/XNSimHtml/utils/systemMonitor.js index 1ce0903..305c92e 100644 --- a/XNSimHtml/utils/systemMonitor.js +++ b/XNSimHtml/utils/systemMonitor.js @@ -112,14 +112,16 @@ function initializeMonitor(domainId) { } // 清理监控服务器资源 -function cleanupMonitor() { +async function cleanupMonitor() { if (!monitorLib) { return; } + try { monitorLib.XN_Cleanup(); } catch (error) { - return; + console.error('清理监控服务器资源失败:', error); + throw error; } } @@ -283,7 +285,7 @@ function resumeEngine() { } // 停止引擎 -function stopEngine() { +async function stopEngine() { if (!monitorLib) { return '监控服务器库未加载'; }