mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-27 01:53:19 +01:00
fix(channelserver): prevent guild RP rollover race and redundant stepup query
RolloverDailyRP now locks the guild row with SELECT FOR UPDATE and re-checks rp_reset_at inside the transaction, so concurrent callers cannot double-rollover and zero out freshly donated RP. Gacha stepup entry-type check is now skipped when the row was already deleted as stale, avoiding a redundant DELETE on step 0.
This commit is contained in:
@@ -327,14 +327,15 @@ func handleMsgMhfGetStepupStatus(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
s.logger.Error("Failed to reset stale gacha stepup", zap.Error(err))
|
s.logger.Error("Failed to reset stale gacha stepup", zap.Error(err))
|
||||||
}
|
}
|
||||||
step = 0
|
step = 0
|
||||||
}
|
} else if err == nil {
|
||||||
|
// Only check for valid entry type if the stepup is fresh
|
||||||
hasEntry, _ := s.server.gachaRepo.HasEntryType(pkt.GachaID, step)
|
hasEntry, _ := s.server.gachaRepo.HasEntryType(pkt.GachaID, step)
|
||||||
if !hasEntry {
|
if !hasEntry {
|
||||||
if err := s.server.gachaRepo.DeleteStepup(pkt.GachaID, s.charID); err != nil {
|
if err := s.server.gachaRepo.DeleteStepup(pkt.GachaID, s.charID); err != nil {
|
||||||
s.logger.Error("Failed to reset gacha stepup state", zap.Error(err))
|
s.logger.Error("Failed to reset gacha stepup state", zap.Error(err))
|
||||||
|
}
|
||||||
|
step = 0
|
||||||
}
|
}
|
||||||
step = 0
|
|
||||||
}
|
}
|
||||||
bf := byteframe.NewByteFrame()
|
bf := byteframe.NewByteFrame()
|
||||||
bf.WriteUint8(step)
|
bf.WriteUint8(step)
|
||||||
|
|||||||
@@ -942,11 +942,26 @@ func (r *GuildRepository) ListInvitedCharacters(guildID uint32) ([]*ScoutedChara
|
|||||||
|
|
||||||
// RolloverDailyRP moves rp_today into rp_yesterday for all members of a guild,
|
// RolloverDailyRP moves rp_today into rp_yesterday for all members of a guild,
|
||||||
// then updates the guild's rp_reset_at timestamp.
|
// then updates the guild's rp_reset_at timestamp.
|
||||||
|
// Uses SELECT FOR UPDATE to prevent concurrent rollovers from racing.
|
||||||
func (r *GuildRepository) RolloverDailyRP(guildID uint32, noon time.Time) error {
|
func (r *GuildRepository) RolloverDailyRP(guildID uint32, noon time.Time) error {
|
||||||
tx, err := r.db.Begin()
|
tx, err := r.db.Begin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// Lock the guild row and re-check whether rollover is still needed.
|
||||||
|
var rpResetAt time.Time
|
||||||
|
if err := tx.QueryRow(
|
||||||
|
`SELECT COALESCE(rp_reset_at, '2000-01-01'::timestamptz) FROM guilds WHERE id = $1 FOR UPDATE`,
|
||||||
|
guildID,
|
||||||
|
).Scan(&rpResetAt); err != nil {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !rpResetAt.Before(noon) {
|
||||||
|
// Another goroutine already rolled over; nothing to do.
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if _, err := tx.Exec(
|
if _, err := tx.Exec(
|
||||||
`UPDATE guild_characters SET rp_yesterday = rp_today, rp_today = 0 WHERE guild_id = $1`,
|
`UPDATE guild_characters SET rp_yesterday = rp_today, rp_today = 0 WHERE guild_id = $1`,
|
||||||
guildID,
|
guildID,
|
||||||
|
|||||||
Reference in New Issue
Block a user