mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 03:05:02 +03:00
fix(tgbot): release the invite claim lock even when a claim panics
claimInvite took the package-level inviteClaimMu and released it without defer, inside a handler whose panics the bot recovers. A panic while resolving or binding a token left the lock held for good, and every later /start claim parked on a worker slot until the bot answered nobody. The lock is now held in its own function with a deferred unlock.
This commit is contained in:
@@ -71,13 +71,7 @@ func (t *Tgbot) claimInvite(chatId int64, fromID int64, payload string) inviteOu
|
||||
return inviteInvalid
|
||||
}
|
||||
|
||||
inviteClaimMu.Lock()
|
||||
outcome, records := t.resolveInviteToken(token, fromID)
|
||||
var bindErr error
|
||||
if outcome == inviteBindable {
|
||||
bindErr = t.bindRecordsToUser(records, fromID)
|
||||
}
|
||||
inviteClaimMu.Unlock()
|
||||
outcome, records, bindErr := t.resolveAndBindInvite(token, fromID)
|
||||
|
||||
switch outcome {
|
||||
case inviteAlreadyOwned:
|
||||
@@ -97,6 +91,18 @@ func (t *Tgbot) claimInvite(chatId int64, fromID int64, payload string) inviteOu
|
||||
return outcome
|
||||
}
|
||||
|
||||
// resolveAndBindInvite holds inviteClaimMu with a deferred unlock: the handler recovers panics,
|
||||
// and a lock a panic left held would park every later /start claim for good.
|
||||
func (t *Tgbot) resolveAndBindInvite(token string, fromID int64) (inviteOutcome, []*model.ClientRecord, error) {
|
||||
inviteClaimMu.Lock()
|
||||
defer inviteClaimMu.Unlock()
|
||||
outcome, records := t.resolveInviteToken(token, fromID)
|
||||
if outcome != inviteBindable {
|
||||
return outcome, records, nil
|
||||
}
|
||||
return outcome, records, t.bindRecordsToUser(records, fromID)
|
||||
}
|
||||
|
||||
func recordEmails(records []*model.ClientRecord) string {
|
||||
emails := make([]string, 0, len(records))
|
||||
for _, record := range records {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package tgbot
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -222,3 +223,17 @@ func TestTgUserMentionEscapesName(t *testing.T) {
|
||||
t.Errorf("tgUserMention = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// The bot's handlers recover panics, so a claim that panicked with inviteClaimMu held left
|
||||
// it locked for good and every later /start claim parked on a worker slot.
|
||||
func TestInviteClaimReleasesTheLockWhenItPanics(t *testing.T) {
|
||||
func() {
|
||||
defer func() { _ = recover() }()
|
||||
// No database is open here, so resolving the token panics inside the lock.
|
||||
(&Tgbot{}).claimInvite(1, 1, base64.RawURLEncoding.EncodeToString([]byte("some-sub-id")))
|
||||
}()
|
||||
if !inviteClaimMu.TryLock() {
|
||||
t.Fatal("inviteClaimMu is still held after a claim panicked")
|
||||
}
|
||||
inviteClaimMu.Unlock()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user