Version de développement
Cette documentation décrit la version « next » (develop), non encore publiée. Pour la version stable, basculez sur « v5 » dans l'en-tête.
Références techniques

Verrous Advisory

Gestion des verrous advisory pour la synchronisation des ressources logiques

Verrous Advisory dans Dodock

Les verrous advisory (ou verrous consultatifs) permettent de synchroniser l'accès à des ressources logiques sans verrouiller physiquement les lignes d'une base de données. Ils sont particulièrement utiles pour éviter les conflits lors de traitements concurrents sur des entités métier comme les articles, les commandes ou les mouvements de stock.

Contexte d'utilisation

Dans un environnement multi-utilisateurs comme Dokos, plusieurs processus peuvent tenter de modifier simultanément les mêmes données. Les verrous advisory offrent une solution légère pour :

  • Éviter les deadlocks (interblocages) en ordonnançant les accès
  • Permettre des transactions longues sans bloquer les lectures
  • Synchroniser des opérations sur des ressources non matérialisées en base (ex : un processus de réapprovisionnement)

Fonctionnement du contexte advisory_lock()

Dodock fournit un gestionnaire de contexte frappe.db.advisory_lock(key, *, timeout=10) qui :

  1. Acquiert un verrou au début du bloc with
  2. Libère automatiquement le verrou à la sortie du bloc (même en cas d'erreur)
  3. Gère les timeouts avec une exception QueryTimeoutError si le verrou n'est pas obtenu

Exemple d'utilisation

from frappe import db

# Verrouillage d'un article pour mise à jour des stocks
article = "ART-001"

try:
    with db.advisory_lock(f"stock_update_{article}", timeout=5):
        # Traitement sécurisé des mouvements de stock
        db.sql("UPDATE tabStock SET quantity = quantity - 1 WHERE item_code = %s", article)
        frappe.msgprint(f"Stock mis à jour pour {article}")
except db.QueryTimeoutError:
    frappe.msgprint("Impossible d'obtenir le verrou - réessayez plus tard", alert=True)

Compatibilité multi-engines

Le mécanisme est implémenté pour les bases de données suivantes :

Base de donnéesMécanisme utiliséPortée du verrou
PostgreSQLpg_advisory_lockSession
MariaDBGET_LOCK/RELEASE_LOCKSession
SQLiteNon implémenté (lève NotImplementedError)

Bonnes pratiques

  1. Durée des verrous :
    • Limitez la durée des verrous à quelques secondes
    • Utilisez le paramètre timeout pour éviter les attentes infinies
  2. Clés de verrouillage :
    • Préférez des clés explicites comme "repost_stock_ART-001"
    • Évitez les clés génériques qui pourraient créer des goulots d'étranglement
  3. Gestion des erreurs :
    • Toujours capturer QueryTimeoutError pour proposer une relance
    • Journaliser les échecs de verrouillage pour le débogage
  4. Alternatives :
    • Pour les opérations simples, préférez les verrous de ligne classiques (FOR UPDATE)
    • Pour les traitements longs, envisagez des files de messages (ex : Redis)

Cas d'usage typiques

1. Réapprovisionnement concurrent

@frappe.whitelist()
def trigger_replenishment(item_code):
    try:
        with db.advisory_lock(f"replenish_{item_code}", timeout=10):
            if not is_replenishment_in_progress(item_code):
                create_replenishment_order(item_code)
    except db.QueryTimeoutError:
        frappe.log_error(f"Replenishment locked for {item_code}")
        return False
    return True

2. Génération de rapports sensibles

@frappe.whitelist()
def generate_financial_report(company, fiscal_year):
    report_key = f"financial_report_{company}_{fiscal_year}"
    
    try:
        with db.advisory_lock(report_key, timeout=30):
            data = get_report_data(company, fiscal_year)
            return build_report(data)
    except db.QueryTimeoutError:
        frappe.throw("Un autre utilisateur génère actuellement ce rapport")

Limitations

  • Portée session : Les verrous sont liés à la connexion à la base de données. En cas de reconnexion (ex : timeout réseau), le verrou est perdu.
  • SQLite : Non supporté - levera une exception NotImplementedError.
  • Performances : Un trop grand nombre de verrous simultanés peut dégrader les performances.

Dépannage

SymptômeCause possibleSolution
QueryTimeoutError fréquentVerrou maintenu trop longtempsRéduire la durée des transactions ou augmenter le timeout
Verrou non libéréTransaction interrompueRedémarrer la session DB ou attendre le timeout automatique
Deadlocks persistantsOrdre des verrous incohérentStandardiser l'ordre d'acquisition des verrous

Références