mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 09:38:02 +00:00
fix(memory): extract req properties to avoid closure capturing entire request object
Problem: - usageCallback closures referenced req.apiKey.id and req.rateLimitInfo - This caused entire req object (including req.body with images) to be retained - Base64 images in messages accumulated in memory (290 images = 26MB) Solution: - Extract needed properties before callback: _apiKeyId, _rateLimitInfo, etc. - Closures now capture small local variables instead of entire req object - Enables proper GC of request bodies after stream completion Results verified via heapdump analysis: - String memory: 144MB -> 24MB (-83%) - Base64 images: 290 -> 0 (-100%) - Heapdump size: 57MB -> 28MB (-51%) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -416,11 +416,18 @@ async function handleMessagesRequest(req, res) {
|
|||||||
// 根据账号类型选择对应的转发服务并调用
|
// 根据账号类型选择对应的转发服务并调用
|
||||||
if (accountType === 'claude-official') {
|
if (accountType === 'claude-official') {
|
||||||
// 官方Claude账号使用原有的转发服务(会自己选择账号)
|
// 官方Claude账号使用原有的转发服务(会自己选择账号)
|
||||||
|
// 🧹 内存优化:提取需要的值,避免闭包捕获整个 req 对象
|
||||||
|
const _apiKeyId = req.apiKey.id
|
||||||
|
const _rateLimitInfo = req.rateLimitInfo
|
||||||
|
const _requestBody = req.body // 传递后清除引用
|
||||||
|
const _apiKey = req.apiKey
|
||||||
|
const _headers = req.headers
|
||||||
|
|
||||||
await claudeRelayService.relayStreamRequestWithUsageCapture(
|
await claudeRelayService.relayStreamRequestWithUsageCapture(
|
||||||
req.body,
|
_requestBody,
|
||||||
req.apiKey,
|
_apiKey,
|
||||||
res,
|
res,
|
||||||
req.headers,
|
_headers,
|
||||||
(usageData) => {
|
(usageData) => {
|
||||||
// 回调函数:当检测到完整usage数据时记录真实token使用量
|
// 回调函数:当检测到完整usage数据时记录真实token使用量
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -470,13 +477,13 @@ async function handleMessagesRequest(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apiKeyService
|
apiKeyService
|
||||||
.recordUsageWithDetails(req.apiKey.id, usageObject, model, usageAccountId, 'claude')
|
.recordUsageWithDetails(_apiKeyId, usageObject, model, usageAccountId, 'claude')
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logger.error('❌ Failed to record stream usage:', error)
|
logger.error('❌ Failed to record stream usage:', error)
|
||||||
})
|
})
|
||||||
|
|
||||||
queueRateLimitUpdate(
|
queueRateLimitUpdate(
|
||||||
req.rateLimitInfo,
|
_rateLimitInfo,
|
||||||
{
|
{
|
||||||
inputTokens,
|
inputTokens,
|
||||||
outputTokens,
|
outputTokens,
|
||||||
@@ -501,11 +508,18 @@ async function handleMessagesRequest(req, res) {
|
|||||||
)
|
)
|
||||||
} else if (accountType === 'claude-console') {
|
} else if (accountType === 'claude-console') {
|
||||||
// Claude Console账号使用Console转发服务(需要传递accountId)
|
// Claude Console账号使用Console转发服务(需要传递accountId)
|
||||||
|
// 🧹 内存优化:提取需要的值
|
||||||
|
const _apiKeyIdConsole = req.apiKey.id
|
||||||
|
const _rateLimitInfoConsole = req.rateLimitInfo
|
||||||
|
const _requestBodyConsole = req.body
|
||||||
|
const _apiKeyConsole = req.apiKey
|
||||||
|
const _headersConsole = req.headers
|
||||||
|
|
||||||
await claudeConsoleRelayService.relayStreamRequestWithUsageCapture(
|
await claudeConsoleRelayService.relayStreamRequestWithUsageCapture(
|
||||||
req.body,
|
_requestBodyConsole,
|
||||||
req.apiKey,
|
_apiKeyConsole,
|
||||||
res,
|
res,
|
||||||
req.headers,
|
_headersConsole,
|
||||||
(usageData) => {
|
(usageData) => {
|
||||||
// 回调函数:当检测到完整usage数据时记录真实token使用量
|
// 回调函数:当检测到完整usage数据时记录真实token使用量
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -556,7 +570,7 @@ async function handleMessagesRequest(req, res) {
|
|||||||
|
|
||||||
apiKeyService
|
apiKeyService
|
||||||
.recordUsageWithDetails(
|
.recordUsageWithDetails(
|
||||||
req.apiKey.id,
|
_apiKeyIdConsole,
|
||||||
usageObject,
|
usageObject,
|
||||||
model,
|
model,
|
||||||
usageAccountId,
|
usageAccountId,
|
||||||
@@ -567,7 +581,7 @@ async function handleMessagesRequest(req, res) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
queueRateLimitUpdate(
|
queueRateLimitUpdate(
|
||||||
req.rateLimitInfo,
|
_rateLimitInfoConsole,
|
||||||
{
|
{
|
||||||
inputTokens,
|
inputTokens,
|
||||||
outputTokens,
|
outputTokens,
|
||||||
@@ -642,11 +656,18 @@ async function handleMessagesRequest(req, res) {
|
|||||||
}
|
}
|
||||||
} else if (accountType === 'ccr') {
|
} else if (accountType === 'ccr') {
|
||||||
// CCR账号使用CCR转发服务(需要传递accountId)
|
// CCR账号使用CCR转发服务(需要传递accountId)
|
||||||
|
// 🧹 内存优化:提取需要的值
|
||||||
|
const _apiKeyIdCcr = req.apiKey.id
|
||||||
|
const _rateLimitInfoCcr = req.rateLimitInfo
|
||||||
|
const _requestBodyCcr = req.body
|
||||||
|
const _apiKeyCcr = req.apiKey
|
||||||
|
const _headersCcr = req.headers
|
||||||
|
|
||||||
await ccrRelayService.relayStreamRequestWithUsageCapture(
|
await ccrRelayService.relayStreamRequestWithUsageCapture(
|
||||||
req.body,
|
_requestBodyCcr,
|
||||||
req.apiKey,
|
_apiKeyCcr,
|
||||||
res,
|
res,
|
||||||
req.headers,
|
_headersCcr,
|
||||||
(usageData) => {
|
(usageData) => {
|
||||||
// 回调函数:当检测到完整usage数据时记录真实token使用量
|
// 回调函数:当检测到完整usage数据时记录真实token使用量
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -696,13 +717,13 @@ async function handleMessagesRequest(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apiKeyService
|
apiKeyService
|
||||||
.recordUsageWithDetails(req.apiKey.id, usageObject, model, usageAccountId, 'ccr')
|
.recordUsageWithDetails(_apiKeyIdCcr, usageObject, model, usageAccountId, 'ccr')
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logger.error('❌ Failed to record CCR stream usage:', error)
|
logger.error('❌ Failed to record CCR stream usage:', error)
|
||||||
})
|
})
|
||||||
|
|
||||||
queueRateLimitUpdate(
|
queueRateLimitUpdate(
|
||||||
req.rateLimitInfo,
|
_rateLimitInfoCcr,
|
||||||
{
|
{
|
||||||
inputTokens,
|
inputTokens,
|
||||||
outputTokens,
|
outputTokens,
|
||||||
|
|||||||
Reference in New Issue
Block a user