fix: harden concurrent quota and status updates
This commit is contained in:
+22
-11
@@ -346,11 +346,21 @@ func (channel *Channel) Save() error {
|
||||
return DB.Save(channel).Error
|
||||
}
|
||||
|
||||
func (channel *Channel) SaveWithoutKey() error {
|
||||
// saveStatusState persists only the fields owned by the channel status flow.
|
||||
// Keeping this allowlist here prevents a stale channel snapshot from
|
||||
// overwriting credentials, accounting counters, or channel configuration.
|
||||
func (channel *Channel) saveStatusState() error {
|
||||
if channel.Id == 0 {
|
||||
return errors.New("channel ID is 0")
|
||||
}
|
||||
return DB.Omit("key").Save(channel).Error
|
||||
updates := map[string]any{
|
||||
"status": channel.Status,
|
||||
"other_info": channel.OtherInfo,
|
||||
}
|
||||
if channel.ChannelInfo.IsMultiKey {
|
||||
updates["channel_info"] = channel.ChannelInfo
|
||||
}
|
||||
return DB.Model(&Channel{}).Where("id = ?", channel.Id).Updates(updates).Error
|
||||
}
|
||||
|
||||
func GetAllChannels(startIdx int, num int, selectAll bool, idSort bool, sortOptions ...ChannelSortOptions) ([]*Channel, error) {
|
||||
@@ -713,19 +723,24 @@ func UpdateChannelStatus(channelId int, usingKey string, status int, reason stri
|
||||
if common.MemoryCacheEnabled {
|
||||
channelStatusLock.Lock()
|
||||
defer channelStatusLock.Unlock()
|
||||
}
|
||||
|
||||
// ChannelInfo stores both multi-key status and the polling cursor. Hold the
|
||||
// same per-channel lock from the first read through persistence so neither
|
||||
// writer can save a stale JSON snapshot over the other.
|
||||
pollingLock := GetChannelPollingLock(channelId)
|
||||
pollingLock.Lock()
|
||||
defer pollingLock.Unlock()
|
||||
|
||||
if common.MemoryCacheEnabled {
|
||||
channelCache, _ := CacheGetChannel(channelId)
|
||||
if channelCache == nil {
|
||||
return false
|
||||
}
|
||||
if channelCache.ChannelInfo.IsMultiKey {
|
||||
// Use per-channel lock to prevent concurrent map read/write with GetNextEnabledKey
|
||||
beforeStatus := channelCache.Status
|
||||
pollingLock := GetChannelPollingLock(channelId)
|
||||
pollingLock.Lock()
|
||||
// 如果是多Key模式,更新缓存中的状态
|
||||
handlerMultiKeyUpdate(channelCache, usingKey, status, reason)
|
||||
pollingLock.Unlock()
|
||||
if beforeStatus != channelCache.Status {
|
||||
CacheUpdateChannelStatus(channelId, channelCache.Status)
|
||||
}
|
||||
@@ -759,11 +774,7 @@ func UpdateChannelStatus(channelId int, usingKey string, status int, reason stri
|
||||
|
||||
if channel.ChannelInfo.IsMultiKey {
|
||||
beforeStatus := channel.Status
|
||||
// Protect map writes with the same per-channel lock used by readers
|
||||
pollingLock := GetChannelPollingLock(channelId)
|
||||
pollingLock.Lock()
|
||||
handlerMultiKeyUpdate(channel, usingKey, status, reason)
|
||||
pollingLock.Unlock()
|
||||
if beforeStatus != channel.Status {
|
||||
shouldUpdateAbilities = true
|
||||
}
|
||||
@@ -775,7 +786,7 @@ func UpdateChannelStatus(channelId int, usingKey string, status int, reason stri
|
||||
channel.Status = status
|
||||
shouldUpdateAbilities = true
|
||||
}
|
||||
err = channel.SaveWithoutKey()
|
||||
err = channel.saveStatusState()
|
||||
if err != nil {
|
||||
common.SysLog(fmt.Sprintf("failed to update channel status: channel_id=%d, status=%d, error=%v", channel.Id, status, err))
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user