---
slug: 4-verrouiller-buzhug
title: Verrouiller de Buzhug
date: "2012-02-07T00:00:00+01:00"
categories: [Dev]
tags:
- Python
- Buzhug
summary: >
  Comment implémenter un verrou inter-processus et système pour Buzhug
---

J'ai récemment décidé d'utiliser [Buzhug] pour un projet. À ce que je puisse en
juger, il s'est avéré efficace, rapide, facile à utiliser et à maintenir.
Cependant, j'ai rencontré quelques problèmes.

[Buzhug]: http://buzhug.sourceforge.net

## Les solutions simples sont souvent les meilleures

J'en suis venu à utiliser Buzhug pour les raisons suivantes :

- J'avais besoin d'une seule table
- Je ne voulais pas ajouter de dépendances supplémentaires au projet
- La taille de la table serait en moyenne de 5K entrées (sans dépasser
  10k entrées en pic)

Et une raison supplémentaire (personnelle) :

- Je ne voulais pas me soucier de SQL. Vraiment pas. Pas question !

Cela ne me laissait qu'une option : une base de données embarquée en pur Python.

Après avoir considéré quelques bibliothèques, j'ai été séduit par la manière
dont l'interface de Buzhug est proche de la manipulation d'objets Python. Et les
benchmarks semblaient montrer qu'il est assez performant pour ce projet.

Après un rapide prototypage (1 jour), le choix était fait.

Puis vinrent quelques semaines de développement et les premiers tests de
charge...

## Et la réalité est revenue rapidement

Plusieurs fois par jour, l'application soutenue par cette base de données est
intensément utilisée :

- Elle peut être exécutée jusqu'à 50 fois simultanément dans des processus
  Python séparés
- Chaque exécution effectue une opération de lecture et une opération
  d'écriture/suppression

Cela provoque une condition de course sur les fichiers utilisés pour stocker les
données, et les écritures concurrentes corrompent la base de données.

L'utilisation de `buzhug.TS_Base` au lieu de `buzhug.Base` n'a rien résolu, car
le problème n'est pas lié aux threads, mais aux processus. Ce dont j'ai besoin
est un verrou inter-processus.

## Voici la solution

La première étape a été de trouver comment implémenter un verrou inter-processus
et système.

Comme cela doit seulement fonctionner sur Linux, la
[classe Lock donnée par Chris de
Vmfarms](http://blog.vmfarms.com/2011/03/cross-process-locking-and.html) convient
parfaitement. Voici une version légèrement modifiée pour en faire un gestionnaire de contexte :

```python
import fcntl

class PsLock:
    """
    Adapté de :
    http://blog.vmfarms.com/2011/03/cross-process-locking-and.html
    """
    def __init__(self, filename):
        self.filename = filename
        self.handle = open(filename, 'w')
    
    # Faites un OR bit à bit avec fcntl.LOCK_NB si vous avez besoin d'un verrou non bloquant
    def acquire(self):
        fcntl.flock(self.handle, fcntl.LOCK_EX)
        
    def release(self):
        fcntl.flock(self.handle, fcntl.LOCK_UN)
        
    def __del__(self):
        self.handle.close()
        
    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is None:
            pass
        self.release()
        
    def __enter__(self):
        self.acquire()
```

La deuxième étape consiste à définir une nouvelle classe qui hérite de
`buzhug.Base` et qui utilise `PsLock` (inspiré de `TS_Base`) :

```python
import buzhug

_lock = PsLock("/tmp/buzhug.lck")

class PS_Base(buzhug.Base):
    def create(self, *args, **kw):
        with _lock:
            res = buzhug.Base.create(self, *args, **kw)
        return res

    def open(self, *args, **kw):
        with _lock:
            res = buzhug.Base.open(self, *args, **kw)
        return res

    def close(self, *args, **kw):
        with _lock:
            res = buzhug.Base.close(self, *args, **kw)
        return res
        
    def destroy(self, *args, **kw):
        with _lock:
            res = buzhug.Base.destroy(self, *args, **kw)
        return res
        
    def set_default(self, *args, **kw):
        with _lock:
            res = buzhug.Base.set_default(self, *args, **kw)
        return res
        
    def insert(self, *args, **kw):
        with _lock:
            res = buzhug.Base.insert(self, *args, **kw)
        return res
        
    def update(self, *args, **kw):
        with _lock:
            res = buzhug.Base.update(self, *args, **kw)
        return res
        
    def delete(self, *args, **kw):
        with _lock:
            res = buzhug.Base.delete(self, *args, **kw)
        return res
        
    def cleanup(self, *args, **kw):
        with _lock:
            res = buzhug.Base.cleanup(self, *args, **kw)
        return res
        
    def commit(self, *args, **kw):
        with _lock:
            res = buzhug.Base.commit(self, *args, **kw)
        return res
        
    def add_field(self, *args, **kw):
        with _lock:
            res = buzhug.Base.add_field(self, *args, **kw)
        return res
        
    def drop_field(self, *args, **kw):
        with _lock:
            res = buzhug.Base.drop_field(self, *args, **kw)
        return res
```

Maintenant, j'utilise simplement

```python
    database = PS_Base( ... )
```

Et toutes les erreurs ont disparu.
