# -*- mode: python -*- # -*- coding: utf-8 -*- """essai: ceci serait un module scodoc/sco_xxx.py """ import time import thread from scodoc_manager import sco_mgr import app.scodoc.sco_utils as scu from app.scodoc.notes_log import log from app.scodoc.sco_exceptions import NoteProcessError from app.scodoc import sco_cache # # Cache global: chaque instance, repérée par sa connexion db, a un cache # qui est recréé à la demande # NOTES_CACHE_INST = {} # { db_cnx_string : CacheNotesTable instance } CACHE_formsemestre_inscription = {} CACHE_evaluations = {} # cache notes evaluations def get_evaluations_cache(context): """returns cache for evaluations""" u = sco_mgr.get_db_uri() if CACHE_evaluations.has_key(u): return CACHE_evaluations[u] else: log("get_evaluations_cache: new simpleCache") CACHE_evaluations[u] = sco_cache.simpleCache() return CACHE_evaluations[u] class CacheNotesTable: """gestion rudimentaire de cache pour les NotesTables""" def __init__(self): log("new CacheTable (id=%s)" % id(self)) # self.lock = thread.allocate_lock() self.owner_thread = None # thread owning this cache self.nref = 0 # Cache des NotesTables self.cache = {} # { formsemestre_id : NoteTable instance } # Cache des classeur PDF (bulletins) self.pdfcache = {} # { formsemestre_id : (filename, pdfdoc) } # Listeners: self.listeners = scu.DictDefault( defaultvalue={} ) # {formsemestre_id : {listener_id : callback }} def acquire(self): "If this thread does not own the cache, acquire the lock" if thread.get_ident() != self.owner_thread: if self.lock.locked(): log( "acquire: ident=%s waiting for lock" % thread.get_ident() ) # XXX debug self.lock.acquire() self.owner_thread = thread.get_ident() if self.owner_thread is None: # bug catching log("WARNING: None thread id !") self.nref += 1 # log('nref=%d' % self.nref) def release(self): "Release the lock" cur_owner_thread = self.owner_thread # log('release: ident=%s (nref=%d)' % (thread.get_ident(), self.nref)) self.nref -= 1 if self.nref == 0: self.lock.release() self.owner_thread = None # Debug: if thread.get_ident() != cur_owner_thread: log( "WARNING: release: ident=%s != owner=%s nref=%d" % (thread.get_ident(), cur_owner_thread, self.nref) ) raise NoteProcessError("problem with notes cache") def get_NotesTable(self, context, formsemestre_id): # > import notes_table try: self.acquire() if self.cache.has_key(formsemestre_id): # log('cache hit %s (id=%s, thread=%s)' # % (formsemestre_id, id(self), thread.get_ident())) return self.cache[formsemestre_id] else: t0 = time.time() nt = notes_table.NotesTable(context, formsemestre_id) dt = time.time() - t0 self.cache[formsemestre_id] = nt log( "caching formsemestre_id=%s (id=%s) (%gs)" % (formsemestre_id, id(self), dt) ) return nt finally: self.release() def get_cached_formsemestre_ids(self): "List of currently cached formsemestre_id" return self.cache.keys() def inval_cache(self, context, formsemestre_id=None, pdfonly=False): # > "expire cache pour un semestre (ou tous si pas d'argument)" from app.scodoc import sco_parcours_dut log( "inval_cache, formsemestre_id=%s pdfonly=%s (id=%s)" % (formsemestre_id, pdfonly, id(self)) # > ) try: self.acquire() if not hasattr(self, "pdfcache"): self.pdfcache = {} # fix for old zope instances... if formsemestre_id is None: # clear all caches log("----- inval_cache: clearing all caches -----") # log('cache was containing ' + str(self.cache.keys())) # logCallStack() # >>> DEBUG <<< if not pdfonly: self.cache = {} self.pdfcache = {} self._call_all_listeners() get_evaluations_cache( context, ).inval_cache() else: # formsemestre_id modifié: # on doit virer formsemestre_id et tous les semestres # susceptibles d'utiliser des UE capitalisées de ce semestre. to_trash = [ formsemestre_id ] + sco_parcours_dut.list_formsemestre_utilisateurs_uecap( context, formsemestre_id ) if not pdfonly: for formsemestre_id in to_trash: if self.cache.has_key(formsemestre_id): log( "delete %s from cache (id=%s)" % (formsemestre_id, id(self)) ) del self.cache[formsemestre_id] self._call_listeners(formsemestre_id) get_evaluations_cache( context, ).inval_cache() for formsemestre_id in to_trash: for ( cached_formsemestre_id, cached_version, ) in self.pdfcache.keys(): if cached_formsemestre_id == formsemestre_id: log( "delete pdfcache[(%s,%s)]" % (formsemestre_id, cached_version) ) del self.pdfcache[(formsemestre_id, cached_version)] finally: self.release() def store_bulletins_pdf(self, formsemestre_id, version, filename, pdfdoc): "cache pdf data" log( "caching PDF formsemestre_id=%s version=%s (id=%s)" % (formsemestre_id, version, id(self)) ) try: self.acquire() self.pdfcache[(formsemestre_id, version)] = (filename, pdfdoc) finally: self.release() def get_bulletins_pdf(self, formsemestre_id, version): "returns cached PDF, or None if not in the cache" try: self.acquire() if not hasattr(self, "pdfcache"): self.pdfcache = {} # fix for old zope instances... r = self.pdfcache.get((formsemestre_id, version), None) if r: log( "get_bulletins_pdf(%s): cache hit %s (id=%s, thread=%s)" % (version, formsemestre_id, id(self), thread.get_ident()) ) return r finally: self.release() def add_listener(self, callback, formsemestre_id, listener_id): """Add a "listener": a function called each time a formsemestre is modified""" self.listeners[formsemestre_id][listener_id] = callback def remove_listener(self, formsemestre_id, listener_id): """Remove a listener. May raise exception if does not exists. """ del self.listeners[formsemestre_id][listener_id] def _call_listeners(self, formsemestre_id): for listener_id, callback in self.listeners[formsemestre_id].items(): callback(listener_id) def _call_all_listeners(self): for formsemestre_id in self.listeners: self._call_listeners(formsemestre_id) def get_notes_cache(context): "returns CacheNotesTable instance for us" u = sco_mgr.get_db_uri() # identifie le dept de facon unique if not NOTES_CACHE_INST.has_key(u): log("getNotesCache: creating cache for %s" % u) NOTES_CACHE_INST[u] = CacheNotesTable() return NOTES_CACHE_INST[u] def inval_cache( context, formsemestre_id=None, pdfonly=False, formsemestre_id_list=None ): # > "expire cache pour un semestre (ou tous si pas d'argument)" if formsemestre_id_list: for formsemestre_id in formsemestre_id_list: get_notes_cache(context).inval_cache( context, formsemestre_id=formsemestre_id, pdfonly=pdfonly ) # Affecte aussi cache inscriptions get_formsemestre_inscription_cache(context).inval_cache(key=formsemestre_id) else: get_notes_cache(context).inval_cache( context, formsemestre_id=formsemestre_id, pdfonly=pdfonly ) # Affecte aussi cache inscriptions get_formsemestre_inscription_cache(context).inval_cache(key=formsemestre_id) # Cache inscriptions semestres def get_formsemestre_inscription_cache(context, format=None): u = sco_mgr.get_db_uri() if CACHE_formsemestre_inscription.has_key(u): return CACHE_formsemestre_inscription[u] else: log("get_formsemestre_inscription_cache: new simpleCache") CACHE_formsemestre_inscription[u] = sco_cache.simpleCache() return CACHE_formsemestre_inscription[u]