You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Electron中Python服务器启动后轮询检测机制未停止问题排查

问题:轮询机制未检测到已启动的Python服务器

我在Electron应用中实现轮询机制,等待Python服务器启动完成后再创建浏览器窗口——因为若服务器启动过慢,我需要刷新应用才能从服务器获取数据。
我设置了10秒的重试时长,使用electron-forge start启动应用时,服务器约1秒即可启动,控制台可见启动日志,且我能通过Chrome浏览器成功访问/health-check健康检查接口(控制台中的INFO行是我刷新浏览器验证连接的记录)。但我的轮询机制会持续检测直到10秒超时,随后应用退出。

代码与控制台输出

main.ts代码

// Electron modules
import { app, BrowserWindow, ipcMain } from 'electron';

// Built-in modules
import { spawn, exec } from 'child_process';
import path from 'path';

// Extra modules
import getPort, { portNumbers } from 'get-port';
import get from 'axios';


// Function to check if server is running
async function isServerRunning(serverUrl: string) {
  try {
    const response = await get(serverUrl);
    console.log(response);
    return response.status === 200;
  } catch (error) {
    return false;
  }
}

// Function to wait for the server to start
async function waitForServer(serverUrl: string) {
  const maxRetries = 10; // Set max retries
  let attempts = 0;

  while (attempts < maxRetries) {
    console.log(`Attempt #${attempts}`);
    const serverIsRunning = await isServerRunning(serverUrl);
    console.log(serverIsRunning);
    if (serverIsRunning) {
      return true; // Server is up
    }
    attempts++;
    await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
  }
  throw new Error('Waiting for server to start.');
}


const createWindow = (port: number) => {
  const mainWindow = new BrowserWindow({
    frame: false,
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      nodeIntegration: true,
      contextIsolation: false,
    },
  });

  // and load the index.html of the app.
  if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
    mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);
  } else {
    mainWindow.loadFile(
      path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`),
    );
  }
};


app.whenReady().then(async () => {
  const port = 8036;

  // Start the flask server in development or production
  if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
    spawn(`python backend/main.py ${port}`, {
      detached: true,
      shell: true,
      stdio: 'inherit',
    });
  } else {
    ...
  }

  try {
    await waitForServer(`http://localhost:${port}/health-check`);
    createWindow(port);
  } catch (error) {
    console.error(error);
    app.quit();
  }

  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow(port);
  });

  app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
      app.quit();
    }
  });
});

控制台输出

✔ Checking your system
✔ Locating application
✔ Loading configuration
✔ Preparing native dependencies [0.2s]
✔ Running generateAssets hook
⠙ [plugin-vite] Launching dev servers for renderer process code
◼ [plugin-vite] Compiling main process code
✔ [plugin-vite] Launching dev servers for renderer process code [0.1s]
⠸ [plugin-vite] Compiling main process code
vite v4.4.9 building for development...

watching for file changes...
vite v4.4.9 building for development...

watching for file changes...

build started...

build started...
✓ 1 modules transformed.
Generated an empty chunk: "preload".
✔ [plugin-vite] Launching dev servers for renderer process code [0.1s]
✔ [plugin-vite] Launching dev servers for renderer process code [0.1s]
⠧ [plugin-vite] Compiling main process code
✔ [plugin-vite] Launching dev servers for renderer process code [0.1s]
✔ [plugin-vite] Compiling main process code [0.8s]

2024-09-26 22:54:06.322 Electron[22787:10970072] WARNING: Secure coding is not enabled for restorable state! Enable secure coding by implementing NSApplicationDelegate.applicationSupportsSecureRestorableState: and returning YES.
Attempt #0
false
INFO:     Started server process [22790]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8036 (Press CTRL+C to quit)
Attempt #1
false
Attempt #2
false
INFO:     127.0.0.1:53635 - "GET /health-check HTTP/1.1" 200 OK
Attempt #3
false
Attempt #4
false
Attempt #5
false
INFO:     127.0.0.1:53635 - "GET /health-check HTTP/1.1" 200 OK
Attempt #6
false
Attempt #7
false
Attempt #8
false
Attempt #9
false
Error: Waiting for server to start.
    at Oo (/repopath/.vite/build/main.js:21:7777)
    at async /repopath/.vite/build/main.js:21:8504

问题原因与修复方案

核心原因

你的Python服务器绑定的是0.0.0.0:8036(仅监听IPv4地址),但Electron中轮询请求用的是http://localhost:8036/health-check。部分环境下Electron会优先将localhost解析为IPv6地址[::1],而服务器未监听IPv6,导致请求失败,轮询无法检测到服务器已启动。

另外,当前isServerRunning函数未打印错误详情,无法直观排查请求失败原因。

修复步骤

  1. 修改轮询地址:将waitForServer的参数从http://localhost:${port}/health-check改为http://127.0.0.1:${port}/health-check,强制使用IPv4地址请求,避免解析歧义。
  2. 添加错误日志:在isServerRunning的catch块中增加错误打印,方便后续排查:
    catch (error) {
      console.error(`健康检查请求失败:`, error.message);
      return false;
    }
    
  3. 可选:兼容IPv6:如果需要支持IPv6,可以修改Python服务器启动参数,让Uvicorn同时监听IPv4和IPv6,例如启动命令加上--host ::。

验证效果

修改地址后,轮询请求将直接访问IPv4的127.0.0.1,与Chrome测试的地址一致,就能正确检测到服务器状态,不会再超时退出。

内容的提问来源于stack exchange,提问作者Simon1

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 20:05:08