mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
fix(memory): clear bodyString after req.write() to prevent closure capture
Additional memory optimizations: - Set bodyString = null after req.write() in both stream and non-stream requests - Use let instead of const for bodyString to allow nullifying - Store non-stream originalBodyString in bodyStore to avoid closure capture - Clean up bodyStore in finally block for non-stream requests This prevents V8 closures (res.on handlers) from retaining large request body strings until stream completion. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -382,6 +382,7 @@ class ClaudeRelayService {
|
|||||||
let queueLockAcquired = false
|
let queueLockAcquired = false
|
||||||
let queueRequestId = null
|
let queueRequestId = null
|
||||||
let selectedAccountId = null
|
let selectedAccountId = null
|
||||||
|
let bodyStoreIdNonStream = null // 🧹 在 try 块外声明,以便 finally 清理
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 调试日志:查看API Key数据
|
// 调试日志:查看API Key数据
|
||||||
@@ -542,8 +543,10 @@ class ClaudeRelayService {
|
|||||||
|
|
||||||
const isRealClaudeCodeRequest = this._isActualClaudeCodeRequest(requestBody, clientHeaders)
|
const isRealClaudeCodeRequest = this._isActualClaudeCodeRequest(requestBody, clientHeaders)
|
||||||
const processedBody = this._processRequestBody(requestBody, account)
|
const processedBody = this._processRequestBody(requestBody, account)
|
||||||
// 🧹 内存优化:存储序列化字符串用于重试,避免重复转换工具名
|
// 🧹 内存优化:存储到 bodyStore,避免闭包捕获
|
||||||
const originalBodyString = JSON.stringify(processedBody)
|
const originalBodyString = JSON.stringify(processedBody)
|
||||||
|
bodyStoreIdNonStream = ++this._bodyStoreIdCounter
|
||||||
|
this.bodyStore.set(bodyStoreIdNonStream, originalBodyString)
|
||||||
|
|
||||||
// 获取代理配置
|
// 获取代理配置
|
||||||
const proxyAgent = await this._getProxyAgent(accountId)
|
const proxyAgent = await this._getProxyAgent(accountId)
|
||||||
@@ -571,12 +574,12 @@ class ClaudeRelayService {
|
|||||||
let shouldRetry = false
|
let shouldRetry = false
|
||||||
|
|
||||||
do {
|
do {
|
||||||
// 🧹 每次重试从字符串解析新对象,避免使用被修改的 body
|
// 🧹 每次重试从 bodyStore 解析新对象,避免闭包捕获
|
||||||
let retryRequestBody
|
let retryRequestBody
|
||||||
try {
|
try {
|
||||||
retryRequestBody = JSON.parse(originalBodyString)
|
retryRequestBody = JSON.parse(this.bodyStore.get(bodyStoreIdNonStream))
|
||||||
} catch (parseError) {
|
} catch (parseError) {
|
||||||
logger.error(`❌ Failed to parse originalBodyString for retry: ${parseError.message}`)
|
logger.error(`❌ Failed to parse body for retry: ${parseError.message}`)
|
||||||
throw new Error(`Request body parse failed: ${parseError.message}`)
|
throw new Error(`Request body parse failed: ${parseError.message}`)
|
||||||
}
|
}
|
||||||
response = await this._makeClaudeRequest(
|
response = await this._makeClaudeRequest(
|
||||||
@@ -916,6 +919,10 @@ class ClaudeRelayService {
|
|||||||
)
|
)
|
||||||
throw error
|
throw error
|
||||||
} finally {
|
} finally {
|
||||||
|
// 🧹 清理 bodyStore
|
||||||
|
if (bodyStoreIdNonStream !== null) {
|
||||||
|
this.bodyStore.delete(bodyStoreIdNonStream)
|
||||||
|
}
|
||||||
// 📬 释放用户消息队列锁(兜底,正常情况下已在请求发送后提前释放)
|
// 📬 释放用户消息队列锁(兜底,正常情况下已在请求发送后提前释放)
|
||||||
if (queueLockAcquired && queueRequestId && selectedAccountId) {
|
if (queueLockAcquired && queueRequestId && selectedAccountId) {
|
||||||
try {
|
try {
|
||||||
@@ -1431,7 +1438,7 @@ class ClaudeRelayService {
|
|||||||
return prepared.abortResponse
|
return prepared.abortResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
const { bodyString, headers, isRealClaudeCode, toolNameMap } = prepared
|
let { bodyString, headers, isRealClaudeCode, toolNameMap } = prepared
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// 支持自定义路径(如 count_tokens)
|
// 支持自定义路径(如 count_tokens)
|
||||||
@@ -1545,6 +1552,8 @@ class ClaudeRelayService {
|
|||||||
|
|
||||||
// 写入请求体
|
// 写入请求体
|
||||||
req.write(bodyString)
|
req.write(bodyString)
|
||||||
|
// 🧹 内存优化:立即清空 bodyString 引用,避免闭包捕获
|
||||||
|
bodyString = null
|
||||||
req.end()
|
req.end()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1846,7 +1855,7 @@ class ClaudeRelayService {
|
|||||||
return prepared.abortResponse
|
return prepared.abortResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
const { bodyString, headers, toolNameMap } = prepared
|
let { bodyString, headers, toolNameMap } = prepared
|
||||||
const toolNameStreamTransformer = this._createToolNameStripperStreamTransformer(
|
const toolNameStreamTransformer = this._createToolNameStripperStreamTransformer(
|
||||||
streamTransformer,
|
streamTransformer,
|
||||||
toolNameMap
|
toolNameMap
|
||||||
@@ -2627,6 +2636,8 @@ class ClaudeRelayService {
|
|||||||
|
|
||||||
// 写入请求体
|
// 写入请求体
|
||||||
req.write(bodyString)
|
req.write(bodyString)
|
||||||
|
// 🧹 内存优化:立即清空 bodyString 引用,避免闭包捕获
|
||||||
|
bodyString = null
|
||||||
req.end()
|
req.end()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user