mirror of
https://github.com/geekgeekrun/geekgeekrun.git
synced 2026-09-08 17:09:07 +08:00
add socketToWorkerIdSetMap to fix when user process exit, worker is still running; add isReset for ensureIpcPipeName to fix daemon may not be connected
This commit is contained in:
+23
-7
@@ -43,9 +43,10 @@ const ipcSocketPath = process.platform === 'win32'
|
|||||||
: path.join(tmpdir(), `${ipcSocketName}.sock`)
|
: path.join(tmpdir(), `${ipcSocketName}.sock`)
|
||||||
|
|
||||||
const workers = new Map(); // workerId -> { process, status, restartCount, socket, latestScreenshot, latestScreenshotAt }
|
const workers = new Map(); // workerId -> { process, status, restartCount, socket, latestScreenshot, latestScreenshotAt }
|
||||||
const guiClients = new Set(); // GUI客户端连接集合
|
const userProcessClients = new Set(); // GUI客户端连接集合
|
||||||
const stoppedWorkers = new Set(); // 被用户主动停止的workerId集合,用于防止竞态条件
|
const stoppedWorkers = new Set(); // 被用户主动停止的workerId集合,用于防止竞态条件
|
||||||
const pidToProcessInfoMap = new Map()
|
const pidToProcessInfoMap = new Map()
|
||||||
|
const socketToWorkerIdSetMap = new WeakMap()
|
||||||
|
|
||||||
// 创建TCP服务器
|
// 创建TCP服务器
|
||||||
const server = net.createServer((socket) => {
|
const server = net.createServer((socket) => {
|
||||||
@@ -85,7 +86,7 @@ const server = net.createServer((socket) => {
|
|||||||
socket.on('close', () => {
|
socket.on('close', () => {
|
||||||
console.log('客户端已断开连接');
|
console.log('客户端已断开连接');
|
||||||
// 清理GUI客户端连接
|
// 清理GUI客户端连接
|
||||||
guiClients.delete(socket);
|
userProcessClients.delete(socket);
|
||||||
// 清理工具进程连接
|
// 清理工具进程连接
|
||||||
for (const [workerId, workerInfo] of workers.entries()) {
|
for (const [workerId, workerInfo] of workers.entries()) {
|
||||||
if (workerInfo.socket === socket) {
|
if (workerInfo.socket === socket) {
|
||||||
@@ -93,6 +94,10 @@ const server = net.createServer((socket) => {
|
|||||||
workerInfo.socket = null;
|
workerInfo.socket = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const workerIdSet = socketToWorkerIdSetMap.get(socket) || new Set()
|
||||||
|
;[...workerIdSet].forEach(workerId => {
|
||||||
|
stopWorker(workerId);
|
||||||
|
})
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -107,11 +112,11 @@ function handleMessage(socket, message) {
|
|||||||
});
|
});
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (message.type === 'gui-register') {
|
if (message.type === 'user-process-register') {
|
||||||
// GUI客户端的控制消息
|
// GUI客户端的控制消息
|
||||||
// 标记为GUI客户端
|
// 标记为GUI客户端
|
||||||
if (!guiClients.has(socket)) {
|
if (!userProcessClients.has(socket)) {
|
||||||
guiClients.add(socket);
|
userProcessClients.add(socket);
|
||||||
}
|
}
|
||||||
sendResponse(socket, _callbackUuid, {
|
sendResponse(socket, _callbackUuid, {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -183,6 +188,17 @@ function handleMessage(socket, message) {
|
|||||||
message: `工具进程 ${message.workerId} 已启动`,
|
message: `工具进程 ${message.workerId} 已启动`,
|
||||||
workerId: message.workerId
|
workerId: message.workerId
|
||||||
});
|
});
|
||||||
|
let socketToWorkerIdSet = socketToWorkerIdSetMap.get(socket)
|
||||||
|
if (
|
||||||
|
!(socketToWorkerIdSet instanceof Set)
|
||||||
|
) {
|
||||||
|
socketToWorkerIdSet = new Set()
|
||||||
|
socketToWorkerIdSetMap.set(
|
||||||
|
socket,
|
||||||
|
socketToWorkerIdSet
|
||||||
|
)
|
||||||
|
}
|
||||||
|
socketToWorkerIdSet.add(workerId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,13 +414,13 @@ function broadcastStatus() {
|
|||||||
|
|
||||||
// 广播消息给所有GUI客户端
|
// 广播消息给所有GUI客户端
|
||||||
function broadcastToGUI(message) {
|
function broadcastToGUI(message) {
|
||||||
guiClients.forEach(socket => {
|
userProcessClients.forEach(socket => {
|
||||||
if (!socket.destroyed) {
|
if (!socket.destroyed) {
|
||||||
try {
|
try {
|
||||||
sendResponse(socket, null, message);
|
sendResponse(socket, null, message);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('广播消息失败:', e);
|
console.error('广播消息失败:', e);
|
||||||
guiClients.delete(socket);
|
userProcessClients.delete(socket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,37 @@
|
|||||||
import { app } from 'electron'
|
|
||||||
import { AUTO_CHAT_ERROR_EXIT_CODE } from '../../common/enums/auto-start-chat'
|
import { AUTO_CHAT_ERROR_EXIT_CODE } from '../../common/enums/auto-start-chat'
|
||||||
import { sendToDaemon } from '../flow/OPEN_SETTING_WINDOW/connect-to-daemon'
|
import { daemonEE, sendToDaemon } from '../flow/OPEN_SETTING_WINDOW/connect-to-daemon'
|
||||||
import { saveAndGetCurrentRunRecord } from '../flow/OPEN_SETTING_WINDOW/utils/db'
|
import { saveAndGetCurrentRunRecord } from '../flow/OPEN_SETTING_WINDOW/utils/db'
|
||||||
|
import minimist from 'minimist'
|
||||||
|
|
||||||
export async function runCommon({ mode }) {
|
export async function runCommon({ mode }) {
|
||||||
app.dock?.hide()
|
await sendToDaemon(
|
||||||
|
{
|
||||||
|
type: 'user-process-register'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
needCallback: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const taskList = (
|
||||||
|
await sendToDaemon(
|
||||||
|
{
|
||||||
|
type: 'get-status'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
needCallback: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)?.workers
|
||||||
|
const runningTask = taskList?.find((it) => it.workerId === mode)
|
||||||
|
if (runningTask) {
|
||||||
|
const commandlineArgs = minimist(runningTask.args ?? [])
|
||||||
|
const runRecordId = Number(commandlineArgs['run-record-id'])
|
||||||
|
console.log('任务已在运行中')
|
||||||
|
return {
|
||||||
|
runRecordId,
|
||||||
|
isAlreadyRunning: true
|
||||||
|
}
|
||||||
|
}
|
||||||
const currentRunRecord = (await saveAndGetCurrentRunRecord())?.data
|
const currentRunRecord = (await saveAndGetCurrentRunRecord())?.data
|
||||||
const subProcessEnv = {
|
const subProcessEnv = {
|
||||||
...process.env,
|
...process.env,
|
||||||
@@ -30,13 +57,16 @@ export async function runCommon({ mode }) {
|
|||||||
needCallback: true
|
needCallback: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
;['SIGINT', 'SIGTERM'].forEach((evName) => {
|
daemonEE.on('message', (message) => {
|
||||||
process.on(evName, () => {
|
if (message.type === 'worker-exited') {
|
||||||
sendToDaemon({
|
if (
|
||||||
type: 'stop-worker',
|
message.workerId === mode &&
|
||||||
workerId: mode
|
!message.restarting &&
|
||||||
})
|
globalThis.GEEKGEEKRUN_PROCESS_ROLE !== 'ui'
|
||||||
})
|
) {
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
runRecordId: currentRunRecord?.id
|
runRecordId: currentRunRecord?.id
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ import './app-menu'
|
|||||||
import initIpc from './ipc'
|
import initIpc from './ipc'
|
||||||
import gtag from '../../utils/gtag'
|
import gtag from '../../utils/gtag'
|
||||||
import initPublicIpc from '../../utils/initPublicIpc'
|
import initPublicIpc from '../../utils/initPublicIpc'
|
||||||
import { launchDaemon } from './launch-daemon'
|
import { sendToDaemon, closeDaemonClient } from './connect-to-daemon'
|
||||||
import { connectToDaemon, sendToDaemon, closeDaemonClient } from './connect-to-daemon'
|
|
||||||
import { sleep } from "@geekgeekrun/utils/sleep.mjs"
|
|
||||||
|
|
||||||
export function openSettingWindow() {
|
export function openSettingWindow() {
|
||||||
// TODO: singleton lock; how can we check if there is another process should run as singleton with arguments?
|
// TODO: singleton lock; how can we check if there is another process should run as singleton with arguments?
|
||||||
@@ -81,7 +79,7 @@ export function openSettingWindow() {
|
|||||||
)
|
)
|
||||||
await sendToDaemon(
|
await sendToDaemon(
|
||||||
{
|
{
|
||||||
type: 'gui-register'
|
type: 'user-process-register'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
needCallback: true
|
needCallback: true
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ import { randomUUID } from 'node:crypto'
|
|||||||
import { connectToDaemon } from './connect-to-daemon'
|
import { connectToDaemon } from './connect-to-daemon'
|
||||||
|
|
||||||
const isUiDev = process.env.NODE_ENV === 'development'
|
const isUiDev = process.env.NODE_ENV === 'development'
|
||||||
export async function ensureIpcPipeName() {
|
export async function ensureIpcPipeName({ isReset } = {}) {
|
||||||
|
if (isReset) {
|
||||||
|
await writeStorageFile('ipc-pipe-name', '', { isJson: false })
|
||||||
|
}
|
||||||
let ipcPipeName = readStorageFile('ipc-pipe-name', { isJson: false })
|
let ipcPipeName = readStorageFile('ipc-pipe-name', { isJson: false })
|
||||||
if (!ipcPipeName) {
|
if (!ipcPipeName) {
|
||||||
ipcPipeName = `geekgeekrun-d_${randomUUID()}`
|
ipcPipeName = `geekgeekrun-d_${randomUUID()}`
|
||||||
@@ -20,10 +23,11 @@ export async function ensureIpcPipeName() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function launchDaemon() {
|
export async function launchDaemon() {
|
||||||
|
let daemonProcess
|
||||||
async function startDaemon() {
|
async function startDaemon() {
|
||||||
console.log('启动守护进程...')
|
console.log('启动守护进程...')
|
||||||
// 添加参数使守护进程在后台运行,不显示 UI
|
// 添加参数使守护进程在后台运行,不显示 UI
|
||||||
const daemonProcess = spawn(
|
daemonProcess = spawn(
|
||||||
process.argv[0],
|
process.argv[0],
|
||||||
isUiDev ? [process.argv[1], `--mode=launchDaemon`] : [`--mode=launchDaemon`],
|
isUiDev ? [process.argv[1], `--mode=launchDaemon`] : [`--mode=launchDaemon`],
|
||||||
{
|
{
|
||||||
@@ -59,11 +63,29 @@ export async function launchDaemon() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
await ensureIpcPipeName()
|
||||||
try {
|
try {
|
||||||
await connectToDaemon()
|
await connectToDaemon()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
let isDaemonLaunched = false
|
||||||
|
console.log('cannot connect to daemon, try to launch it', err)
|
||||||
// 启动守护进程
|
// 启动守护进程
|
||||||
|
try {
|
||||||
await startDaemon()
|
await startDaemon()
|
||||||
|
isDaemonLaunched = true
|
||||||
|
} catch (err) {
|
||||||
|
console.log('cannot launch to daemon, try to change port', err)
|
||||||
|
daemonProcess?.kill('SIGKILL')
|
||||||
|
await ensureIpcPipeName({ isReset: true })
|
||||||
|
try {
|
||||||
|
await startDaemon()
|
||||||
|
isDaemonLaunched = true
|
||||||
|
} catch (err) {
|
||||||
|
console.log('cannot launch to daemon, try to change port failed', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isDaemonLaunched) {
|
||||||
await connectToDaemon()
|
await connectToDaemon()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import minimist from 'minimist'
|
import minimist from 'minimist'
|
||||||
import { runCommon } from './features/run-common';
|
import { runCommon } from './features/run-common';
|
||||||
import { ensureIpcPipeName, launchDaemon } from './flow/OPEN_SETTING_WINDOW/launch-daemon';
|
import { launchDaemon } from './flow/OPEN_SETTING_WINDOW/launch-daemon';
|
||||||
|
import { app } from 'electron';
|
||||||
|
|
||||||
// 捕获未处理的 EPIPE 错误
|
// 捕获未处理的 EPIPE 错误
|
||||||
process.on('uncaughtException', (err) => {
|
process.on('uncaughtException', (err) => {
|
||||||
@@ -58,19 +59,25 @@ const runMode = commandlineArgs['mode'];
|
|||||||
|
|
||||||
// #region user entry
|
// #region user entry
|
||||||
case 'geekAutoStartWithBoss': {
|
case 'geekAutoStartWithBoss': {
|
||||||
await ensureIpcPipeName()
|
app.dock?.hide()
|
||||||
await launchDaemon()
|
await launchDaemon()
|
||||||
await runCommon({ mode: 'geekAutoStartWithBossMain' })
|
const { isAlreadyRunning } = await runCommon({ mode: 'geekAutoStartWithBossMain' })
|
||||||
|
if (isAlreadyRunning) {
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'readNoReplyAutoReminder': {
|
case 'readNoReplyAutoReminder': {
|
||||||
await ensureIpcPipeName()
|
app.dock?.hide()
|
||||||
await launchDaemon()
|
await launchDaemon()
|
||||||
await runCommon({ mode: 'readNoReplyAutoReminderMain' })
|
const { isAlreadyRunning } = await runCommon({ mode: 'readNoReplyAutoReminderMain' })
|
||||||
|
if (isAlreadyRunning) {
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
await ensureIpcPipeName()
|
globalThis.GEEKGEEKRUN_PROCESS_ROLE = 'ui'
|
||||||
await launchDaemon()
|
await launchDaemon()
|
||||||
const { openSettingWindow } = await import('./flow/OPEN_SETTING_WINDOW/index')
|
const { openSettingWindow } = await import('./flow/OPEN_SETTING_WINDOW/index')
|
||||||
openSettingWindow()
|
openSettingWindow()
|
||||||
|
|||||||
Reference in New Issue
Block a user