Skip to content

feat: optional bounded concurrency for batch channel testing - #6004

Open
opastorello wants to merge 5 commits into
QuantumNous:mainfrom
opastorello:feat/channel-test-concurrency
Open

feat: optional bounded concurrency for batch channel testing#6004
opastorello wants to merge 5 commits into
QuantumNous:mainfrom
opastorello:feat/channel-test-concurrency

Conversation

@opastorello

@opastorello opastorello commented Jul 8, 2026

Copy link
Copy Markdown

⚠️ 提交说明 / PR Notice

This PR is AI-assisted: the code was written with AI help, then reviewed, built and tested by me. The description below is written by me based on my understanding of the change.

📝 变更描述 / Description

Batch channel testing (performChannelTests in controller/channel-test.go) tested every channel strictly sequentially — one testChannel call at a time. On instances with many channels a run takes very long because each slow/dead upstream is waited on one after another.

This PR makes the batch test run channels through a bounded worker pool whose size is configurable, while keeping the default behavior identical to today.

Backend

  • New setting MonitorSetting.ChannelTestConcurrency (json:"test_concurrency"), default 1 → existing deployments are unchanged unless they opt in.
  • Optional env override CHANNEL_TEST_CONCURRENCY (mirrors CHANNEL_TEST_FREQUENCY/CHANNEL_TEST_ENABLED); non-positive values normalize to 1.
  • performChannelTests dispatches to at most N concurrent goroutines: shared channelTestSummary is mutex-guarded, progress is reported through a single serialized advanceProgress (monotonic), ctx cancellation is honored while acquiring a slot, and RequestInterval throttles strictly between dispatches (no trailing wait, no leaked timer).

Frontend (default theme)

  • Adds a "Test concurrency" number field to Model settings → Routing Reliability, wired to monitor_setting.test_concurrency (mirrors the existing test-interval field), with en/zh strings and i18n:sync applied to the other locales.

Why it works

Per-channel testChannel calls each build their own gin context and recorder, so they are independent. The only shared state (summary counters, progress) is synchronized. Per-channel side effects (processChannelError, EnableChannel, UpdateResponseTime, consume-log writes) operate on independent rows through GORM, which is safe for concurrent use.

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix)
  • ✨ 新功能 (New feature) — opt-in, backward compatible (default 1)
  • ⚡ 性能优化 / 重构 (Refactor)

🔗 关联任务 / Related Issue

  • N/A

