Skip to content

🐛 fix(channel): evict auto-disabled multi-key channels from cache#4983

Open
t0ng7u wants to merge 1 commit into
mainfrom
fix/multikey-auto-disable-cache-eviction
Open

🐛 fix(channel): evict auto-disabled multi-key channels from cache#4983
t0ng7u wants to merge 1 commit into
mainfrom
fix/multikey-auto-disable-cache-eviction

Conversation

@t0ng7u
Copy link
Copy Markdown
Collaborator

@t0ng7u t0ng7u commented May 20, 2026

Ensure multi-key channels are removed from the in-memory routing cache when all keys become auto-disabled, preventing subsequent requests from repeatedly selecting channels with no available keys.

Also make multi-key status updates more robust by handling missing key matches, checking actual enabled key availability, and restoring the channel status when a key is re-enabled. Add regression coverage for disabled cached channels and multi-key cache eviction.

⚠️ 提交说明 / PR Notice

Important

  • 请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。

📝 变更描述 / Description

(简述:做了什么?为什么这样改能生效?请基于你对代码逻辑的理解来写,避免粘贴未经整理的内容)

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

  • Closes # (如有)

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

(请在此粘贴截图、关键日志或测试报告,以证明变更生效)

Summary by CodeRabbit

  • Bug Fixes

    • Improved multi-key channel status handling with enhanced key validation and automatic status adjustments when keys become unavailable
    • Optimized caching to eliminate unnecessary status synchronization operations
  • Tests

    • Added test coverage for multi-key channel cache eviction and route cache management behavior

Review Change Stack

Ensure multi-key channels are removed from the in-memory routing cache when all keys become auto-disabled, preventing subsequent requests from repeatedly selecting channels with no available keys.

Also make multi-key status updates more robust by handling missing key matches, checking actual enabled key availability, and restoring the channel status when a key is re-enabled. Add regression coverage for disabled cached channels and multi-key cache eviction.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

Walkthrough

The PR enhances multi-key channel status management by adding auto-disable behavior when no multi-keys are enabled and optimizing cache updates to skip unnecessary writes when status does not change. This improves channel lifecycle handling and reduces redundant cache operations in the in-memory caching path.

Changes

Multi-key Channel Status Auto-disable and Cache Optimization

Layer / File(s) Summary
Multi-key update handler and auto-disable logic
model/channel.go
handlerMultiKeyUpdate initializes a sentinel index, explicitly searches for keys, and when a key is not found either logs and returns (if key is non-empty) or updates channel.Status and persists status_reason/status_time to OtherInfo. A new hasEnabledMultiKey helper detects when no multi-keys remain enabled and triggers auto-disable (ChannelStatusAutoDisabled); enable actions restore status to ChannelStatusEnabled.
Cache update optimization in UpdateChannelStatus
model/channel.go
UpdateChannelStatus now captures the cached status before the multi-key update, performs the handler under the per-channel polling lock, and only calls CacheUpdateChannelStatus if the status value actually changed, reducing unnecessary cache writes.
Test setup and multi-key channel cache eviction
model/channel_cache_test.go
Test helper isolates in-memory cache state by overriding and restoring global cache variables via t.Cleanup. Test case verifies that when a multi-key enabled channel becomes auto-disabled, it is evicted from the route cache and random channel selection returns only the remaining single-key channel.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • QuantumNous/new-api#1498: Modifies the same multi-key update flow in handlerMultiKeyUpdate and UpdateChannelStatus with direct code-level overlap in channel status management.

Poem

🐰 A channel wakes with multi-keys in hand,
But when they vanish, status understands—
Auto-disable when none remain awake,
Cache skips redundant writes for speed's own sake,
Multi-key channels now know when to rest.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main fix: evicting auto-disabled multi-key channels from cache, which directly matches the changeset's core objective of removing such channels from routing cache.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/multikey-auto-disable-cache-eviction

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a 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

🧹 Nitpick comments (1)
model/channel_cache_test.go (1)

55-68: ⚡ Quick win

Exercise UpdateChannelStatus directly in this regression.

This test currently calls handlerMultiKeyUpdate() and CacheUpdateChannelStatus() manually, so it won't catch a regression in the new UpdateChannelStatus() cache-eviction branch. Since the production change is the conditional CacheUpdateChannelStatus() call inside UpdateChannelStatus, the regression should go through that public flow instead of its internals.

🤖 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 `@model/channel_cache_test.go` around lines 55 - 68, The test exercises
channel-status changes by calling handlerMultiKeyUpdate and
CacheUpdateChannelStatus directly, so it won't catch regressions in the new
UpdateChannelStatus cache-eviction branch; change the test to invoke the public
UpdateChannelStatus(path that takes channel id/status/reason or the exported
UpdateChannelStatus function) after performing the multi-key update (or replace
the manual CacheUpdateChannelStatus call with a call to UpdateChannelStatus for
the channel id and new status), ensuring the call goes through
UpdateChannelStatus (not CacheUpdateChannelStatus) so the conditional
cache-eviction branch is exercised; keep references to handlerMultiKeyUpdate and
CacheUpdateChannelStatus only for setup/verification but use UpdateChannelStatus
to perform the status update.
🤖 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 `@model/channel.go`:
- Around line 717-725: The DB-path early return that checks channel.Status ==
status must be changed so the no-op check runs after handlerMultiKeyUpdate();
instead of returning based solely on channel.Status, reload or compare the
persisted ChannelInfo/MultiKeyStatusList against the updated
channelCache.ChannelInfo (or compare MultiKeyStatusList entries) and only
short-circuit if the per-key status lists are identical; this ensures
handlerMultiKeyUpdate(channelCache, usingKey, status, reason) updates are
persisted via CacheUpdateChannelStatus/DB write even when the aggregate
channel.Status remains enabled.
- Around line 653-663: When handling the whole-channel enable path (when
usingKey == ""), do not just flip channel.Status and return; you must clear or
recompute the per-key disabled state so GetNextEnabledKey() won't still see all
keys as disabled. In the branch that sets channel.Status, update
channel.MultiKeyStatusList (or the structure holding per-key disabled maps) to
reflect the new enabled state (e.g., reset/clear the list or set each key's
status to enabled) and persist any changes (same place
GetOtherInfo()/SetOtherInfo() are used for channel metadata) before returning so
route cache and per-key lookup remain consistent.

---

Nitpick comments:
In `@model/channel_cache_test.go`:
- Around line 55-68: The test exercises channel-status changes by calling
handlerMultiKeyUpdate and CacheUpdateChannelStatus directly, so it won't catch
regressions in the new UpdateChannelStatus cache-eviction branch; change the
test to invoke the public UpdateChannelStatus(path that takes channel
id/status/reason or the exported UpdateChannelStatus function) after performing
the multi-key update (or replace the manual CacheUpdateChannelStatus call with a
call to UpdateChannelStatus for the channel id and new status), ensuring the
call goes through UpdateChannelStatus (not CacheUpdateChannelStatus) so the
conditional cache-eviction branch is exercised; keep references to
handlerMultiKeyUpdate and CacheUpdateChannelStatus only for setup/verification
but use UpdateChannelStatus to perform the status update.
🪄 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: b2631c94-4e48-41ce-acd1-0ee944fd40b2

📥 Commits

Reviewing files that changed from the base of the PR and between 20d3e73 and 3ba01a7.

📒 Files selected for processing (2)
  • model/channel.go
  • model/channel_cache_test.go

Comment thread model/channel.go
Comment on lines +653 to +663
if keyIndex < 0 {
if usingKey != "" {
common.SysLog(fmt.Sprintf("failed to update multi-key status: channel_id=%d, using key not found", channel.Id))
return
}
channel.Status = status
info := channel.GetOtherInfo()
info["status_reason"] = reason
info["status_time"] = common.GetTimestamp()
channel.SetOtherInfo(info)
return
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't re-enable the aggregate channel without reconciling per-key state.

When usingKey == "", this branch flips channel.Status and returns without touching MultiKeyStatusList. If a fully auto-disabled multi-key channel is re-enabled through this path, GetNextEnabledKey() will still see every key as disabled while the route cache now treats the channel as enabled. Please clear or recompute the per-key disabled maps before returning from the whole-channel enable path.

🤖 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 `@model/channel.go` around lines 653 - 663, When handling the whole-channel
enable path (when usingKey == ""), do not just flip channel.Status and return;
you must clear or recompute the per-key disabled state so GetNextEnabledKey()
won't still see all keys as disabled. In the branch that sets channel.Status,
update channel.MultiKeyStatusList (or the structure holding per-key disabled
maps) to reflect the new enabled state (e.g., reset/clear the list or set each
key's status to enabled) and persist any changes (same place
GetOtherInfo()/SetOtherInfo() are used for channel metadata) before returning so
route cache and per-key lookup remain consistent.

Comment thread model/channel.go
Comment on lines +717 to +725
beforeStatus := channelCache.Status
pollingLock := GetChannelPollingLock(channelId)
pollingLock.Lock()
// 如果是多Key模式,更新缓存中的状态
handlerMultiKeyUpdate(channelCache, usingKey, status, reason)
pollingLock.Unlock()
if beforeStatus != channelCache.Status {
CacheUpdateChannelStatus(channelId, channelCache.Status)
}
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Persist per-key recoveries even when the aggregate status stays enabled.

This cache-path change updates channelCache.ChannelInfo before comparing beforeStatus, but the DB path still short-circuits at Line 750 when channel.Status == status. Re-enabling one key on an already-enabled multi-key channel will clear the in-memory MultiKeyStatusList entry and then return before the DB copy is updated, so the key comes back as disabled after a reload. The no-op check for multi-key channels needs to run after handlerMultiKeyUpdate() and consider ChannelInfo changes, not just channel.Status.

🤖 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 `@model/channel.go` around lines 717 - 725, The DB-path early return that
checks channel.Status == status must be changed so the no-op check runs after
handlerMultiKeyUpdate(); instead of returning based solely on channel.Status,
reload or compare the persisted ChannelInfo/MultiKeyStatusList against the
updated channelCache.ChannelInfo (or compare MultiKeyStatusList entries) and
only short-circuit if the per-key status lists are identical; this ensures
handlerMultiKeyUpdate(channelCache, usingKey, status, reason) updates are
persisted via CacheUpdateChannelStatus/DB write even when the aggregate
channel.Status remains enabled.

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