Edgewall Software

TracDev/Proposals/CacheInvalidation: cache_control-r7933.diff

File cache_control-r7933.diff, 5.6 KB (added by cboos, 3 years ago)

Proof-of-concept for solution 2 - env cache control and sample usage by InterWikiMap

  • trac/env.py

    Proof-of-concept implementation of cache control
    
    diff --git a/trac/env.py b/trac/env.py
    a b  
    196196            ('setuptools', setuptools.__version__), 
    197197            ] 
    198198        self._href = self._abs_href = None 
     199        self._cache_control = {} 
     200        self._cache_lock = threading.RLock() 
    199201 
    200202        from trac.loader import load_components 
    201203        plugins_dir = self.config.get('inherit', 'plugins_dir') 
     
    481483        return self._abs_href 
    482484    abs_href = property(_get_abs_href, 'The application URL') 
    483485 
     486    # Cache control 
     487 
     488    def update_cache_control(self): 
     489        self._cache_lock.acquire() 
     490        try: 
     491            db = self.get_db_cnx() 
     492            cursor = db.cursor() 
     493            cursor.execute("SELECT key, generation FROM cache_control") 
     494            for key, gen in cursor.fetchall(): 
     495                previous_gen = self._cache_control.get(key, 0) 
     496                if abs(previous_gen) < gen:  
     497                    self._cache_control[key] = -gen 
     498                    self.log.debug("Cache '%s' needs update (%d)", key, gen) 
     499                # otherwise a race happened, a newer generation already made it 
     500                # into the cache control: 
     501                #  - either it's positive, meaning the actual cache was  
     502                #    already updated to a newer generation,  
     503                #  - or it's negative meaning we already target a newer  
     504                #    generation.  
     505                # In both cases we simply do nothing. 
     506        finally: 
     507            self._cache_lock.release() 
     508 
     509    def invalidate_cache(self, key): 
     510        if not isinstance(key, basestring): 
     511            key = key.__class__.__name__ 
     512        self._cache_lock.acquire() 
     513        try: 
     514            db = self.get_db_cnx() 
     515            cursor = db.cursor() 
     516            try: 
     517                gen = 1 
     518                cursor.execute("INSERT INTO cache_control VALUES (%s,%s)", 
     519                               (key, gen)) 
     520            except: 
     521                cursor.execute("UPDATE cache_control SET " 
     522                               "generation=generation+1 WHERE key=%s ", (key,)) 
     523                cursor.execute("SELECT generation FROM cache_control " 
     524                               "WHERE key=%s", (key,)) 
     525                cursor.fetchone() 
     526                for gen, in cursor: 
     527                    break 
     528            db.commit() 
     529            self._cache_control[key] = -gen # negative means invalid 
     530            self.log.debug("Cache '%s' invalidated (generation %d)", key, gen) 
     531        finally: 
     532            self._cache_lock.release() 
     533 
     534    def cache_is_valid(self, key): 
     535        if not isinstance(key, basestring): 
     536            key = key.__class__.__name__ 
     537        # Not sure it's worth locking here: in case of a a concurrent  
     538        # invalidation, even if we use the lock, we risk to miss the  
     539        # notification for this request 
     540        return self._cache_control.get(key, 0) >= 0 # negative means invalid 
     541 
     542    def validate_cache(self, key): 
     543        if not isinstance(key, basestring): 
     544            key = key.__class__.__name__ 
     545        self._cache_lock.acquire() 
     546        try: 
     547            gen = abs(self._cache_control.get(key, 1)) 
     548            self._cache_control[key] = gen 
     549            self.log.debug("Cache '%s' updated to generation %d", key, gen) 
     550        finally: 
     551            self._cache_lock.release() 
     552 
     553 
    484554 
    485555class EnvironmentSetup(Component): 
    486556    implements(IEnvironmentSetupParticipant) 
     
    583653                env = None 
    584654            if env is None: 
    585655                env = env_cache.setdefault(env_path, open_environment(env_path)) 
     656            else: 
     657                env.update_cache_control() 
    586658        finally: 
    587659            env_cache_lock.release() 
    588660    else: 
  • trac/wiki/interwiki.py

    diff --git a/trac/wiki/interwiki.py b/trac/wiki/interwiki.py
    a b  
    4444 
    4545    def reset(self): 
    4646        self._interwiki_map = None 
    47         self.config.touch() 
    48         # This dictionary maps upper-cased namespaces 
    49         # to (namespace, prefix, title) values; 
     47        # The above dictionary maps upper-cased namespaces 
     48        # to (namespace, prefix, title) values 
     49        self.env.invalidate_cache(self) 
    5050 
    5151    # The component itself behaves as a map 
    5252 
     
    114114 
    115115    def _get_interwiki_map(self): 
    116116        from trac.wiki.model import WikiPage 
    117         if self._interwiki_map is None: 
     117        if self._interwiki_map is None or not self.env.cache_is_valid(self): 
    118118            self._interwiki_lock.acquire() 
    119119            try: 
    120                 if self._interwiki_map is None: 
     120                if self._interwiki_map is None or not self.env.cache_is_valid(self): 
    121121                    self._interwiki_map = {} 
    122122                    content = WikiPage(self.env, InterWikiMap._page_name).text 
    123123                    in_map = False 
     
    131131                                    prefix, url, title = m.groups() 
    132132                                    url = url.strip() 
    133133                                    title = title and title.strip() or prefix 
    134                                     self[prefix] = (prefix, url, title) 
     134                                    self._interwiki_map[prefix.upper()] = \ 
     135                                            (prefix, url, title) 
    135136                        elif line.startswith('----'): 
    136137                            in_map = True 
     138                    self.env.validate_cache(self) 
    137139            finally: 
    138140                self._interwiki_lock.release() 
    139141        return self._interwiki_map