✅ 提交前检查项 / Checklist

  • 人工确认: 描述由我本人撰写整理(AI 辅助编码,已在顶部声明)。
  • 非重复提交: 已搜索现有 Issues/PRs(feat: 渠道模型测试增加超时设置与响应时间筛选功能 #5844 仅新增测试超时设置,与本 PR 不重叠)。
  • 变更理解: 我理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 仅涉及批量测试并发(后端 + 前端设置项)。
  • 本地验证: 后端 go build/go vet/包测试通过;前端 bun run typecheck 通过,改动文件 oxlint 0 error。
  • 安全合规: 无敏感凭据;默认值 1 保持原有行为。

📸 运行证明 / Proof of Work

# backend
$ go build ./controller/... ./setting/operation_setting/...   -> ok
$ go vet   ./controller/... ./setting/operation_setting/...   -> ok
$ go test  ./setting/operation_setting/ -run ChannelTestConcurrency -v
--- PASS: TestGetMonitorSetting_ChannelTestConcurrencyNormalizedToAtLeastOne
--- PASS: TestGetMonitorSetting_ChannelTestConcurrencyEnvOverride
--- PASS: TestGetMonitorSetting_ChannelTestConcurrencyEnvNonPositiveNormalizedToOne
$ go test  ./controller/ -run 'SelectChannelsForAutomaticTest|SettleTestQuota'  -> ok

# frontend (web/default)
$ bun run typecheck                       -> tsgo -b, no errors
$ bunx oxlint <changed files>             -> 0 warnings, 0 errors

Batch channel testing ran every channel strictly sequentially, so a run
over many channels waited on each slow/dead upstream one after another.

Add MonitorSetting.ChannelTestConcurrency (json test_concurrency, default
1) plus an optional CHANNEL_TEST_CONCURRENCY env override. performChannelTests
now dispatches tests through a bounded worker pool of that size: the shared
summary is mutex guarded, progress uses an atomic counter, ctx cancellation
is honored while acquiring a slot, and RequestInterval still throttles
dispatch. Default 1 preserves the original sequential behavior.
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a ChannelTestConcurrency setting with environment override and minimum clamping, then runs channel tests with bounded concurrency and updates the matching system-settings UI.

Changes

Concurrent Channel Testing

Layer / File(s) Summary
Concurrency setting and validation
setting/operation_setting/monitor_setting.go, setting/operation_setting/monitor_setting_test.go
Adds ChannelTestConcurrency, defaults it to 1, reads CHANNEL_TEST_CONCURRENCY, clamps the value to at least 1, and covers normalization and override cases in tests.
Parallel test result handling
controller/channel-test.go
Updates per-channel test execution to handle cancellation, response-time-based auto-disable, enable/disable actions, and synchronized summary/progress updates.
Bounded concurrent dispatch
controller/channel-test.go
Replaces sequential dispatch with semaphore-limited goroutines, throttled starts, wait-group synchronization, and final completion reporting.
System settings field and form
web/default/src/features/system-settings/models/index.tsx, web/default/src/features/system-settings/models/routing-reliability-section.tsx, web/default/src/features/system-settings/models/section-registry.tsx, web/default/src/features/system-settings/types.ts, web/default/src/features/models/components/drawers/model-mutate-drawer.tsx, web/default/src/i18n/locales/en.json, web/default/src/i18n/locales/zh.json, web/default/src/i18n/locales/fr.json, web/default/src/i18n/locales/ja.json, web/default/src/i18n/locales/ru.json, web/default/src/i18n/locales/vi.json, web/default/src/i18n/locales/zh-TW.json, web/default/src/i18n/locales/_reports/_sync-report.json, web/default/src/i18n/locales/_reports/ja.untranslated.json, web/default/src/i18n/locales/_reports/ru.untranslated.json
Adds monitor_setting.test_concurrency to the model defaults, types, routing-reliability schema, form defaults, normalization, rendered input, and locale/report data.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • QuantumNous/new-api#5592 — Also changes controller/channel-test.go’s channel-testing flow and worker dispatch logic.

Suggested reviewers: Calcium-Ion

Poem

I’m a rabbit with a stopwatch bright,
Hopping channels, one then two in flight.
With concurrency set just right,
Progress twinkles through the night,
And the test burrow feels delight. 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: optional bounded concurrency for batch channel testing.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@controller/channel-test.go`:
- Around line 988-990: The progress callback invocation in testOne is now
concurrent, which can make report calls arrive out of order and regress
downstream progress updates. Serialize all report(...) calls by introducing a
shared synchronization point around the callback path in the
controller/channel-test.go flow, and apply the same protection to the other
report usage near the second mentioned location so every callback is emitted
sequentially with monotonically increasing processed counts.

In `@setting/operation_setting/monitor_setting_test.go`:
- Around line 45-56: Test
GetMonitorSetting_ChannelTestConcurrencyNormalizedToAtLeastOne currently depends
on CHANNEL_TEST_CONCURRENCY from the environment, so make the fixture fully
explicit by setting or clearing that env var inside the test before calling
GetMonitorSetting(). Keep the existing monitorSetting override in place, but
ensure the test controls the env-backed input as well so CI or local settings
cannot change the result. Use the GetMonitorSetting function and the
monitorSetting test fixture as the main points to update.

In `@setting/operation_setting/monitor_setting.go`:
- Around line 55-62: The CHANNEL_TEST_CONCURRENCY override handling in
monitorSetting should normalize any non-positive env value to 1 instead of
skipping the assignment and leaving an older positive value intact. Update the
logic around os.LookupEnv and strconv.Atoi so that when the env var is present
and parses successfully, monitorSetting.ChannelTestConcurrency is set from the
parsed value and then clamped to at least 1. Keep the fallback normalization in
the monitor setting initialization path so the final value is always valid.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7a6079f3-1a06-4ec0-bd23-f11f2ba5abc1

📥 Commits

Reviewing files that changed from the base of the PR and between 6ce7305 and c228628.

📒 Files selected for processing (3)
  • controller/channel-test.go
  • setting/operation_setting/monitor_setting.go
  • setting/operation_setting/monitor_setting_test.go

Comment thread controller/channel-test.go Outdated
Comment thread setting/operation_setting/monitor_setting_test.go
Comment thread setting/operation_setting/monitor_setting.go
…ency env

Address review feedback:
- Serialize the progress report callback: concurrent testOne goroutines could
  call report out of order. Route all reports through advanceProgress, guarded
  by a dedicated mutex and a monotonic counter, so persisted progress never
  regresses.
- Honor a non-positive CHANNEL_TEST_CONCURRENCY override and let the clamp
  normalize it to 1 instead of silently ignoring it.
- Isolate the normalization test from ambient CHANNEL_TEST_CONCURRENCY and add
  a case covering the env=0 -> 1 normalization.
@opastorello

Copy link
Copy Markdown
Author

Thanks for the review 🙏 Addressed all three in the latest commit:

  1. Serialize progress callbacks (Major): all progress reports now go through a single advanceProgress helper guarded by a dedicated mutex with a monotonic completed counter, so concurrent testOne goroutines can no longer report out of order or regress the persisted progress. Replaced the atomic counter accordingly.
  2. Clamp non-positive env override: CHANNEL_TEST_CONCURRENCY=0/-1 is now applied and then normalized to 1 by the existing clamp, instead of being silently ignored.
  3. Isolate the normalization test: it now sets CHANNEL_TEST_CONCURRENCY="" so ambient CI env can't affect it, plus a new case covering the env=0 -> 1 normalization.

go build / go vet / package tests all pass locally.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
controller/channel-test.go (1)

1004-1046: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Optional: skip the dispatch throttle after the final channel.

The RequestInterval throttle at Lines 1035-1045 runs after every dispatched channel, including the last one, adding an unnecessary RequestInterval delay before wg.Wait(). Additionally, on cancellation the time.After timer at Line 1042 leaks until it fires. Both are minor, but you can avoid the trailing wait by only throttling between dispatches.

The core semaphore/wait-group coordination and cancellation handling look correct.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controller/channel-test.go` around lines 1004 - 1046, The dispatch loop in
channel-test.go applies the RequestInterval throttle after the final channel and
also leaves a time.After timer pending on cancellation. Update the dispatch
logic in the dispatch loop around testOne, sem, wg, and RequestInterval so
throttling only happens between channels (not after the last dispatch), and
avoid using a timer that can linger on ctx.Done() by restructuring the
sleep/cancel handling accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@controller/channel-test.go`:
- Around line 1004-1046: The dispatch loop in channel-test.go applies the
RequestInterval throttle after the final channel and also leaves a time.After
timer pending on cancellation. Update the dispatch logic in the dispatch loop
around testOne, sem, wg, and RequestInterval so throttling only happens between
channels (not after the last dispatch), and avoid using a timer that can linger
on ctx.Done() by restructuring the sleep/cancel handling accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 703dc17d-bf1e-4520-aafc-aa3e11c85d85

📥 Commits

Reviewing files that changed from the base of the PR and between c228628 and 131e7f3.

📒 Files selected for processing (3)
  • controller/channel-test.go
  • setting/operation_setting/monitor_setting.go
  • setting/operation_setting/monitor_setting_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • setting/operation_setting/monitor_setting.go

Move the RequestInterval throttle to the top of the dispatch loop, gated on a
dispatched flag, so it runs strictly between channels and not after the final
one. Use a stoppable time.NewTimer instead of time.After so the timer does not
linger when ctx is cancelled during the throttle.
@opastorello

Copy link
Copy Markdown
Author

Applied the nitpick as well: the RequestInterval throttle now runs strictly between dispatches (gated on a dispatched flag), so there's no trailing wait after the last channel, and it uses a stoppable time.NewTimer instead of time.After so no timer lingers when ctx is cancelled mid-throttle. Thanks for confirming the semaphore/wait-group coordination 👍

…tings

Add a 'Test concurrency' number field to the default-theme monitor settings so
the new monitor_setting.test_concurrency backend option is configurable from the
admin UI (mirrors the existing test-interval field). Includes en/zh i18n.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
web/default/src/features/system-settings/models/routing-reliability-section.tsx (1)

78-89: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider capping test_concurrency to a sane maximum.

Unlike RetryTimes (.max(10)), test_concurrency only has a lower bound (.min(1)). Per the PR objectives, the backend normalizes non-positive values to 1 but doesn't appear to clamp an upper bound either, so an admin could set an arbitrarily large value that's forwarded directly to the channel-test worker pool, risking resource exhaustion during batch tests.

♻️ Suggested bound
       test_concurrency: z.coerce
         .number()
         .int()
-        .min(1, 'Concurrency must be at least 1'),
+        .min(1, 'Concurrency must be at least 1')
+        .max(50, 'Concurrency must be at most 50'),
                       <Input
                         type='number'
                         min={1}
+                        max={50}
                         step={1}
                         {...safeNumberFieldProps(field)}
                       />

Worth confirming what (if any) upper bound the backend enforces, so the UI limit stays consistent.

Also applies to: 466-489

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@web/default/src/features/system-settings/models/routing-reliability-section.tsx`
around lines 78 - 89, The `monitor_setting` schema in
`routing-reliability-section.tsx` only enforces a minimum for
`test_concurrency`, so add an explicit upper cap in the
`z.coerce.number().int()` chain similar to `RetryTimes` to prevent overly large
worker-pool values. Update the `test_concurrency` validation in
`monitor_setting` (and any matching schema referenced by the same settings form)
to clamp to a sane maximum that aligns with backend behavior, and ensure the
user-facing error message explains the allowed range.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@web/default/src/features/system-settings/models/routing-reliability-section.tsx`:
- Around line 78-89: The `monitor_setting` schema in
`routing-reliability-section.tsx` only enforces a minimum for
`test_concurrency`, so add an explicit upper cap in the
`z.coerce.number().int()` chain similar to `RetryTimes` to prevent overly large
worker-pool values. Update the `test_concurrency` validation in
`monitor_setting` (and any matching schema referenced by the same settings form)
to clamp to a sane maximum that aligns with backend behavior, and ensure the
user-facing error message explains the allowed range.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b6a26c16-2b86-4180-b42d-7d560532ec54

📥 Commits

Reviewing files that changed from the base of the PR and between 2cd8184 and 6514633.

📒 Files selected for processing (6)
  • web/default/src/features/system-settings/models/index.tsx
  • web/default/src/features/system-settings/models/routing-reliability-section.tsx
  • web/default/src/features/system-settings/models/section-registry.tsx
  • web/default/src/features/system-settings/types.ts
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/zh.json
✅ Files skipped from review due to trivial changes (1)
  • web/default/src/i18n/locales/en.json

The ModelSettings type now requires monitor_setting.test_concurrency, so the
default object in model-mutate-drawer must include it. Also run i18n:sync to
propagate the two new Routing Reliability strings to all locale files.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
web/default/src/i18n/locales/zh-TW.json (1)

4355-4356: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

New keys added with English placeholder values instead of Traditional Chinese translations.

Both "Test concurrency" and "Number of channels tested in parallel during a batch test (1 = one at a time)." are added with English text as the translation value, identical to the source key. Every other entry in this file has a proper Traditional Chinese translation. Note the flat English-key convention itself is fine per prior guidance; the issue here is the untranslated value.

🌐 Suggested translation
-    "Test concurrency": "Test concurrency",
-    "Number of channels tested in parallel during a batch test (1 = one at a time).": "Number of channels tested in parallel during a batch test (1 = one at a time).",
+    "Test concurrency": "測試並行數",
+    "Number of channels tested in parallel during a batch test (1 = one at a time).": "大量測試時同時測試的渠道數量(1 = 逐個測試)。",

Based on learnings, flat English-key i18n convention in web/default/src/i18n/locales/ is correct and not itself a violation; the concern here is the untranslated value for the zh-TW locale.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/i18n/locales/zh-TW.json` around lines 4355 - 4356, The zh-TW
locale entries for the new i18n keys still use English placeholder text instead
of Traditional Chinese. Update the translation values for “Test concurrency” and
“Number of channels tested in parallel during a batch test (1 = one at a time).”
in the zh-TW JSON file so they match the locale, keeping the existing flat
English-key convention intact and changing only the untranslated values.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@web/default/src/i18n/locales/zh-TW.json`:
- Around line 4355-4356: The zh-TW locale entries for the new i18n keys still
use English placeholder text instead of Traditional Chinese. Update the
translation values for “Test concurrency” and “Number of channels tested in
parallel during a batch test (1 = one at a time).” in the zh-TW JSON file so
they match the locale, keeping the existing flat English-key convention intact
and changing only the untranslated values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8321ab49-7939-49c8-bd78-73e81406ffdb

📥 Commits

Reviewing files that changed from the base of the PR and between 6514633 and d173917.

📒 Files selected for processing (9)
  • web/default/src/features/models/components/drawers/model-mutate-drawer.tsx
  • web/default/src/i18n/locales/_reports/_sync-report.json
  • web/default/src/i18n/locales/_reports/ja.untranslated.json
  • web/default/src/i18n/locales/_reports/ru.untranslated.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/zh-TW.json
✅ Files skipped from review due to trivial changes (4)
  • web/default/src/i18n/locales/_reports/ja.untranslated.json
  • web/default/src/i18n/locales/_reports/ru.untranslated.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant