From 62e7ecba01601ab3d5aa6c913db064d5fa768d9a Mon Sep 17 00:00:00 2001 From: Fringg Date: Wed, 29 Apr 2026 10:28:57 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20deadlock=20on=20user=20deletion=20?= =?UTF-8?q?=E2=80=94=20webhook=20handler=20never=20checked=20intentional?= =?UTF-8?q?=20mark?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mark_intentional_panel_deletion was called before api.delete_user, but _is_intentional_panel_deletion_event was never called in the webhook handler — it was dead code. The user.deleted webhook processed unconditionally, causing a deadlock between delete_user_account (Tx1 holding subscription row locks) and the webhook handler (Tx2 trying to lock the same rows via decrement_subscription_server_counts). Fix: check _is_intentional_panel_deletion_event at the top of _handle_user_deleted — if True, log and return immediately without touching the DB. --- app/services/remnawave_webhook_service.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/services/remnawave_webhook_service.py b/app/services/remnawave_webhook_service.py index 9f9530b6..d987a3e3 100644 --- a/app/services/remnawave_webhook_service.py +++ b/app/services/remnawave_webhook_service.py @@ -994,6 +994,16 @@ class RemnaWaveWebhookService: async def _handle_user_deleted( self, db: AsyncSession, user: User, subscription: Subscription | None, data: dict ) -> None: + # Suppress webhook if this deletion was initiated by delete_user_account — + # prevents deadlock between the ongoing deletion transaction and this handler + if self._is_intentional_panel_deletion_event(data): + logger.info( + 'Webhook user.deleted suppressed — intentional panel deletion in progress', + user_id=user.id, + uuid=data.get('uuid'), + ) + return + user_id = user.id sub_id = subscription.id if subscription else None