-
-
Notifications
You must be signed in to change notification settings - Fork 27
fix(sentry): optimize HttpClient memory usage and error handling #905
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- Replace Closure storage with direct parameter arrays to reduce memory overhead - Improve parameter cleanup in finally block to prevent memory leaks - Remove unused Closure import - Add clearer comments for loop lifecycle management - Streamline channel initialization and cleanup logic
|
Caution Review failedThe pull request is closed. Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. Walkthrough将 HttpClient 的异步分发从将闭包入通道改为将参数数组入通道; Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Client as 客户端
participant Http as HttpClient
participant Channel as Channel
participant Worker as Worker 协程
participant Parent as parent::sendRequest
Client->>Http: sendRequest(...args)
alt loop 未启动
Http->>Http: loop() 懒初始化 (tap(new Channel(...)))
Http->>Worker: 启动 Worker 协程 & waitingWorkerExit 协程
end
Http->>Channel: push([$request, $options]) // 返回 202 Queued
loop: 处理队列
Worker->>Channel: pop()
alt 收到参数数组
Worker->>Worker: 构造 callable = fn() => Parent(...$args)
alt 并发控制存在
Worker->>Worker: concurrent->create(callable)
else
Worker->>Worker: Coroutine::create(callable)
end
Worker->>Worker: finally 清理 callable 与 args
else 通道关闭或 pop 失败
Worker-->>Worker: 退出循环
end
end
Worker->>Http: 触发 WORKER_EXIT
Note right of Http: waitingWorkerExit 监听 WORKER_EXIT,调用 close() 并设置 workerExited = true
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 PHPStan (2.1.17)At least one path must be specified to analyse. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR optimizes the HttpClient's memory usage by replacing Closure-based parameter storage with direct parameter arrays and improves error handling. The changes aim to reduce memory overhead and prevent potential memory leaks in the Sentry HTTP client implementation.
- Replace Closure storage with direct parameter arrays to reduce memory footprint
- Streamline channel initialization and cleanup logic for better resource management
- Add descriptive comments to improve code maintainability
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/sentry/src/HttpClient/HttpClient.php (1)
30-31: 必须修复:等待协程属性类型与 Coroutine::create 返回值不匹配Coroutine::create 返回协程 ID (int),当前声明为 ?Coroutine 会在启用严格类型的 PHP8+ 时抛出 TypeError;将属性改为 ?int 并重命名为 waitingWorkerExitId,更新赋值逻辑。
文件:src/sentry/src/HttpClient/HttpClient.php 行 30-31;同样适用于行 109-117。
- protected ?Coroutine $waitingWorkerExit = null; + protected ?int $waitingWorkerExitId = null;- $this->waitingWorkerExit ??= Coroutine::create(function () { + if ($this->waitingWorkerExitId === null) { + $this->waitingWorkerExitId = Coroutine::create(function () { try { CoordinatorManager::until(Constants::WORKER_EXIT)->yield(); $this->close(); $this->workerExited = true; } catch (Throwable) { } - }); + }); + }
🧹 Nitpick comments (2)
src/sentry/src/HttpClient/HttpClient.php (2)
66-69: 使用全等比较以避免潜在类型转换问题
if ($this->chan != null)建议改为严格比较:- if ($this->chan != null) { + if ($this->chan !== null) {
82-103: 可将双层 while 合并为单层以简化控制流,并避免异常分支后的空转当前
catch (Throwable) { break; }仅跳出内层 while,随后外层 while 立即再次进入新一轮内层 while,可能产生无意义的循环。可考虑改为如下结构(示意):- while (true) { - while (true) { - $params = $this->chan?->pop(); - if (! $params) { break 2; } - try { - // ... - } catch (Throwable) { - break; - } finally { - $params = null; - } - } - } + while (($params = $this->chan?->pop()) !== null) { + try { + // ... + } catch (Throwable) { + // 视需要决定是 continue 还是直接 break 整体循环 + continue; + } finally { + $params = null; + } + }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/sentry/src/HttpClient/HttpClient.php(2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: Test on PHP 8.3 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.1 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.1 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.2 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.2 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.3 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.3 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.1 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.2 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.3 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.2 with Swoole 5.1.7
🔇 Additional comments (1)
src/sentry/src/HttpClient/HttpClient.php (1)
76-83: 队列化与惰性初始化设计方向正确惰性启动 worker + Channel 有助于在空闲期节省资源,并使热路径
sendRequest的责任清晰。与“显式参数数组”结合,可减少闭包捕获造成的内存占用。保持这一思路。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/sentry/src/HttpClient/HttpClient.php (1)
32-33: 属性类型错误:Coroutine::create 返回 int|false,声明为 ?Coroutine 将在赋值时报 TypeError
Hyperf\Engine\Coroutine::create()返回协程 ID(int)或 false;当前把其赋给?Coroutine会触发类型错误。建议改为?int,并将 false 转为 null。- protected ?Coroutine $waitingWorkerExit = null; + protected ?int $waitingWorkerExit = null;- $this->waitingWorkerExit ??= Coroutine::create(function () { + $this->waitingWorkerExit ??= (Coroutine::create(function () { try { CoordinatorManager::until(Constants::WORKER_EXIT)->yield(); $this->close(); $this->workerExited = true; } catch (Throwable) { } - }); + }) ?: null);Also applies to: 104-106
♻️ Duplicate comments (2)
src/sentry/src/HttpClient/HttpClient.php (2)
51-56: 修复静默丢包并避免阻塞:worker 已退出或 push 失败时回退为同步发送,并为 push 设置短超时当前在 worker 退出或通道尚未/已被关闭时,
$this->chan?->push(...)会成为空操作,仍返回 202,导致请求被悄然丢弃;同时无超时的push可能在高压下阻塞请求路径。建议如下修改:public function sendRequest(Request $request, Options $options): Response { - // Start the loop if not started yet - $this->loop(); - - // Push the request to the channel - $this->chan?->push([$request, $options]); + // 启动消费循环 + $this->loop(); + + // worker 已退出或通道不可用时,回退为同步发送,避免静默丢包 + if ($this->workerExited || ! $this->chan) { + return parent::sendRequest($request, $options); + } + + // 带极短超时的 push;失败则回退为同步发送 + if ($this->chan->push([$request, $options], 0.001) === false) { + return parent::sendRequest($request, $options); + } return new Response(202, ['X-Sentry-Request-Status' => ['Queued']], ''); }
84-89: 在子协程内捕获 parent::sendRequest 异常,并配合 WaitGroup 标记完成未捕获的异常会污染日志且可能占用并发槽位;同时补充 WaitGroup 记账,确保 close 时可等待收敛。
- try { - $callable = fn () => parent::sendRequest(...$params); + try { + $this->wg?->add(); + $callable = function () use ($params) { + try { + parent::sendRequest(...$params); + } catch (Throwable $e) { + // 可替换为注入的 Logger + error_log(sprintf('[Sentry HttpClient] sendRequest failed: %s', (string) $e)); + } finally { + $this->wg?->done(); + } + }; if ($this->concurrent) { $this->concurrent->create($callable); } else { Coroutine::create($callable); }
🧹 Nitpick comments (2)
src/sentry/src/HttpClient/HttpClient.php (2)
78-96: 双层 while + break 2 容易造成控制流复杂与潜在自旋,建议合并为单循环逻辑等价且更易读,也避免
catch后简单break只跳出内层循环导致外层空转。- try { - while (true) { - while (true) { - if ($chan->isClosing() || ! $params = $chan->pop()) { - break 2; - } - try { - // 调度... - } catch (Throwable) { - break; - } finally { - $params = null; - } - } - } + try { + while (! $chan->isClosing() && ($params = $chan->pop())) { + try { + // 调度... + } catch (Throwable) { + // 记录后继续消费下一条,避免提前退出 + continue; + } finally { + $params = null; + } + } } catch (Throwable $e) {
97-101: 不要吞掉异常:至少输出一次性错误空的
catch会隐藏真实问题,排障困难。最低限度记录错误(或注入 Logger)。- } catch (Throwable $e) { - } finally { + } catch (Throwable $e) { + error_log(sprintf('[Sentry HttpClient] worker loop crashed: %s', (string) $e)); + } finally { $this->close(); }如需,我可以补一版基于
Psr\Log\LoggerInterface的注入与使用。
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/sentry/src/HttpClient/HttpClient.php(2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Test on PHP 8.1 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.3 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.2 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.1 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.3 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.2 with Swoole 5.1.7
🔇 Additional comments (1)
src/sentry/src/HttpClient/HttpClient.php (1)
24-25: 引入 tap 合理用 tap 初始化并开启消费协程,语义清晰,减少样板代码。保持此做法。
… loop initialization
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (3)
src/sentry/src/HttpClient/HttpClient.php (3)
49-58: 请求可能在 worker 退出或 channel 关闭时静默丢失当
workerExited为 true 时,loop()会直接返回导致$this->chan保持为 null,此时$this->chan?->push()会成为空操作但仍返回 202 状态码,造成请求静默丢失。另外,当 Channel 满时push()会阻塞。建议添加超时和回退机制:
public function sendRequest(Request $request, Options $options): Response { // Start the loop if not started yet $this->loop(); + // Worker 已退出时直接同步发送 + if ($this->workerExited) { + return parent::sendRequest($request, $options); + } + - // Push the request to the channel - $this->chan?->push(func_get_args()); + // Push 到队列,设置短超时避免阻塞 + if (! $this->chan || $this->chan->push(func_get_args(), 0.001) === false) { + // 队列不可用或超时,回退为同步发送 + return parent::sendRequest($request, $options); + } return new Response(202, ['X-Sentry-Request-Status' => ['Queued']], ''); }
60-64: 关闭 Channel 前未等待并发任务完成
close()方法仅关闭 Channel 但未等待进行中的并发请求完成,可能导致尾部请求被截断。建议使用 WaitGroup 跟踪并等待任务:
+use Hyperf\Coroutine\WaitGroup; class HttpClient extends \Sentry\HttpClient\HttpClient { protected ?Channel $chan = null; protected ?Concurrent $concurrent = null; + protected ?WaitGroup $wg = null; public function __construct( string $sdkIdentifier, string $sdkVersion, protected int $channelSize = 65535, int $concurrentLimit = 1000, ) { parent::__construct($sdkIdentifier, $sdkVersion); + $this->wg = new WaitGroup(); if ($concurrentLimit > 0) { $this->concurrent = new Concurrent($concurrentLimit); } } public function close(): void { + // 等待进行中的任务完成(最多等待 100ms) + $this->wg?->wait(0.1); $this->chan?->close(); $this->chan = null; }并在派发任务时调用
$this->wg->add()和$this->wg->done()。
94-104: 未捕获的异常可能污染日志或中断并发槽位
parent::sendRequest()调用可能抛出异常,在协程内未被捕获会成为未处理异常。建议在协程内部捕获异常:
try { - $closure = fn () => parent::sendRequest(...$args); + $closure = function () use ($args) { + try { + return parent::sendRequest(...$args); + } catch (Throwable $e) { + // 记录错误但不中断其他请求 + // TODO: 添加日志记录 + } + }; if ($this->concurrent) { $this->concurrent->create($closure); } else { Coroutine::create($closure); }
🧹 Nitpick comments (2)
src/sentry/src/HttpClient/HttpClient.php (2)
75-82: 保留了注释掉的调试代码这段注释掉的内存监控代码可以移除,或者考虑通过配置开关来控制是否启用调试模式。
建议完全移除或改为可配置的调试模式:
- // Dump memory usage and channel size - // Coroutine::create(function () use ($chan) { - // while (! $chan->isClosing()) { - // dump('Memory Usage(MB): ' . memory_get_usage(true) / 1024 / 1024); - // dump('Channel Size: ' . $chan->getLength()); - // sleep(1); - // } - // }); + // 如需调试,可通过环境变量启用监控 + if ((bool) env('SENTRY_DEBUG_MODE', false)) { + Coroutine::create(function () use ($chan) { + while (! $chan->isClosing()) { + // 使用日志而非 dump + $this->logger?->debug('Sentry HttpClient stats', [ + 'memory_mb' => memory_get_usage(true) / 1024 / 1024, + 'channel_size' => $chan->getLength(), + ]); + sleep(1); + } + }); + }
88-92: 类型注解与实际使用不匹配虽然 PR 描述提到使用
array{0:Request,1:Options}类型,但func_get_args()返回的是普通数组,且pop()可能返回false或null。建议使用更精确的数组构造:
- $this->chan?->push(func_get_args()); + $this->chan?->push([$request, $options]);并在处理时添加类型检查:
- if ($chan->isClosing() || ! $args = $chan->pop()) { + if ($chan->isClosing() || ! ($args = $chan->pop()) || ! is_array($args)) { break 2; } + // 验证参数结构 + if (count($args) !== 2 || ! $args[0] instanceof Request || ! $args[1] instanceof Options) { + continue; + }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/sentry/publish/sentry.php(1 hunks)src/sentry/src/HttpClient/HttpClient.php(3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
- GitHub Check: Test on PHP 8.3 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.2 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.1 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.2 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.1 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.3 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.2 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.1 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.1 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.3 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.2 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.3 with Swoole 5.1.7
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/sentry/src/HttpClient/HttpClient.php (1)
114-120: 修复竞态:应先标记 workerExited 再 close(),避免窗口期被 loop() 重新初始化。- CoordinatorManager::until(Constants::WORKER_EXIT)->yield(); - $this->close(); - $this->workerExited = true; + CoordinatorManager::until(Constants::WORKER_EXIT)->yield(); + // 先标记退出,避免窗口期重建 Channel + $this->workerExited = true; + $this->close();
♻️ Duplicate comments (3)
src/sentry/src/HttpClient/HttpClient.php (3)
94-104: 子协程未捕获 parent::sendRequest 异常;建议在子协程内部 try/catch,并配合 WaitGroup 做 in-flight 统计。
- 未捕获异常会污染日志或中断并发槽位。
- 仅置空
$closure意义有限,建议同时释放$args引用。
[Suggest_essential_refactor]- try { - $closure = fn () => parent::sendRequest(...$args); + try { + $this->wg?->add(); + $closure = function () use ($args) { + try { + parent::sendRequest(...$args); + } catch (Throwable $e) { + // 可替换为 PSR-3 日志器 + error_log('Sentry HttpClient worker error: ' . $e->getMessage()); + } finally { + $this->wg?->done(); + } + }; if ($this->concurrent) { $this->concurrent->create($closure); } else { Coroutine::create($closure); } } catch (Throwable) { break; } finally { - $closure = null; + $closure = null; + $args = null; }
51-57: 避免静默丢包与请求路径阻塞:push 需带超时并在失败/已退出时回退同步发送。
- worker 已退出时
$this->chan为 null,当前实现会直接“空推送”但仍返回 202,导致事件丢弃。- 队列满时
push()会阻塞请求线程,需设置极短超时并回退到同步发送。建议改动:
- // Start the loop if not started yet - $this->loop(); - - // Push the request to the channel - $this->chan?->push(func_get_args()); - - return new Response(202, ['X-Sentry-Request-Status' => ['Queued']], ''); + // Start the loop if not started yet + $this->loop(); + + // Worker 已退出或 Channel 不可用时,回退同步发送,避免静默丢包 + if ($this->workerExited) { + return parent::sendRequest($request, $options); + } + + $payload = [$request, $options]; + $chan = $this->chan; + + // 带极短超时;失败(超时/已关闭)则回退同步发送 + if (! $chan || $chan->push($payload, 0.001) === false) { + return parent::sendRequest($request, $options); + } + + return new Response(202, ['X-Sentry-Request-Status' => ['Queued']], '');
60-64: close() 未等待在途任务完成,易“丢尾”。仅关闭 Channel 不等于并发任务完成;建议在 close 前对在途任务做短暂收敛等待(如 100ms,可配置)。
public function close(): void { - $this->chan?->close(); - $this->chan = null; + // 短暂等待在途任务收敛,避免尾部请求未完成即退出 + if (isset($this->wg)) { + $this->wg->wait(0.1); + } + $this->chan?->close(); + $this->chan = null; }为支撑上述修改,需要在类中增加 WaitGroup(见下方补充代码)。
🧹 Nitpick comments (2)
src/sentry/src/HttpClient/HttpClient.php (2)
88-93: pop 判定与双重 while 结构可读性/健壮性不足。
- 使用赋值表达式结合取反容易误解;明确区分“Channel 正在关闭”与“pop 失败(false)”更直观。
- // If the channel is closing or pop failed, exit the loop - if ($chan->isClosing() || ! $args = $chan->pop()) { - break 2; - } + // Channel 关闭或 pop 失败(false 表示超时/关闭)则退出 + if ($chan->isClosing()) { + break 2; + } + $args = $chan->pop(); + if ($args === false) { + break 2; + } + // 可选:断言参数形状 + // if (!is_array($args) || count($args) !== 2) { continue; }
107-111: 外层异常被吞没,建议至少记录 error 级日志,便于排障。- } catch (Throwable $e) { - } finally { + } catch (Throwable $e) { + error_log('Sentry HttpClient worker loop crashed: ' . $e->getMessage()); + } finally { $this->close(); }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/sentry/src/HttpClient/HttpClient.php(2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
- GitHub Check: Test on PHP 8.2 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.1 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.3 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.1 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.2 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.3 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.2 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.1 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.1 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.2 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.3 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.3 with Swoole 6.0.2
🔇 Additional comments (1)
src/sentry/src/HttpClient/HttpClient.php (1)
24-25: tap 引入合理,便于惰性初始化与副作用封装。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/sentry/src/HttpClient/HttpClient.php (2)
32-33: 属性类型错误:Coroutine::create 返回 int,属性声明为 ?Coroutine 将触发 TypeError应将
?Coroutine改为?int(或重命名为$waitingWorkerExitId),并相应调整赋值与判断。-protected ?Coroutine $waitingWorkerExit = null; +protected ?int $waitingWorkerExitId = null;以及:
- $this->waitingWorkerExit ??= Coroutine::create(function () { + $this->waitingWorkerExitId ??= Coroutine::create(function () {
114-120: 竞态:worker 退出时应先置位再关闭 Channel,避免被 loop() 重新初始化
$this->workerExited在close()之后才置位;若此间sendRequest()调用loop(),可能重新拉起通道/循环。先置位,再关通道。- CoordinatorManager::until(Constants::WORKER_EXIT)->yield(); - $this->close(); - $this->workerExited = true; + CoordinatorManager::until(Constants::WORKER_EXIT)->yield(); + $this->workerExited = true; + $this->close();
♻️ Duplicate comments (4)
src/sentry/src/HttpClient/HttpClient.php (4)
68-71: worker 已退出时 loop 提前返回会导致 sendRequest 静默丢包你在
sendRequest()中未判断该状态且仍返回 202,已在上文建议中通过“回退同步发送”修复。可在压测中验证:强制触发
WORKER_EXIT后,继续发送若干请求,确保无 202 且未真正发送的情况。
54-57: 避免静默丢包与阻塞:为 push 增加超时并在失败时回退同步发送,同时避免 func_get_args()
- 当前
?->push()在$this->chan === null时直接空操作且仍返回 202,导致“无声丢包”。Channel::push满载时会阻塞调用路径,建议增加极短超时,失败则回退parent::sendRequest(...)。- 建议用显式数组
[$request, $options],与声明的array{0:Request,1:Options}更一致,便于静态分析。public function sendRequest(Request $request, Options $options): Response { // Start the loop if not started yet $this->loop(); - // Push the request to the channel - $this->chan?->push(func_get_args()); + // worker 已退出:回退为同步发送,避免丢包 + if ($this->workerExited) { + return parent::sendRequest($request, $options); + } + + // Push 到队列;不存在/已关闭/超时则回退为同步发送 + $chan = $this->chan; + if (! $chan || $chan->push([$request, $options], 0.001) === false) { + return parent::sendRequest($request, $options); + } return new Response(202, ['X-Sentry-Request-Status' => ['Queued']], ''); }
60-64: close() 未等待在途任务完成,存在“尾部丢单”风险建议用 WaitGroup 统计/等待已派发协程,带上短超时后再关闭 Channel。
示例(仅示意,需在类中补充属性与初始化):
+use Hyperf\Coroutine\WaitGroup; ... - public function close(): void + public function close(): void { - $this->chan?->close(); + // 等待在途任务短暂收敛(示例 100ms) + $this->wg?->wait(0.1); + $this->chan?->close(); $this->chan = null; }派发处(见下条评论中的 diff)在创建子协程前
add(1),子协程finally中done()。
94-99: 子协程内未捕获的异常会污染日志并占用并发槽位在
$callable内部捕获Throwable,记录日志或吞掉。- $callable = fn () => parent::sendRequest(...$args); + $callable = function () use ($args) { + try { + parent::sendRequest(...$args); + } catch (\Throwable $e) { + // TODO: 注入 logger 后记录到 error 级别,避免静默。 + } + };
🧹 Nitpick comments (3)
src/sentry/src/HttpClient/HttpClient.php (3)
75-82: 调试代码建议移除或加条件编译注释块中包含频繁
dump()/sleep示例,建议删除或用环境变量守护以免误启。- // Dump memory usage and channel size - // Coroutine::create(function () use ($chan) { - // while (! $chan->isClosing()) { - // dump('Memory Usage(MB): ' . memory_get_usage(true) / 1024 / 1024); - // dump('Channel Size: ' . $chan->getLength()); - // sleep(1); - // } - // }); + // Debug hooks (optional): keep disabled in production.
100-103: 调度异常不应中断循环
break会提前退出当前批次;更合理的是continue跳过坏消息。-} catch (Throwable) { - break; +} catch (Throwable) { + continue;
107-111: 吞异常不利于排障建议至少 trace 一条日志,或使用统一异常上报钩子。
-} catch (Throwable $e) { -} finally { +} catch (Throwable $e) { + // TODO: logger->error('Sentry worker loop crashed', ['exception' => $e]); +} finally { $this->close(); }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/sentry/src/HttpClient/HttpClient.php(2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: Test on PHP 8.1 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.1 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.2 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.2 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.3 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.2 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.1 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.1 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.3 with Swoole 5.1.7
🔇 Additional comments (3)
src/sentry/src/HttpClient/HttpClient.php (3)
24-25: 引入 tap 用法合理与下方 lazy-init 逻辑契合,无异议。
36-47: 构造函数并发限制参数 OK
$concurrentLimit>0 时才初始化Concurrent,符合期望。
51-53: 在发送前确保 loop 启动的做法正确此处保持 idempotent,没问题。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/sentry/src/HttpClient/HttpClient.php (1)
32-33: 致命:属性类型与 Coroutine::create 返回值不匹配,将触发类型错误
Coroutine::create()返回int|false,但$waitingWorkerExit被声明为?Coroutine并被赋值为Coroutine::create(...)的返回值,这会在运行时抛出TypeError。建议用布尔哨兵或存协程 ID。- protected ?Coroutine $waitingWorkerExit = null; + // 仅作“是否已启动等待协程”的哨兵位 + protected bool $waitingWorkerExitStarted = false; @@ - $this->waitingWorkerExit ??= Coroutine::create(function () { + if (! $this->waitingWorkerExitStarted) { + Coroutine::create(function () { try { CoordinatorManager::until(Constants::WORKER_EXIT)->yield(); $this->close(); $this->workerExited = true; } catch (Throwable) { } - }); + }); + $this->waitingWorkerExitStarted = true; + }Also applies to: 111-118
♻️ Duplicate comments (2)
src/sentry/src/HttpClient/HttpClient.php (2)
51-57: 修复:避免在 worker 已退出/通道关闭/队列已满时静默丢包;push 应加超时并回退同步发送当前
push无返回值检查且workerExited时仍返回 202,事件会被悄然丢弃。建议:
- 若
workerExited为 true 或chan不可用/push失败(含关闭或超时),直接回退parent::sendRequest()。- 为
push设置极短超时,避免在热点路径阻塞。public function sendRequest(Request $request, Options $options): Response { // Start the loop if not started yet $this->loop(); - // Push the request to the channel - $chan = $this->chan; - $chan?->push([$request, $options]); + // worker 已退出或通道不可用/超时 -> 同步发送,避免丢包或阻塞 + if ($this->workerExited) { + return parent::sendRequest($request, $options); + } + $chan = $this->chan; + if (! $chan || $chan->push([$request, $options], 0.001) === false) { + return parent::sendRequest($request, $options); + } return new Response(202, ['X-Sentry-Request-Status' => ['Queued']], ''); }若需要,我可将超时阈值做成可配置参数(如
$this->pushTimeout)。请确认 Hyperf Engine
Channel::push的超时参数与返回值(true/false)行为与当前版本一致:Hyperf Engine Channel::push 是否支持超时参数?关闭或超时时返回值是什么?
88-96: 子协程内未捕获异常,可能污染日志/打断并发槽位;建议包装 callable 捕获异常
parent::sendRequest(...$args)若抛异常将在子协程内未被捕获。建议在callable内部 try/catch,必要时记录日志。- try { - $callable = fn () => parent::sendRequest(...$args); + try { + $callable = function () use ($args) { + try { + parent::sendRequest(...$args); + } catch (Throwable $e) { + // TODO: 根据项目日志方案记录错误,例如: + // $this->logger?->error('Sentry async send failed', ['exception' => $e]); + } + }; if ($this->concurrent) { $this->concurrent->create($callable); } else { Coroutine::create($callable); }
🧹 Nitpick comments (2)
src/sentry/src/HttpClient/HttpClient.php (2)
80-87: 简化双重 while 与 pop 判定;提升循环可读性当前双层
while+break 2可读性弱。可合并为单循环,并使 pop 判定更直观。- try { - while (true) { - while (true) { - // If the channel is closing or pop failed, exit the loop - if ($chan->isClosing() || ! $args = $chan->pop()) { - break 2; - } + try { + while (! $chan->isClosing() && ($args = $chan->pop()) !== false) { try { $callable = fn () => parent::sendRequest(...$args); if ($this->concurrent) { $this->concurrent->create($callable); } else { Coroutine::create($callable); } - } catch (Throwable) { - break; + } catch (Throwable) { + // 调度失败,跳过当前消息,继续后续 + continue; } finally { $callable = null; $args = null; } - } }Also applies to: 100-101
103-104: 顶层异常被吞掉,缺乏可观测性请至少记录一次异常,便于定位循环崩溃原因。
- } catch (Throwable $e) { - } finally { + } catch (Throwable $e) { + // TODO: 记录日志或打印异常栈 + // $this->logger?->error('Sentry HttpClient loop crashed', ['exception' => $e]); + // Coroutine::printException($e); // 若可用 + } finally { $this->close(); }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/sentry/src/HttpClient/HttpClient.php(3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
- GitHub Check: Test on PHP 8.1 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.1 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.2 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.2 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.3 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.3 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.1 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.1 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.2 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.3 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.3 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.2 with Swoole 5.1.7
🔇 Additional comments (3)
src/sentry/src/HttpClient/HttpClient.php (3)
24-25: 引入 tap() 没问题可读性更好,且便于在初始化时附带副作用。
63-67: 早退守卫合理,但需与 sendRequest 回退逻辑配套
workerExited的早退能避免无意义初始化;配合上条修改,确保此状态下请求走同步发送而非返回 202。
121-129: 保留 public close()(或提供兼容 public 代理)并在关闭前等待在途任务收敛
- 兼容性:src/sentry/src/HttpClient/HttpClient.php 中 close() 目前为 protected;仓库内未发现外部对该类实例调用 ->close(),但若历史版本或外部用户依赖 public close(),降级为 protected 会造成破坏性变更。恢复为 public 或新增 public 代理(例如 public function close(int $timeoutMs = 100): void)以维持 BC。
- 并发完整性:当前 close() 仅 close Channel,不等待正在执行的并发任务(Concurrent/Coroutine),可能导致未完成的“尾部请求”被中断。建议在派发任务前使用 WaitGroup->add(1),任务结束时 done(),并在 close()/代理中通过 WaitGroup->wait(timeout) 做短时收敛(或提供可配置的 drain/closeWithTimeout API)。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/sentry/src/HttpClient/HttpClient.php (1)
32-33: 属性类型错误:Coroutine::create 返回 int|false,非对象实例。
$waitingWorkerExit被声明为?Coroutine会在赋值Coroutine::create(...)时触发类型错误。应用此补丁修正类型:
- protected ?Coroutine $waitingWorkerExit = null; + protected int|false|null $waitingWorkerExit = null;
♻️ Duplicate comments (3)
src/sentry/src/HttpClient/HttpClient.php (3)
51-58: 避免静默丢包与热点路径阻塞:push 需带超时,且失败/已退出时回退同步发送。当 worker 已退出或通道满/关闭时,当前实现仍返回 202,导致请求被悄然丢弃;且无超时会在高负载下阻塞请求线程。
应用下面的补丁到 sendRequest 以修复:
public function sendRequest(Request $request, Options $options): Response { // Start the loop if not started yet $this->loop(); - // Push the request to the channel - $chan = $this->chan; - $chan?->push([$request, $options]); + // worker 已退出或队列不可用时回退为同步发送;push 使用极短超时避免阻塞 + if ($this->workerExited) { + return parent::sendRequest($request, $options); + } + + $chan = $this->chan; + if (! $chan || $chan->push([$request, $options], 0.001) === false) { + // 可选:依据 $chan?->errCode 记录 SWOOLE_CHANNEL_TIMEOUT / SWOOLE_CHANNEL_CLOSED + return parent::sendRequest($request, $options); + } return new Response(202, ['X-Sentry-Request-Status' => ['Queued']], ''); }Also applies to: 63-67
82-101: 简化双层 while 并显式 pop 判定,提升可读性与健壮性。当前
break 2/! $args = $chan->pop()判定不直观。可合并为单循环并使用!== false明确区分失败与空数组。应用此补丁:
- try { - while (true) { - while (true) { - // If the channel is closing or pop failed, exit the loop - if ($chan->isClosing() || ! $args = $chan->pop()) { - break 2; - } - try { - $callable = fn () => parent::sendRequest(...$args); - if ($this->concurrent) { - $this->concurrent->create($callable); - } else { - Coroutine::create($callable); - } - } catch (Throwable) { - break; - } finally { - $callable = null; - $args = null; - } - } - } + try { + while (! $chan->isClosing() && ($args = $chan->pop()) !== false) { + try { + $callable = fn () => parent::sendRequest(...$args); + if ($this->concurrent) { + $this->concurrent->create($callable); + } else { + Coroutine::create($callable); + } + } catch (Throwable) { + // 调度失败:跳过当前消息,继续 + continue; + } finally { + $callable = null; + $args = null; + } + }
88-96: 在子协程内捕获 parent::sendRequest 异常,并配合 WaitGroup 统计在途任务,close 前短暂等待收敛。避免未捕获异常污染日志或占用并发槽位;同时在关闭前等待短时间避免“尾部丢单”。
应用此补丁为派发处加计数并在 callable 内 done;同时在
close()里等待收敛(需搭配下方“额外变更”):- try { - $callable = fn () => parent::sendRequest(...$args); + try { + if ($this->wg) { $this->wg->add(1); } + $callable = function () use ($args) { + try { + parent::sendRequest(...$args); + } catch (Throwable $e) { + // TODO: 记录日志或打点 + } finally { + $this->wg?->done(); + } + }; if ($this->concurrent) { $this->concurrent->create($callable); } else { Coroutine::create($callable); } } catch (Throwable) { break;并在
close()中增加短暂等待(100ms 仅示例,可配为可调):public function close(): void { - $chan = $this->chan; + $this->wg?->wait(0.1); + $chan = $this->chan; $chan?->close();额外变更(不在上述选定行内):
// 顶部增加 use Hyperf\Coroutine\WaitGroup; // 类属性中增加 protected ?WaitGroup $wg = null; // 构造函数中初始化 $this->wg = new WaitGroup();
🧹 Nitpick comments (2)
src/sentry/src/HttpClient/HttpClient.php (2)
103-107: finally 内重复关闭通道,保留一次即可。
$chan->close()与$this->close()都会关闭通道;重复关闭无益且增加竞态复杂度。- } finally { - $chan->close(); - $this->close(); - } + } finally { + $this->close(); + }
85-86: 判定表达式建议加括号,避免运算符优先级歧义。若不采纳单循环重构,至少改为显式
($args = $chan->pop()) === false。- if ($chan->isClosing() || ! $args = $chan->pop()) { + if ($chan->isClosing() || ($args = $chan->pop()) === false) {
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/sentry/src/HttpClient/HttpClient.php(3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Test on PHP 8.1 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.2 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.3 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.3 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.2 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.1 with Swoole 5.1.7
🔇 Additional comments (2)
src/sentry/src/HttpClient/HttpClient.php (2)
24-25: 引入 tap 用得对,初始化管道时更简洁。
122-130: ```shell
#!/bin/bash
set -euo pipefailecho "=== 搜索仓库中 '->close(' 的调用 ==="
rg -nP --hidden --no-ignore-vcs -S -C3 '->\sclose\s(' || trueecho
echo "=== 搜索仓库中 'HttpClient' 的引用(类型、实例化、use 等) ==="
rg -nP --hidden --no-ignore-vcs -S -C3 '\bHttpClient\b' || trueecho
echo "=== 打印 src/sentry/src/HttpClient/HttpClient.php 前 240 行(含 close() 方法) ==="
sed -n '1,240p' src/sentry/src/HttpClient/HttpClient.php || true</blockquote></details> </blockquote></details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
huangdijia
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
代码审查报告
🎯 变更总结
- 内存优化:将
Closure对象替换为数组参数[$request, $options],减少内存开销 - 资源清理:改进了参数清理逻辑,在
finally块中显式设置$callable和$args为null - 代码重构:使用
tap函数简化通道初始化逻辑,提高代码可读性 - 方法可见性:将
close()方法从public改为protected - 注释改进:添加了更清晰的注释说明代码逻辑
⚠️ 潜在风险
- 破坏性变更:
close()方法可见性从public变为protected,可能影响外部直接调用此方法的代码 - 嵌套循环:新增了双重
while(true)循环结构,虽然有break 2跳出,但逻辑复杂度增加 - 内存泄漏风险:虽然添加了显式的
null赋值,但在高并发场景下仍需验证是否完全解决内存泄漏问题 - 异常处理:外层
catch (Throwable $e)捕获异常但未记录日志,可能导致错误难以追踪
💡 优化建议
-
日志记录:建议在异常捕获处添加日志记录
} catch (Throwable $e) { // 建议添加日志记录 logger()->error('HttpClient loop error: ' . $e->getMessage()); }
-
简化循环结构:考虑将双重 while 循环简化为单层循环,提高代码可读性
-
添加单元测试:针对内存优化和资源清理逻辑增加相应的测试用例
-
性能监控:建议保留被注释的内存监控代码作为调试选项,或提供配置开关
✅ 合规性检查
- ✅ 代码风格:符合 PSR-12 标准,导入语句按字母排序
- ✅ 命名规范:变量和方法命名清晰,符合项目惯例
- ✅ 类型声明:正确使用了 PHP 8+ 的类型声明特性
- ✅ 注释规范:添加的注释简洁明了,提高了代码可维护性
⚠️ 向后兼容:close()方法可见性变更可能影响向后兼容性
📝 总体评价
这个 PR 在内存优化和代码可读性方面有显著改进。主要优化了内存使用,通过将 Closure 替换为数组参数减少了内存开销。代码重构合理,使用了现代 PHP 特性。建议在合并前确认 close() 方法可见性变更的影响范围。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/sentry/src/HttpClient/HttpClient.php (1)
30-31: 属性类型错误:Coroutine::create 返回 int,赋值给 ?Coroutine 将抛 TypeError
protected ?Coroutine $waitingWorkerExit = null;随后用Coroutine::create(...)赋值(返回 int/false),会在运行时触发类型错误。该属性只用于“是否已启动监听”的语义,改为布尔更合适。- protected ?Coroutine $waitingWorkerExit = null; + // 标记是否已启动 worker 退出监听 + protected bool $waitingWorkerExitStarted = false; @@ - // Wait for the worker exit event - $this->waitingWorkerExit ??= Coroutine::create(function () { + // Wait for the worker exit event (启动一次) + if (! $this->waitingWorkerExitStarted) { + Coroutine::create(function () { try { CoordinatorManager::until(Constants::WORKER_EXIT)->yield(); $this->close(); $this->workerExited = true; } catch (Throwable) { } - }); + }); + $this->waitingWorkerExitStarted = true; + }Also applies to: 102-104
♻️ Duplicate comments (5)
src/sentry/src/HttpClient/HttpClient.php (5)
47-56: 避免静默丢单与热路径阻塞:push 未检查返回值,worker 退出时仍返回 202
- 当 worker 已退出或通道已关闭/不存在时,
$chan?->push()什么也不做却仍返回 202,导致请求被悄然丢弃。- 未设置 push 超时,通道已满时会阻塞业务请求线程。
建议:在 push 前后增加短超时与回退到同步发送;若 worker 已退出或 push 失败(超时/关闭),直接走
parent::sendRequest(...)。public function sendRequest(Request $request, Options $options): Response { // Start the loop if not started yet $this->loop(); - // Push the request to the channel - $chan = $this->chan; - $chan?->push([$request, $options]); + // worker 已退出或队列不可用/已关闭/超时 -> 回退为同步发送,避免静默丢单 + if ($this->workerExited) { + return parent::sendRequest($request, $options); + } + $chan = $this->chan; + if (! $chan || $chan->push([$request, $options], 0.002) === false) { + // 可选:基于 $chan->errCode 区分 CLOSED / TIMEOUT + return parent::sendRequest($request, $options); + } return new Response(202, ['X-Sentry-Request-Status' => ['Queued']], ''); }
75-95: 合并双重 while,显式判断 pop 失败,避免异常路径 busy loop当前嵌套 while +
break容易读错控制流;catch(Throwable){ break; }仅跳出内层循环,外层继续,存在忙等风险。建议改为单循环并用($args = $chan->pop()) !== false显式判断。- try { - while (true) { - while (true) { - // If the channel is closing or pop failed, exit the loop - if (! $args = $this->chan?->pop()) { - break 2; - } - try { - $callable = fn () => parent::sendRequest(...$args); - if ($this->concurrent) { - $this->concurrent->create($callable); - } else { - Coroutine::create($callable); - } - } catch (Throwable) { - break; - } finally { - $callable = null; - $args = null; - } - } - } - } catch (Throwable $e) { - } finally { - $this->close(); - } + try { + $chan = $this->chan; + while ($chan && ! $chan->isClosing() && ($args = $chan->pop()) !== false) { + $callable = function () use ($args) { + try { + parent::sendRequest(...$args); + } catch (Throwable $e) { + // TODO: 视需要记录日志/打点 + } + }; + if ($this->concurrent) { + $this->concurrent->create($callable); + } else { + Coroutine::create($callable); + } + $callable = null; + $args = null; + } + } catch (Throwable $e) { + // TODO: 视需要记录日志/打点 + } finally { + $this->close(); + }
82-87: 子协程内未捕获的异常会污染日志并中断并发槽位
parent::sendRequest(...$args)可能抛出异常;当前仅在“调度”处 try/catch,子协程内异常未被捕获。请在可执行体内捕获。- $callable = fn () => parent::sendRequest(...$args); + $callable = function () use ($args) { + try { + parent::sendRequest(...$args); + } catch (Throwable $e) { + // TODO: 记录错误或打点,不要向上抛 + } + };
113-121: 优雅收敛:close 仅关闭 Channel,未等待已派发任务完成,可能丢尾部请求建议用 WaitGroup 或等效手段在关闭前短暂等待在途任务完成(带超时,防止卡退出)。
protected function close(): void { $chan = $this->chan; - $chan?->close(); + // 等待在途任务短暂收敛(示例 100ms,可调/可配置) + $this->wg?->wait(0.1); + $chan?->close(); if ($this->chan === $chan) { $this->chan = null; } }为支持上述等待,请在类中补充以下改动(文件其它位置,非选区增量):
use Hyperf\Coroutine\WaitGroup; protected ?WaitGroup $wg = null; public function __construct(string $sdkIdentifier, string $sdkVersion, protected int $channelSize = 65535, int $concurrentLimit = 100,) { parent::__construct($sdkIdentifier, $sdkVersion); $this->wg = new WaitGroup(); if ($concurrentLimit > 0) { $this->concurrent = new Concurrent($concurrentLimit); } }在派发任务时配合 add/done:
$this->wg?->add(1); $callable = function () use ($args) { try { parent::sendRequest(...$args); } catch (Throwable $e) { /* log */ } finally { $this->wg?->done(); } };
61-68: 与 sendRequest 的交互导致“早退 + 丢单”
loop()在workerExited === true时直接返回且不创建通道;随后sendRequest()仍执行$chan?->push(...)并返回 202。请结合前述修改,在sendRequest()中检测workerExited/chan状态并同步回退。
🧹 Nitpick comments (2)
src/sentry/src/HttpClient/HttpClient.php (2)
97-99: 异常被吞并且无可观测性外层
catch (Throwable $e) {}空块会屏蔽错误根因。建议至少记录一次错误或打点,方便问题排查。- } catch (Throwable $e) { - } finally { + } catch (Throwable $e) { + // TODO: 使用项目内 Logger/Tracer 记录一次 + } finally { $this->close(); }
37-45: 通道容量缺省值偏大,需评估内存与背压策略
$channelSize = 65535在高流量下会缓存大量Request/Options对,内存占用可观。建议:
- 将默认值下调(如 1024/4096),或改为配置驱动;
- 与
Concurrent的并发上限联动(例如容量 ≈ 5–10 × 并发)。是否需要我基于现有配置体系补一个可配置项并给出合理默认值?
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/sentry/src/HttpClient/HttpClient.php(3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: Test on PHP 8.2 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.2 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.1 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.3 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.1 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.1 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.3 with Swoole 6.0.2
- GitHub Check: Test on PHP 8.2 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.3 with Swoole 5.1.7
- GitHub Check: Test on PHP 8.2 with Swoole 6.0.2
🔇 Additional comments (1)
src/sentry/src/HttpClient/HttpClient.php (1)
113-114: 方法可见性变更 — 仓库内未发现对 HttpClient::close() 的外部调用仓库搜索到的 close() 调用均为类内 $this->close() 或作用于其它类型(Response/Channel/Connection),测试只在测试对象上调用 close();未发现对 src/sentry/src/HttpClient/HttpClient::close() 的外部直接调用,因此在本仓库内把 public → protected 不会直接产生 BC 破坏。合并前确认下游/对外依赖。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/sentry/src/HttpClient/HttpClient.php (2)
32-34: 属性类型错误:Coroutine::create 返回 int,声明为 ?Coroutine 会导致 TypeError
Hyperf\Engine\Coroutine::create()返回协程 ID(int),而非对象。应改为?int,并相应重命名属性以表达语义。- protected ?Coroutine $waitingWorkerExit = null; + // 协程 ID:用于标记“等待 worker 退出”的守护协程是否已创建 + protected ?int $waitingWorkerExitId = null;
110-118: 与上条配套:修正属性使用处将写入的对象改为协程 ID,并保持只创建一次。
- $this->waitingWorkerExit ??= Coroutine::create(function () { + $this->waitingWorkerExitId ??= Coroutine::create(function () { try { CoordinatorManager::until(Constants::WORKER_EXIT)->yield(); $this->close(); $this->workerExited = true; } catch (Throwable) { } });
♻️ Duplicate comments (2)
src/sentry/src/HttpClient/HttpClient.php (2)
63-67: workerExited 分支可能导致 sendRequest 仍返回 202(丢包)
loop()在workerExited === true时直接返回,但sendRequest()仍可能走到 202。请结合上一条修改在 push 前做显式回退,避免丢包。
68-109: tap 回调签名错误 + 在协程内直接访问 $this->chan 存在竞态/NPE 风险;建议捕获 $chan 并简化循环
- 回调未接收参数将触发 ArgumentCountError。
- 子协程内多处直接访问
$this->chan,在close()将其置空后可能触发“Call to a member function pop() on null”;虽被最外层 try/catch 吞掉,但会产生不必要的异常开销。- 双 while 与
break 2可简化;pop()判定应使用严格比较,避免将[]当作 false(虽当前不会推空数组,但更健壮)。- 建议在子协程内包装
parent::sendRequest(...$args)的异常,避免未捕获异常污染日志或中断并发槽位。按下述方式修复:
- // Initialize the channel and start the loop - $this->chan ??= tap(new Channel($this->channelSize), function () { + // Initialize the channel and start the loop + $this->chan ??= tap(new Channel($this->channelSize), function (Channel $chan): void { // Dump memory usage and channel size // Coroutine::create(function () use ($chan) { // while (! $chan->isClosing()) { // dump('Memory Usage(MB): ' . memory_get_usage(true) / 1024 / 1024); // dump('Channel Size: ' . $chan->getLength()); // sleep(1); // } // }); // Start the loop - Coroutine::create(function () { + Coroutine::create(function () use ($chan): void { try { - while (true) { - while (true) { - // If the channel is closing or pop failed, exit the loop - if (! $args = $this->chan->pop()) { - break 2; - } + while (! $chan->isClosing() && ($args = $chan->pop()) !== false) { try { - $callable = fn () => parent::sendRequest(...$args); + $callable = function () use ($args): void { + try { + parent::sendRequest(...$args); + } catch (Throwable $e) { + // 可选:记录日志或打点 + } + }; if ($this->concurrent) { $this->concurrent->create($callable); } else { Coroutine::create($callable); } - } catch (Throwable) { - break; + } catch (Throwable) { + // 调度失败:跳过当前消息,继续 + continue; } finally { $callable = null; $args = null; } - } - } + } } catch (Throwable $e) { } finally { $this->close(); } }); - }); + });
🧹 Nitpick comments (3)
src/sentry/src/HttpClient/HttpClient.php (3)
24-25: tap 使用提醒:回调需要接收参数值已引入 tap 没问题,但下方 loop 中的 tap 回调当前未接收参数,会在运行时触发 ArgumentCountError。见后续针对 68-109 的修复建议。
103-106: 空 catch 建议最少记录一次错误信息完全吞掉异常会丢失故障线索。建议输出日志或打点(同时注意限流)。
89-94: [可选] 直接内联 $callable 以减少一次变量分配若不需要在 finally 中置空引用,可将
$callable内联至create()调用,进一步减少短生命周期变量。
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/sentry/src/HttpClient/HttpClient.php(3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Test on PHP 8.3 with Swoole 5.1.7
🔇 Additional comments (1)
src/sentry/src/HttpClient/HttpClient.php (1)
49-59: 避免静默丢包与阻塞:对 Channel::push 使用短超时并在失败时回退为同步发送验证:Hyperf/Swoole 的 Channel::push 支持超时参数(float,默认 -1 阻塞),在超时或通道已关闭时返回 false;基于此,建议改为非阻塞 push 并在失败/通道不可用时回退到同步发送。文件:src/sentry/src/HttpClient/HttpClient.php 行 49-59。建议 patch:
public function sendRequest(Request $request, Options $options): Response { // Start the loop if not started yet $this->loop(); - // Push the request to the channel - $chan = $this->chan; - $chan?->push([$request, $options]); + // Worker 已退出或队列不可用:同步发送,避免静默丢包 + $chan = $this->chan; + if ($this->workerExited || ! $chan) { + return parent::sendRequest($request, $options); + } + // 非阻塞推送;失败(超时/已关闭)则回退为同步发送 + if ($chan->push([$request, $options], 0.001) === false) { + return parent::sendRequest($request, $options); + } return new Response(202, ['X-Sentry-Request-Status' => ['Queued']], ''); }
* fix(sentry): optimize HttpClient memory usage and error handling - Replace Closure storage with direct parameter arrays to reduce memory overhead - Improve parameter cleanup in finally block to prevent memory leaks - Remove unused Closure import - Add clearer comments for loop lifecycle management - Streamline channel initialization and cleanup logic * fix(HttpClient): improve channel initialization and request handling logic * fix(HttpClient): prevent redundant loop initialization in channel management * fix(sentry): increase HTTP concurrent limit to improve request handling * fix(HttpClient): increase default concurrent limit to improve request handling * fix(HttpClient): add memory usage and channel size logging in loop initialization * fix(HttpClient): rename variable for clarity in error handling * fix(HttpClient): exit loop if channel is closing or pop fails * fix(HttpClient): comment out memory usage and channel size logging in loop initialization * fix(HttpClient): use func_get_args() for pushing requests to the channel * fix(sentry): reduce default HTTP concurrent limit from 1000 to 100 * 更新 HttpClient.php * fix(HttpClient): rename closure variable to callable for clarity * fix(HttpClient): refactor request channel push and restore close method * fix(HttpClient): clear callable and args variables after use * fix(HttpClient): update request pushing to use explicit parameters * fix(HttpClient): ensure channel is closed after request processing * fix(HttpClient): simplify channel initialization and loop handling * Revert "fix(HttpClient): simplify channel initialization and loop handling" This reverts commit ec1d335. * fix(HttpClient): refactor channel initialization and loop handling * fix(HttpClient): update channel reference in memory usage logging --------- Co-authored-by: Deeka Wong <8337659+huangdijia@users.noreply.github.com>
Summary
Changes
Closurestorage witharray{0:Request,1:Options}parameter arrays to reduce memory overheadClosureimportfinallyblock to prevent memory leaksTest Plan
Summary by CodeRabbit
新功能
重构
文档