Promise.race对synchrony自定义函数失效,超时未触发异常排查
问题出在synchrony.deobfuscateNode返回的Promise内部大概率是同步阻塞的CPU密集型操作——虽然它包装成了Promise,但内部逻辑是同步执行完才resolve,直接把Node.js的单线程事件循环卡死了。
Node.js的事件循环机制是:先执行完当前所有同步任务,才会去处理定时器、I/O回调这类异步队列的任务。所以当反混淆操作在同步占用CPU时,你设置的超时setTimeout回调根本没机会触发,必须等反混淆完全结束,定时器才会执行,但这时候Promise.race里的反混淆Promise已经resolve了,超时逻辑自然失效。
而你用testPromise测试时,内部是setTimeout异步任务,不会阻塞事件循环,所以超时定时器能正常触发,Promise.race能按预期工作。
要解决这个问题,核心是把CPU密集的反混淆操作移出主线程,避免阻塞事件循环,常用的两种方式:
1. 使用Worker Threads(推荐)
Node.js的worker_threads模块可以把CPU密集任务放到单独的工作线程执行,主线程的事件循环不会被阻塞,超时逻辑就能正常运行。
步骤1:创建工作线程文件(比如deobfuscate.worker.js)
const { parentPort, workerData } = require('worker_threads'); const { Deobfuscator } = require('./synchrony'); async function run() { try { const synchrony = new Deobfuscator(); const result = await synchrony.deobfuscateNode(workerData.ast); parentPort.postMessage({ success: true, data: result }); } catch (err) { parentPort.postMessage({ success: false, error: err.message }); } } run();
步骤2:修改主程序的callDeobfuscator函数
const { Worker } = require('worker_threads'); const { timeout } = require('../../../config'); const { parse } = require('../../utils/parser'); async function callDeobfuscator(ast) { const withTimeout = (promise) => { let timeoutPid; const timeoutPromise = new Promise((_, reject) => timeoutPid = setTimeout(() => reject(new Error('反混淆超时')), timeout)); return Promise.race([promise, timeoutPromise]) .finally(() => timeoutPid && clearTimeout(timeoutPid)); }; // 用工作线程执行反混淆 const workerPromise = new Promise((resolve, reject) => { const worker = new Worker('./deobfuscate.worker.js', { workerData: { ast } }); worker.on('message', (msg) => { worker.terminate(); // 任务完成后终止工作线程 if (msg.success) resolve(msg.data); else reject(new Error(msg.error)); }); worker.on('error', reject); worker.on('exit', (code) => { if (code !== 0) reject(new Error(`工作线程异常退出,代码:${code}`)); }); }); return await withTimeout(workerPromise); } // 原读取文件逻辑补充错误处理 const fs = require('fs'); fs.readFile('script.js', 'utf8', async function(err, source){ if (err) throw err; let ast = parse(source); try { ast = await callDeobfuscator(ast); console.log(ast); } catch (err) { console.error('操作失败:', err.message); } });
2. 使用子进程(child_process)
如果你的Node.js版本较低(低于10.5.0,不支持worker_threads),可以用child_process.fork创建子进程来执行反混淆逻辑,原理和worker threads类似,都是把任务放到独立的执行环境,避免阻塞主线程。
可以检查一下synchrony的官方文档,看看它是否内置了超时配置选项,或者有没有提供非阻塞的反混淆API,如果有的话,直接用内置功能会更简单。
内容的提问来源于stack exchange,提问作者GRoutar

