fix: advisory lock por usuario en processMessage para eliminar race condition
- GET_LOCK('bot_user_ID', 10) serializa el procesamiento por usuario
- Solo un proceso a la vez puede ejecutar processMessage para el mismo user
- Recarga datos frescos del usuario tras adquirir el lock
- El lock se libera siempre en el bloque finally, incluso si hay excepciones
- Resuelve definitivamente el doble envío de bienvenida + términos
This commit is contained in:
+28
-1
@@ -21,9 +21,29 @@ class BotService {
|
|||||||
if ($messageType !== 'text') {
|
if ($messageType !== 'text') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$messageText = trim($messageText);
|
$messageText = trim($messageText);
|
||||||
$phoneNumber = $user['phone_number'];
|
$phoneNumber = $user['phone_number'];
|
||||||
|
$userId = $user['id'] ?? null;
|
||||||
|
|
||||||
|
// ── Advisory lock por usuario ─────────────────────────────────────────
|
||||||
|
// Serializar el procesamiento para el mismo usuario y evitar race conditions
|
||||||
|
// (WhatsApp puede entregar el mismo webhook dos veces en paralelo).
|
||||||
|
$lockAcquired = false;
|
||||||
|
if ($userId) {
|
||||||
|
$lockKey = 'bot_user_' . $userId;
|
||||||
|
$lockRow = $this->db->fetch("SELECT GET_LOCK(:k, 10) as ok", ['k' => $lockKey]);
|
||||||
|
$lockAcquired = !empty($lockRow['ok']);
|
||||||
|
if (!$lockAcquired) {
|
||||||
|
error_log("[BotService] processMessage - lock timeout para user_id={$userId}, ignorando llamada concurrente");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Recargar datos frescos del usuario después de adquirir el lock
|
||||||
|
$fresh = $this->db->fetch("SELECT * FROM users WHERE id = :id", ['id' => $userId]);
|
||||||
|
if ($fresh) $user = $fresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
// Comprobar en la base de datos el estado más reciente de 'in_service' para evitar condiciones de carrera
|
// Comprobar en la base de datos el estado más reciente de 'in_service' para evitar condiciones de carrera
|
||||||
try {
|
try {
|
||||||
@@ -393,6 +413,13 @@ class BotService {
|
|||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
// Liberar advisory lock si fue adquirido
|
||||||
|
if ($lockAcquired && isset($lockKey)) {
|
||||||
|
try { $this->db->query("DO RELEASE_LOCK(:k)", ['k' => $lockKey]); } catch (Exception $e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user