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.
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 :
advisory_lock()Dodock fournit un gestionnaire de contexte frappe.db.advisory_lock(key, *, timeout=10) qui :
withQueryTimeoutError si le verrou n'est pas obtenufrom 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)
Le mécanisme est implémenté pour les bases de données suivantes :
| Base de données | Mécanisme utilisé | Portée du verrou |
|---|---|---|
| PostgreSQL | pg_advisory_lock | Session |
| MariaDB | GET_LOCK/RELEASE_LOCK | Session |
| SQLite | Non implémenté (lève NotImplementedError) |
timeout pour éviter les attentes infinies"repost_stock_ART-001"QueryTimeoutError pour proposer une relanceFOR UPDATE)@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
@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")
NotImplementedError.| Symptôme | Cause possible | Solution |
|---|---|---|
QueryTimeoutError fréquent | Verrou maintenu trop longtemps | Réduire la durée des transactions ou augmenter le timeout |
| Verrou non libéré | Transaction interrompue | Redémarrer la session DB ou attendre le timeout automatique |
| Deadlocks persistants | Ordre des verrous incohérent | Standardiser l'ordre d'acquisition des verrous |