From 9aa0de6539f690098fd6ba72b9d5c822c3683d48 Mon Sep 17 00:00:00 2001 From: Valentin Samir Date: Mon, 24 Feb 2014 14:48:03 +0100 Subject: [PATCH] Travaux sur les locks, ajout de context manager pour les cransLdapObject MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit D'une façon général, on s'assure que tous les locks concernant un cransLdapObject sont bien mis avec l'identifiant de lock cransLdapObject.lockId. Avant d'entrer dans le context manager du cransLdapObject, on fait bien attention d'intercepter les exceptions pouvant être levée pour libérer les locks potentiellement déjà posés avant de propager l'exception. Si on essayer d'appeler une methode d'enregistrement (.save() .delete() .create()) sans utiliser un context manager, on affiche un warning sur stderr. À terme ça serait bien de n'utiliser que des context manager pour être sûr qu'on ne laisse pas de lock traîner dans la base de donnée. Il faut bien sûr faire attention de bien ajouter les lock avec l'identifiant cransLdapObject.lockId puisqu'on se base là dessus pour les libérer. Si on a utiliser une context manager, en en sortant, on rend le cransLdapObject read only (de façon douce en modifiant le cransLdapObject.mode et de façon force en changeant les methodes save create delete pour lever l'exception EnvironmentError("Hors du context, impossible de faire des écritures")) --- lc_ldap.py | 18 ++++++++++-------- objets.py | 54 +++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 51 insertions(+), 21 deletions(-) diff --git a/lc_ldap.py b/lc_ldap.py index 8c136a4..4d1c28f 100644 --- a/lc_ldap.py +++ b/lc_ldap.py @@ -372,18 +372,20 @@ class lc_ldap(ldap.ldapobject.LDAPObject, object): données encodées.''' # Ajout des locks, on instancie les attributs qui ne sont pas # des id, ceux-ci étant déjà lockés. - for key, values in uldif.iteritems(): - attribs = [attributs.attrify(val, key, self) for val in values] - for attribut in attribs: - if attribut.unique: - self.lockholder.addlock(key, str(attribut)) + Id=None try: - return objets.new_cransldapobject(self, dn, 'rw', uldif) - except ldap_locks.LockError: + obj = objets.new_cransldapobject(self, dn, 'rw', uldif) + Id = obj.lockId for key, values in uldif.iteritems(): attribs = [attributs.attrify(val, key, self) for val in values] for attribut in attribs: - self.lockholder.removelock(key, str(attribut)) + if attribut.unique: + self.lockholder.addlock(key, str(attribut), Id=Id) + return obj + except ldap_locks.LockError: + # On supprime seulement les locks que l'on vient de poser + if Id is not None: + self.lockholder.purge(Id) raise def _find_id(self, attr, realm=None): diff --git a/objets.py b/objets.py index 1282233..13f33fb 100644 --- a/objets.py +++ b/objets.py @@ -60,6 +60,7 @@ if not "/usr/scripts/" in sys.path: sys.path.append('/usr/scripts/') import gestion.config as config +import cranslib.deprecated #: Champs à ignorer dans l'historique HIST_IGNORE_FIELDS = ["modifiersName", "entryCSN", "modifyTimestamp", "historique"] @@ -105,6 +106,10 @@ class CransLdapObject(object): attribs = [] + @property + def lockId(self): + return '%s_%s' % (os.getpid(), id(self)) + def __init__(self, conn, dn, mode='ro', uldif=None): ''' Créée une instance d'un objet Crans (machine, adhérent, @@ -114,6 +119,7 @@ class CransLdapObject(object): if not isinstance(conn, lc_ldap.lc_ldap): raise TypeError("conn doit être une instance de lc_ldap") + self.in_context = False self.conn = conn self.attrs = attributs.AttrsDict(conn, Parent=self) # Contient un dico ldif qui doit représenter ce qui @@ -162,6 +168,27 @@ class CransLdapObject(object): nvals = [nldif[attr][vals.index(v)] for v in vals ] raise EnvironmentError("λv. str(Attr(v)) n'est peut-être pas une projection (ie non idempotente):", attr, nvals, vals) + + def _out_of_context(self, *args, **kwargs): + raise EnvironmentError("Hors du context, impossible de faire des écritures") + + def __exit__(self, exc_type, exc_value, traceback): + # Sortie du context manager + self.in_context = False + # On rend les écriture impossible + self.save = self._out_of_context + self.create = self._out_of_context + self.delete = self._out_of_context + # On retombe en read only + self.mode = 'ro' + # On purge les lock de l'objet + self.conn.lockholder.purge(self.lockId) + + def __enter__(self): + # On est dans un context, normalement, c'est locksafe + self.in_context = True + return self + def __ne__(self, obj): return not self == obj def __eq__(self, obj): @@ -268,6 +295,9 @@ class CransLdapObject(object): """Crée l'objet dans la base ldap, cette méthode vise à faire en sorte que l'objet se crée lui-même, si celui qui essaye de le modifier a les droits de le faire.""" + if not self.in_context: + # forcer l'utilisation d'un context manager permet d'être certain que les locks seront libéré quoi qu'il arrive + cranslib.deprecated.usage("Les nouveaux objets ne devrait être initialisés qu'avec des contexts managers (with func() as variable)", level=2) try: if login is None: login = self.conn.current_login @@ -302,13 +332,7 @@ class CransLdapObject(object): return finally: # On nettoie les locks - for key, values in self._modifs.to_ldif().iteritems(): - for value in values: - try: - self.conn.lockholder.removelock(key, value) - except: - pass - self.conn.lockholder.purge(id(self)) + self.conn.lockholder.purge(self.lockId) # Services à relancer services.services_to_restart(self.conn, {}, self._modifs, created_object=[self]) @@ -344,9 +368,12 @@ class CransLdapObject(object): raise EnvironmentError("Objet en lecture seule, réessayer en lecture/écriture") if not self.may_be(variables.deleted, self.conn.droits): raise EnvironmentError("Vous n'avez pas le droit de supprimer %s." % self.dn) + if not self.in_context: + # forcer l'utilisation d'un context manager permet d'être certain que les locks seront libéré quoi qu'il arrive + cranslib.deprecated.usage("La suppression d'un objet ne devrait être faite qu'avec des contexts managers (with func() as variable)", level=2) self.bury(comm, login) self.conn.delete_s(self.dn) - self.conn.lockholder.purge(id(self)) + self.conn.lockholder.purge(self.lockId) self._post_deletion() services.services_to_restart(self.conn, self.attrs, {}, deleted_object=[self]) @@ -356,6 +383,9 @@ class CransLdapObject(object): enregistre les modifications.""" if self.mode not in ['w', 'rw']: raise EnvironmentError("Objet en lecture seule, réessayer en lecture/écriture") + if not self.in_context: + # forcer l'utilisation d'un context manager permet d'être certain que les locks seront libéré quoi qu'il arrive + cranslib.deprecated.usage("Les écritures sur un objet ne devrait être faite qu'avec des contexts managers (with func() as variable)", level=2) self._check_optionnal(comment="modifiez") @@ -363,12 +393,10 @@ class CransLdapObject(object): modlist = self.get_modlist() try: self.conn.modify_s(self.dn, modlist) - self.conn.lockholder.purge(id(self)) - self.conn.lockholder.purge() + self.conn.lockholder.purge(self.lockId) except Exception as error: # On nettoie les locks - self.conn.lockholder.purge(id(self)) - self.conn.lockholder.purge() + self.conn.lockholder.purge(self.lockId) self._modifs = self.attrs raise EnvironmentError("Impossible de modifier l'objet, peut-être n'existe-t-il pas ? %r" % error) @@ -492,7 +520,7 @@ class CransLdapObject(object): for attribut in attrs_before_verif: if attribut.unique: try: - self.conn.lockholder.addlock(attr, str(attribut), id(self)) + self.conn.lockholder.addlock(attr, str(attribut), self.lockId) except: self._modifs[attr] = list(self.attrs[attr]) raise