fix: 修复未处理的Promise拒绝和文件流错误导致的进程崩溃
- 添加全局异常捕获(uncaughtException, unhandledRejection)防止进程意外退出 - 修复请求合并逻辑中Promise链处理不当导致的Unhandled Promise Rejection - 为文件读取流添加错误监听,防止文件系统异常导致进程崩溃 - 更新README.md文档以反映稳定性修复 - 将*.log添加到.gitignore忽略日志文件
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
.env
|
.env
|
||||||
cache/
|
cache/
|
||||||
|
*.log
|
||||||
@@ -58,4 +58,8 @@
|
|||||||
- 用户体验优化:当在浏览器中直接访问 API (Accept: text/html) 时,返回一个带有加载动画的 HTML 页面,解决等待过程中的白屏问题。
|
- 用户体验优化:当在浏览器中直接访问 API (Accept: text/html) 时,返回一个带有加载动画的 HTML 页面,解决等待过程中的白屏问题。
|
||||||
- 并发健壮性提升:
|
- 并发健壮性提升:
|
||||||
- 实现 **请求合并 (Request Coalescing)**:当多个客户端同时请求同一个未缓存的 URL 时,复用同一个回源请求,避免瞬间高并发流量击穿上游 (Thundering Herd)。
|
- 实现 **请求合并 (Request Coalescing)**:当多个客户端同时请求同一个未缓存的 URL 时,复用同一个回源请求,避免瞬间高并发流量击穿上游 (Thundering Herd)。
|
||||||
- 实现 **原子化缓存写入 (Atomic Write)**:使用“写临时文件 + 重命名”策略,确保缓存文件在写入过程中不会被读取到不完整的数据,彻底解决并发读写导致的文件损坏问题。
|
- 实现 **原子化缓存写入 (Atomic Write)**:使用“写临时文件 + 重命名”策略,确保缓存文件在写入过程中不会被读取到不完整的数据,彻底解决并发读写导致的文件损坏问题。
|
||||||
|
- **稳定性修复**:
|
||||||
|
- 修复了在请求合并逻辑中因 Promise 链处理不当导致的 `Unhandled Promise Rejection` 崩溃问题。
|
||||||
|
- 增加了文件流读取的错误监听,防止因文件系统异常导致的进程退出。
|
||||||
|
- 增加了全局异常捕获 (`uncaughtException`, `unhandledRejection`),确保服务在极端异常下记录日志而不崩溃。
|
||||||
|
|||||||
28
server.js
28
server.js
@@ -6,6 +6,15 @@ const path = require('path');
|
|||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const net = require('net');
|
const net = require('net');
|
||||||
|
|
||||||
|
// 全局错误捕获,防止进程退出
|
||||||
|
process.on('uncaughtException', (err) => {
|
||||||
|
console.error('[FATAL] Uncaught Exception:', err);
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (reason, promise) => {
|
||||||
|
console.error('[FATAL] Unhandled Rejection:', reason);
|
||||||
|
});
|
||||||
|
|
||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = Number(process.env.PORT) || 11489;
|
const PORT = Number(process.env.PORT) || 11489;
|
||||||
@@ -301,6 +310,10 @@ async function handleProxyRequest(res, upstreamUrl, targetUrl) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const stream = fs.createReadStream(dataPath);
|
const stream = fs.createReadStream(dataPath);
|
||||||
|
stream.on('error', (streamErr) => {
|
||||||
|
console.error(`[stream-error] ${streamErr.message}`);
|
||||||
|
if (!res.headersSent) res.end();
|
||||||
|
});
|
||||||
stream.pipe(res);
|
stream.pipe(res);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
@@ -319,13 +332,12 @@ async function handleProxyRequest(res, upstreamUrl, targetUrl) {
|
|||||||
console.log(`[coalesce-hit] joining pending request for ${upstreamUrl}`);
|
console.log(`[coalesce-hit] joining pending request for ${upstreamUrl}`);
|
||||||
resultPromise = pendingRequests.get(key);
|
resultPromise = pendingRequests.get(key);
|
||||||
} else {
|
} else {
|
||||||
resultPromise = fetchAndCache(upstreamUrl, targetUrl, key);
|
// 创建 Promise 链,确保 finally 包含在内,防止 Unhandled Rejection
|
||||||
|
resultPromise = fetchAndCache(upstreamUrl, targetUrl, key)
|
||||||
|
.finally(() => {
|
||||||
|
pendingRequests.delete(key);
|
||||||
|
});
|
||||||
pendingRequests.set(key, resultPromise);
|
pendingRequests.set(key, resultPromise);
|
||||||
|
|
||||||
// 无论成功失败,结束后移除 map
|
|
||||||
resultPromise.finally(() => {
|
|
||||||
pendingRequests.delete(key);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await resultPromise;
|
const result = await resultPromise;
|
||||||
@@ -353,6 +365,10 @@ async function handleProxyRequest(res, upstreamUrl, targetUrl) {
|
|||||||
res.set('Cache-Control', 'public, max-age=315360000, immutable');
|
res.set('Cache-Control', 'public, max-age=315360000, immutable');
|
||||||
res.type(meta.contentType);
|
res.type(meta.contentType);
|
||||||
const stream = fs.createReadStream(dataPath);
|
const stream = fs.createReadStream(dataPath);
|
||||||
|
stream.on('error', (streamErr) => {
|
||||||
|
console.error(`[stream-error] ${streamErr.message}`);
|
||||||
|
if (!res.headersSent) res.end();
|
||||||
|
});
|
||||||
stream.pipe(res);
|
stream.pipe(res);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user