177 lines
5.1 KiB
Markdown
177 lines
5.1 KiB
Markdown
---
|
|
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.
|