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
}
// 工具进程注册消息
if (message.type === 'worker-register') {
const workerId = message.workerId;
// 检查是否在停止列表中(防止竞态条件)
if (stoppedWorkers.has(workerId)) {
console.log(`工具进程 ${workerId} 尝试注册,但已被标记为停止,拒绝注册`);
sendResponse(socket, _callbackUuid, {
error: `工具进程 ${workerId} 已被停止`,
shouldExit: true // 通知子进程应该退出
});
return;
if (message.type === 'gui-register') {
// GUI客户端的控制消息
// 标记为GUI客户端
if (!guiClients.has(socket)) {
guiClients.add(socket);
}
const workerInfo = workers.get(workerId);
if (workerInfo) {
workerInfo.socket = socket;
console.log(`工具进程 ${workerId} 已注册TCP连接`);
sendResponse(socket, _callbackUuid, {
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;
}
// 工具进程查询是否应该退出
if (message.type === 'check-should-exit') {
const workerId = message.workerId;
const workerInfo = workers.get(workerId);
switch (message.type) {
// 从工具进程发送的消息(数据、心跳、截图等)
case 'check-should-exit': {
const workerId = message.workerId;
const shouldExit = stoppedWorkers.has(workerId) || !workers.has(workerId);
@@ -176,13 +147,8 @@ function handleMessage(socket, message) {
if (shouldExit) {
console.log(`工具进程 ${workerId} 查询是否应该退出,返回: 是`);
}
return;
return
}
// 工具进程发送的消息(数据、心跳、截图等)
const workerId = message.workerId;
const workerInfo = workers.get(workerId);
switch (message.type) {
case 'worker-message': {
// 转发工具进程消息到GUI客户端
broadcastToGUI({
@@ -191,7 +157,7 @@ function handleMessage(socket, message) {
data: message.data || message,
timestamp: Date.now()
});
break
return
}
case 'worker-screenshot': {
if (workerInfo && message.data && message.data.screenshot /* && workerInfo.socket === socket */) {
@@ -203,24 +169,21 @@ function handleMessage(socket, message) {
console.error('缓存 worker 截图信息失败:', e);
}
}
break
}
return
}
// GUI客户端的控制消息
// 标记为GUI客户端
if (!guiClients.has(socket)) {
guiClients.add(socket);
}
switch (message.type) {
case 'start-worker':
// GUI进程发送的消息(数据、心跳、截图等)
case 'start-worker': {
const {
workerId,
command,
args,
env
} = message
if (workers.has(workerId)) {
console.log(`工具进程 ${workerId} 已在运行`);
return;
}
startWorker({
workerId,
command,
@@ -232,36 +195,36 @@ function handleMessage(socket, message) {
message: `工具进程 ${message.workerId} 已启动`,
workerId: message.workerId
});
break;
return
}
case 'stop-worker':
case 'stop-worker': {
stopWorker(message.workerId);
sendResponse(socket, _callbackUuid, {
success: true,
message: `工具进程 ${message.workerId} 已停止`,
workerId: message.workerId
});
break;
return
}
case 'get-status':
case 'get-status': {
const status = getWorkersStatus();
sendResponse(socket, _callbackUuid, {
success: true,
workers: status
});
break;
return
}
default:
default: {
sendResponse(socket, _callbackUuid, { error: '未知的消息类型' });
}
}
}
// 启动工具进程
function startWorker({ workerId, command, args, env }, restartCount = 0) {
if (workers.has(workerId)) {
console.log(`工具进程 ${workerId} 已在运行`);
return;
}
const noAutoRestartExitCodeSet = new Set([0]);
(env.GEEKGEEKRUND_NO_AUTO_RESTART_EXIT_CODE ?? '')
.split(',')
@@ -512,11 +475,3 @@ process.on('SIGINT', () => {
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
}
)
await sendToDaemon(
{
type: 'gui-register'
},
{
needCallback: true
}
)
})
}
@@ -51,8 +51,22 @@ export const pageMapByName: {
} = {}
async function periodPushCurrentPageScreenshot () {
try {
if (pageMapByName.boss?.isClosed()) {
return
}
const shouldExit = await checkShouldExit()
if (shouldExit) {
return
}
try {
await pushCurrentPageScreenshot(pageMapByName.boss)
}
catch (err) {
if (err?.message?.includes(`PAGE_CLOSED`)) {
return
}
}
setTimeout(periodPushCurrentPageScreenshot, SCREENSHOT_INTERVAL_MS)
}
catch {}
+9
View File
@@ -3,6 +3,15 @@ import { runCommon } from './features/run-common';
import { launchDaemon } from './flow/OPEN_SETTING_WINDOW/launch-daemon';
import { connectToDaemon } from './flow/OPEN_SETTING_WINDOW/connect-to-daemon';
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 commandlineArgs = minimist(isUiDev ? process.argv.slice(2) : process.argv.slice(1))
console.log(commandlineArgs)
+10 -3
View File
@@ -26,6 +26,9 @@ export async function pushCurrentPageScreenshot (page) {
}
})
} catch (err) {
if (err?.message?.includes('Session closed')) {
throw new Error(`PAGE_CLOSED`)
}
// 截图失败不应影响主流程
console.warn('[READ_NO_REPLY_AUTO_REMINDER] pushCurrentPageScreenshot error', err)
}
@@ -38,9 +41,6 @@ export class PeriodPushCurrentPageScreenshotPlugin {
(page) => {
async function periodPushCurrentPageScreenshot () {
try {
if (!page) {
return
}
if (page.isClosed()) {
return
}
@@ -48,7 +48,14 @@ export class PeriodPushCurrentPageScreenshotPlugin {
if (shouldExit) {
return
}
try {
await pushCurrentPageScreenshot(page)
}
catch (err) {
if (err?.message?.includes(`PAGE_CLOSED`)) {
return
}
}
setTimeout(periodPushCurrentPageScreenshot, SCREENSHOT_INTERVAL_MS)
}
catch {}