fix screenshot error when page closed; catch EPIPE globally; rigister gui process

This commit is contained in:
geekgeekrun
2026-01-17 18:07:50 +08:00
parent 9639151fdd
commit 5063f9c2aa
5 changed files with 82 additions and 89 deletions
+29 -74
View File
@@ -119,52 +119,23 @@ function handleMessage(socket, message) {
}); });
return return
} }
if (message.type === 'gui-register') {
// 工具进程注册消息 // GUI客户端的控制消息
if (message.type === 'worker-register') { // 标记为GUI客户端
const workerId = message.workerId; if (!guiClients.has(socket)) {
guiClients.add(socket);
// 检查是否在停止列表中(防止竞态条件)
if (stoppedWorkers.has(workerId)) {
console.log(`工具进程 ${workerId} 尝试注册,但已被标记为停止,拒绝注册`);
sendResponse(socket, _callbackUuid, {
error: `工具进程 ${workerId} 已被停止`,
shouldExit: true // 通知子进程应该退出
});
return;
} }
const workerInfo = workers.get(workerId);
if (workerInfo) {
workerInfo.socket = socket;
console.log(`工具进程 ${workerId} 已注册TCP连接`);
sendResponse(socket, _callbackUuid, { sendResponse(socket, _callbackUuid, {
success: true, success: true,
type: 'worker-registered',
message: `工具进程 ${workerId} 连接已注册`
}); });
// 通知GUI客户端
broadcastToGUI({
type: 'worker-connected',
workerId: workerId,
message: `工具进程 ${workerId} 已连接`
});
} else {
// 如果workerInfo不存在,但不在停止列表中,可能是进程启动但还未完全初始化
// 这种情况下也拒绝注册,让进程退出
if (!stoppedWorkers.has(workerId)) {
console.log(`工具进程 ${workerId} 尝试注册,但workerInfo不存在`);
}
sendResponse(socket, _callbackUuid, {
error: `工具进程 ${workerId} 未找到`,
shouldExit: true
});
}
return; return;
} }
// 工具进程查询是否应该退出 const workerId = message.workerId;
if (message.type === 'check-should-exit') { const workerInfo = workers.get(workerId);
switch (message.type) {
// 从工具进程发送的消息(数据、心跳、截图等)
case 'check-should-exit': {
const workerId = message.workerId; const workerId = message.workerId;
const shouldExit = stoppedWorkers.has(workerId) || !workers.has(workerId); const shouldExit = stoppedWorkers.has(workerId) || !workers.has(workerId);
@@ -176,13 +147,8 @@ function handleMessage(socket, message) {
if (shouldExit) { if (shouldExit) {
console.log(`工具进程 ${workerId} 查询是否应该退出,返回: 是`); console.log(`工具进程 ${workerId} 查询是否应该退出,返回: 是`);
} }
return; return
} }
// 工具进程发送的消息(数据、心跳、截图等)
const workerId = message.workerId;
const workerInfo = workers.get(workerId);
switch (message.type) {
case 'worker-message': { case 'worker-message': {
// 转发工具进程消息到GUI客户端 // 转发工具进程消息到GUI客户端
broadcastToGUI({ broadcastToGUI({
@@ -191,7 +157,7 @@ function handleMessage(socket, message) {
data: message.data || message, data: message.data || message,
timestamp: Date.now() timestamp: Date.now()
}); });
break return
} }
case 'worker-screenshot': { case 'worker-screenshot': {
if (workerInfo && message.data && message.data.screenshot /* && workerInfo.socket === socket */) { if (workerInfo && message.data && message.data.screenshot /* && workerInfo.socket === socket */) {
@@ -203,24 +169,21 @@ function handleMessage(socket, message) {
console.error('缓存 worker 截图信息失败:', e); console.error('缓存 worker 截图信息失败:', e);
} }
} }
break return
}
} }
// GUI客户端的控制消息 // GUI进程发送的消息(数据、心跳、截图等)
// 标记为GUI客户端 case 'start-worker': {
if (!guiClients.has(socket)) {
guiClients.add(socket);
}
switch (message.type) {
case 'start-worker':
const { const {
workerId, workerId,
command, command,
args, args,
env env
} = message } = message
if (workers.has(workerId)) {
console.log(`工具进程 ${workerId} 已在运行`);
return;
}
startWorker({ startWorker({
workerId, workerId,
command, command,
@@ -232,36 +195,36 @@ function handleMessage(socket, message) {
message: `工具进程 ${message.workerId} 已启动`, message: `工具进程 ${message.workerId} 已启动`,
workerId: message.workerId workerId: message.workerId
}); });
break; return
}
case 'stop-worker': case 'stop-worker': {
stopWorker(message.workerId); stopWorker(message.workerId);
sendResponse(socket, _callbackUuid, { sendResponse(socket, _callbackUuid, {
success: true, success: true,
message: `工具进程 ${message.workerId} 已停止`, message: `工具进程 ${message.workerId} 已停止`,
workerId: message.workerId workerId: message.workerId
}); });
break; return
}
case 'get-status': case 'get-status': {
const status = getWorkersStatus(); const status = getWorkersStatus();
sendResponse(socket, _callbackUuid, { sendResponse(socket, _callbackUuid, {
success: true, success: true,
workers: status workers: status
}); });
break; return
}
default: default: {
sendResponse(socket, _callbackUuid, { error: '未知的消息类型' }); sendResponse(socket, _callbackUuid, { error: '未知的消息类型' });
} }
}
} }
// 启动工具进程 // 启动工具进程
function startWorker({ workerId, command, args, env }, restartCount = 0) { function startWorker({ workerId, command, args, env }, restartCount = 0) {
if (workers.has(workerId)) {
console.log(`工具进程 ${workerId} 已在运行`);
return;
}
const noAutoRestartExitCodeSet = new Set([0]); const noAutoRestartExitCodeSet = new Set([0]);
(env.GEEKGEEKRUND_NO_AUTO_RESTART_EXIT_CODE ?? '') (env.GEEKGEEKRUND_NO_AUTO_RESTART_EXIT_CODE ?? '')
.split(',') .split(',')
@@ -512,11 +475,3 @@ process.on('SIGINT', () => {
process.exit(0); process.exit(0);
}); });
}); });
// 捕获未处理的 EPIPE 错误
process.on('uncaughtException', (err) => {
if (err.code === 'EPIPE' || err.code === 'ERR_STREAM_DESTROYED') {
return
}
throw err
});
@@ -82,5 +82,13 @@ export function openSettingWindow() {
needCallback: true needCallback: true
} }
) )
await sendToDaemon(
{
type: 'gui-register'
},
{
needCallback: true
}
)
}) })
} }
@@ -51,8 +51,22 @@ export const pageMapByName: {
} = {} } = {}
async function periodPushCurrentPageScreenshot () { async function periodPushCurrentPageScreenshot () {
try {
if (pageMapByName.boss?.isClosed()) {
return
}
const shouldExit = await checkShouldExit()
if (shouldExit) {
return
}
try { try {
await pushCurrentPageScreenshot(pageMapByName.boss) await pushCurrentPageScreenshot(pageMapByName.boss)
}
catch (err) {
if (err?.message?.includes(`PAGE_CLOSED`)) {
return
}
}
setTimeout(periodPushCurrentPageScreenshot, SCREENSHOT_INTERVAL_MS) setTimeout(periodPushCurrentPageScreenshot, SCREENSHOT_INTERVAL_MS)
} }
catch {} catch {}
+9
View File
@@ -3,6 +3,15 @@ import { runCommon } from './features/run-common';
import { launchDaemon } from './flow/OPEN_SETTING_WINDOW/launch-daemon'; import { launchDaemon } from './flow/OPEN_SETTING_WINDOW/launch-daemon';
import { connectToDaemon } from './flow/OPEN_SETTING_WINDOW/connect-to-daemon'; import { connectToDaemon } from './flow/OPEN_SETTING_WINDOW/connect-to-daemon';
import { randomUUID } from 'crypto'; import { randomUUID } from 'crypto';
// 捕获未处理的 EPIPE 错误
process.on('uncaughtException', (err) => {
if (err?.code === 'EPIPE' || err?.code === 'ERR_STREAM_DESTROYED') {
return
}
throw err
});
const isUiDev = process.env.NODE_ENV === 'development' const isUiDev = process.env.NODE_ENV === 'development'
const commandlineArgs = minimist(isUiDev ? process.argv.slice(2) : process.argv.slice(1)) const commandlineArgs = minimist(isUiDev ? process.argv.slice(2) : process.argv.slice(1))
console.log(commandlineArgs) console.log(commandlineArgs)
+10 -3
View File
@@ -26,6 +26,9 @@ export async function pushCurrentPageScreenshot (page) {
} }
}) })
} catch (err) { } catch (err) {
if (err?.message?.includes('Session closed')) {
throw new Error(`PAGE_CLOSED`)
}
// 截图失败不应影响主流程 // 截图失败不应影响主流程
console.warn('[READ_NO_REPLY_AUTO_REMINDER] pushCurrentPageScreenshot error', err) console.warn('[READ_NO_REPLY_AUTO_REMINDER] pushCurrentPageScreenshot error', err)
} }
@@ -38,9 +41,6 @@ export class PeriodPushCurrentPageScreenshotPlugin {
(page) => { (page) => {
async function periodPushCurrentPageScreenshot () { async function periodPushCurrentPageScreenshot () {
try { try {
if (!page) {
return
}
if (page.isClosed()) { if (page.isClosed()) {
return return
} }
@@ -48,7 +48,14 @@ export class PeriodPushCurrentPageScreenshotPlugin {
if (shouldExit) { if (shouldExit) {
return return
} }
try {
await pushCurrentPageScreenshot(page) await pushCurrentPageScreenshot(page)
}
catch (err) {
if (err?.message?.includes(`PAGE_CLOSED`)) {
return
}
}
setTimeout(periodPushCurrentPageScreenshot, SCREENSHOT_INTERVAL_MS) setTimeout(periodPushCurrentPageScreenshot, SCREENSHOT_INTERVAL_MS)
} }
catch {} catch {}