mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-04-28 16:50:08 +00:00
fix: handle StaleDataError in webhook user.deleted server counter decrement
When a user is deleted from the panel, the subscription may already be cascade-deleted by the time the webhook handler tries to decrement server counters. This caused StaleDataError followed by PendingRollbackError when accessing subscription.id in the error handler. - Save subscription.id before DB operations to avoid lazy load after rollback - Catch StaleDataError explicitly and rollback the session - Re-fetch subscription/user after potential rollback in _handle_user_deleted - Skip subscription cleanup if it was already cascade-deleted
This commit is contained in:
@@ -6,6 +6,7 @@ from typing import Optional
|
||||
from sqlalchemy import and_, delete, func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
from sqlalchemy.orm.exc import StaleDataError
|
||||
|
||||
from app.config import settings
|
||||
from app.database.crud.notification import clear_notifications
|
||||
@@ -625,6 +626,9 @@ async def decrement_subscription_server_counts(
|
||||
if not subscription:
|
||||
return
|
||||
|
||||
# Save ID before any DB operations that might invalidate the ORM object
|
||||
sub_id = subscription.id
|
||||
|
||||
server_ids: set[int] = set()
|
||||
|
||||
if subscription_servers is not None:
|
||||
@@ -633,12 +637,12 @@ async def decrement_subscription_server_counts(
|
||||
server_ids.add(sub_server.server_squad_id)
|
||||
else:
|
||||
try:
|
||||
ids_from_links = await get_subscription_server_ids(db, subscription.id)
|
||||
ids_from_links = await get_subscription_server_ids(db, sub_id)
|
||||
server_ids.update(ids_from_links)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
'⚠️ Не удалось получить серверы подписки %s для уменьшения счетчика: %s',
|
||||
subscription.id,
|
||||
sub_id,
|
||||
error,
|
||||
)
|
||||
|
||||
@@ -652,7 +656,7 @@ async def decrement_subscription_server_counts(
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
'⚠️ Не удалось сопоставить сквады подписки %s с серверами: %s',
|
||||
subscription.id,
|
||||
sub_id,
|
||||
error,
|
||||
)
|
||||
|
||||
@@ -663,11 +667,18 @@ async def decrement_subscription_server_counts(
|
||||
from app.database.crud.server_squad import remove_user_from_servers
|
||||
|
||||
await remove_user_from_servers(db, sorted(server_ids))
|
||||
except StaleDataError:
|
||||
logger.warning(
|
||||
'⚠️ Подписка %s уже удалена (StaleDataError), пропускаем декремент серверов %s',
|
||||
sub_id,
|
||||
list(server_ids),
|
||||
)
|
||||
await db.rollback()
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
'⚠️ Ошибка уменьшения счетчика пользователей серверов %s для подписки %s: %s',
|
||||
list(server_ids),
|
||||
subscription.id,
|
||||
sub_id,
|
||||
error,
|
||||
)
|
||||
|
||||
|
||||
@@ -574,18 +574,43 @@ class RemnaWaveWebhookService:
|
||||
async def _handle_user_deleted(
|
||||
self, db: AsyncSession, user: User, subscription: Subscription | None, data: dict
|
||||
) -> None:
|
||||
user_id = user.id
|
||||
sub_id = subscription.id if subscription else None
|
||||
|
||||
if subscription:
|
||||
self._stamp_webhook_update(subscription)
|
||||
|
||||
# Decrement server counters BEFORE clearing connected_squads
|
||||
await decrement_subscription_server_counts(db, subscription)
|
||||
|
||||
# Re-fetch after potential rollback inside decrement_subscription_server_counts
|
||||
try:
|
||||
await db.refresh(subscription)
|
||||
except Exception:
|
||||
# Subscription was cascade-deleted, re-fetch user and skip subscription updates
|
||||
logger.warning(
|
||||
'Webhook: subscription %s already deleted for user %s, skipping subscription cleanup',
|
||||
sub_id,
|
||||
user_id,
|
||||
)
|
||||
subscription = None
|
||||
try:
|
||||
await db.refresh(user)
|
||||
except Exception:
|
||||
from app.database.crud.user import get_user_by_id
|
||||
|
||||
user = await get_user_by_id(db, user_id)
|
||||
if not user:
|
||||
logger.error('Webhook: user %s not found after rollback', user_id)
|
||||
return
|
||||
|
||||
if subscription:
|
||||
if subscription.status != SubscriptionStatus.EXPIRED.value:
|
||||
subscription.status = SubscriptionStatus.EXPIRED.value
|
||||
logger.info(
|
||||
'Webhook: subscription %s marked expired (user deleted in panel) for user %s',
|
||||
subscription.id,
|
||||
user.id,
|
||||
sub_id,
|
||||
user_id,
|
||||
)
|
||||
|
||||
# Clear subscription data — panel user no longer exists
|
||||
@@ -596,7 +621,7 @@ class RemnaWaveWebhookService:
|
||||
subscription.updated_at = datetime.now(UTC).replace(tzinfo=None)
|
||||
|
||||
# Remove SubscriptionServer link rows
|
||||
await db.execute(delete(SubscriptionServer).where(SubscriptionServer.subscription_id == subscription.id))
|
||||
await db.execute(delete(SubscriptionServer).where(SubscriptionServer.subscription_id == sub_id))
|
||||
|
||||
# Clear remnawave linkage
|
||||
if user.remnawave_uuid:
|
||||
|
||||
Reference in New Issue
Block a user