From 4c367e84ad4eca2ecf09e3e96b401d18fad00985 Mon Sep 17 00:00:00 2001 From: tolelom <98kimsungmin@naver.com> Date: Fri, 20 Mar 2026 11:16:28 +0900 Subject: [PATCH] =?UTF-8?q?Fix:=20entry=20token=20=EB=A7=8C=EB=A3=8C=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=EC=9C=BC=EB=A1=9C=20stale=20waiting=20room?= =?UTF-8?q?=20=ED=8C=90=EB=8B=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 타임아웃 대신 Redis entry token 존재 여부로 판단: - pending token이 Redis에 남아있음 → 정상 로딩 중, 방 보존 - pending token이 없음 (만료/소비) → abandoned 확정, 즉시 정리 정상 로딩 중인 파티원의 방을 보호하면서도 강제 종료 유저의 즉각적 복구 가능. Co-Authored-By: Claude Opus 4.6 (1M context) --- internal/bossraid/repository.go | 9 ++++----- internal/bossraid/service.go | 22 ++++++++++++++++------ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/internal/bossraid/repository.go b/internal/bossraid/repository.go index ef5f774..8baa55c 100644 --- a/internal/bossraid/repository.go +++ b/internal/bossraid/repository.go @@ -64,14 +64,13 @@ func (r *Repository) CountActiveByUsername(username string) (int64, error) { return count, err } -// FindStaleWaitingRoomsByUsername returns waiting rooms containing the given username -// that were created before the threshold. -func (r *Repository) FindStaleWaitingRoomsByUsername(username string, threshold time.Time) ([]BossRoom, error) { +// FindWaitingRoomsByUsername returns all waiting rooms containing the given username. +func (r *Repository) FindWaitingRoomsByUsername(username string) ([]BossRoom, error) { escaped := strings.NewReplacer("%", "\\%", "_", "\\_").Replace(username) search := `"` + escaped + `"` var rooms []BossRoom - err := r.db.Where("status = ? AND players LIKE ? AND created_at < ?", - StatusWaiting, "%"+search+"%", threshold). + err := r.db.Where("status = ? AND players LIKE ?", + StatusWaiting, "%"+search+"%"). Find(&rooms).Error return rooms, err } diff --git a/internal/bossraid/service.go b/internal/bossraid/service.go index f393503..5d0e56b 100644 --- a/internal/bossraid/service.go +++ b/internal/bossraid/service.go @@ -414,19 +414,29 @@ func (s *Service) RequestEntryWithTokens(usernames []string, bossID int) (*BossR } // cleanupStaleWaitingForUsers checks if any of the given users are stuck in -// a stale waiting room (older than waitingRoomTimeout) and cleans them up. -// This provides instant resolution for players who force-quit during loading. +// a waiting room whose entry token has already expired or been consumed. +// If the pending token is gone from Redis, the room is abandoned and safe to remove. +// If the token still exists, the room may have active loading players — leave it alone. func (s *Service) cleanupStaleWaitingForUsers(usernames []string) { - threshold := time.Now().Add(-waitingRoomTimeout) + ctx := context.Background() for _, username := range usernames { - rooms, err := s.repo.FindStaleWaitingRoomsByUsername(username, threshold) + rooms, err := s.repo.FindWaitingRoomsByUsername(username) if err != nil || len(rooms) == 0 { continue } + + // pending entry token이 Redis에 남아있으면 정상 로딩 중일 수 있음 → 보존 + pendingKey := pendingEntryPrefix + username + exists, _ := s.rdb.Exists(ctx, pendingKey).Result() + if exists > 0 { + continue + } + + // 토큰 만료/소비됨 → 방 abandoned 확정, 정리 for _, room := range rooms { - log.Printf("stale 대기방 정리: session=%s, player=%s", room.SessionName, username) + log.Printf("abandoned 대기방 정리 (토큰 만료): session=%s, player=%s", room.SessionName, username) if err := s.repo.DeleteRoomBySessionName(room.SessionName); err != nil { - log.Printf("stale 대기방 삭제 실패: %v", err) + log.Printf("대기방 삭제 실패: %v", err) } _ = s.repo.ResetRoomSlot(room.SessionName) }