Edgewall Software

Ticket #4586: sync_using_youngest_rev_metadata-r4871.3.diff

File sync_using_youngest_rev_metadata-r4871.3.diff, 14.0 KB (added by cboos, 21 months ago)

Take 3: fix an error with the log.warning call (spotted by Tim) and make sure that self.youngest is a normalized rev (see #4830)

  • trac/versioncontrol/api.py

     
    137137        """Close the connection to the repository.""" 
    138138        raise NotImplementedError 
    139139 
    140     def clear(self): 
    141         """Clear any data that may have been cached in instance properties.""" 
     140    def clear(self, youngest_rev=None): 
     141        """Clear any data that may have been cached in instance properties. 
     142 
     143        `youngest_rev` can be specified as a way to force the value 
     144        of the `youngest_rev` property (''will change in 0.12''). 
     145        """ 
    142146        pass 
    143147 
    144148    def get_quickjump_entries(self, rev): 
     
    222226        The way revisions are sequenced is version control specific. 
    223227        By default, one assumes that the revisions are sequenced in time 
    224228        (... which is ''not'' correct for most VCS, including Subversion). 
     229 
     230        (Deprecated, will not be used anymore in Trac 0.12) 
    225231        """ 
    226232        cursor = db.cursor() 
    227233        cursor.execute("SELECT rev FROM revision ORDER BY time DESC LIMIT 1") 
  • trac/versioncontrol/tests/cache.py

     
    3131    def setUp(self): 
    3232        self.db = InMemoryDatabase() 
    3333        self.log = logger_factory('test') 
     34        cursor = self.db.cursor() 
     35        cursor.execute("INSERT INTO system (name, value) VALUES (%s,%s)", 
     36                       ('youngest_rev', '')) 
    3437 
    3538    def test_initial_sync_with_empty_repos(self): 
    3639        t = datetime(2001, 1, 1, 1, 1, 1, 0, utc) 
     
    9699                           "VALUES ('1',%s,%s,%s,%s,%s)", 
    97100                           [('trunk', 'D', 'A', None, None), 
    98101                            ('trunk/README', 'F', 'A', None, None)]) 
     102        cursor.execute("UPDATE system SET value='1' WHERE name='youngest_rev'") 
    99103 
    100104        changes = [('trunk/README', Node.FILE, Changeset.EDIT, 'trunk/README', 1)] 
    101105        changeset = Mock(Changeset, 2, 'Update', 'joe', t3, 
     
    103107        repos = Mock(Repository, 'test-repos', None, self.log, 
    104108                     get_changeset=lambda x: changeset, 
    105109                     get_youngest_rev=lambda: 2, 
    106                      next_rev=lambda x: int(x) == 1 and 2 or None) 
     110                     get_oldest_rev=lambda: 0, 
     111                     normalize_rev=lambda x: x,                     
     112                     next_rev=lambda x: x and int(x) == 1 and 2 or None) 
    107113        cache = CachedRepository(self.db, repos, None, self.log) 
    108114        cache.sync() 
    109115 
     
    130136                           "VALUES ('1',%s,%s,%s,%s,%s)", 
    131137                           [('trunk', 'D', 'A', None, None), 
    132138                            ('trunk/README', 'F', 'A', None, None)]) 
     139        cursor.execute("UPDATE system SET value='1' WHERE name='youngest_rev'") 
    133140 
    134141        repos = Mock(Repository, 'test-repos', None, self.log, 
    135142                     get_changeset=lambda x: None, 
    136143                     get_youngest_rev=lambda: 1, 
    137                      next_rev=lambda x: None, normalize_rev=lambda rev: rev) 
     144                     get_oldest_rev=lambda: 0, 
     145                     next_rev=lambda x: None, 
     146                     normalize_rev=lambda rev: rev) 
    138147        cache = CachedRepository(self.db, repos, None, self.log) 
    139148        self.assertEqual('1', cache.youngest_rev) 
    140149        changeset = cache.get_changeset(1) 
  • trac/versioncontrol/svn_fs.py

     
    392392        assert self.scope[0] == '/' 
    393393        self.clear() 
    394394 
    395     def clear(self): 
     395    def clear(self, youngest_rev=None): 
    396396        self.youngest = None 
     397        if youngest_rev is not None: 
     398            self.youngest = self.normalize_rev(youngest_rev) 
    397399        self.oldest = None 
    398400 
    399401    def __del__(self): 
  • trac/versioncontrol/cache.py

     
    2727              'D': Changeset.DELETE, 'E': Changeset.EDIT, 
    2828              'M': Changeset.MOVE} 
    2929 
     30CACHE_REPOSITORY_DIR = 'repository_dir' 
     31CACHE_YOUNGEST_REV = 'youngest_rev' 
    3032 
    3133class CachedRepository(Repository): 
    3234 
     
    3436        Repository.__init__(self, repos.name, authz, log) 
    3537        self.db = db 
    3638        self.repos = repos 
    37         try: 
    38             self.sync() 
    39         except TracError: 
    40             raise 
    41         except Exception, e: # most probably 2 concurrent resync attempts 
    42             log.warning('Error during sync(): %s' % e)  
     39        self.sync() 
    4340 
    4441    def close(self): 
    4542        self.repos.close() 
     
    6461    def sync(self): 
    6562        cursor = self.db.cursor() 
    6663 
    67         # -- repository used for populating the cache 
    68         cursor.execute("SELECT value FROM system WHERE name='repository_dir'") 
    69         for previous_repository_dir, in cursor: 
    70             if previous_repository_dir != self.name: 
     64        cursor.execute("SELECT name, value FROM system " 
     65                       "WHERE name IN ('%s', '%s')" % 
     66                       (CACHE_REPOSITORY_DIR, CACHE_YOUNGEST_REV)) 
     67        metadata = {} 
     68        for name, value in cursor: 
     69            metadata[name] = value 
     70         
     71        # -- check that we're populating the cache for the correct repository 
     72        repository_dir = metadata.get(CACHE_REPOSITORY_DIR) 
     73        if repository_dir: 
     74            if repository_dir != self.name: 
    7175                raise TracError("The 'repository_dir' has changed, " 
    7276                                "a 'trac-admin resync' operation is needed.") 
    73             break 
    7477        else: # no 'repository_dir' stored yet, assume everything's OK 
    75             cursor.execute("INSERT INTO system (name,value) " 
    76                            "VALUES ('repository_dir',%s)", (self.name,)) 
     78            cursor.execute("INSERT INTO system (name,value) VALUES (%s,%s)", 
     79                           (CACHE_REPOSITORY_DIR, self.name,)) 
    7780 
     81        # -- retrieve the youngest revision cached so far 
     82        if CACHE_YOUNGEST_REV not in metadata: 
     83            # ''upgrade'' using the legacy `get_youngest_rev_in_cache` method 
     84            self.youngest = self.repos.get_youngest_rev_in_cache(self.db) or '' 
     85            cursor.execute("INSERT INTO system (name, value) VALUES (%s, %s)", 
     86                           (CACHE_YOUNGEST_REV, self.youngest)) 
     87            self.log.info('Upgraded cache metadata (youngest_rev=%s)' % 
     88                          self.youngest_rev) 
     89        else: 
     90            self.youngest = metadata[CACHE_YOUNGEST_REV] 
     91 
     92        if self.youngest: 
     93            self.youngest = self.repos.normalize_rev(self.youngest) 
     94        else: 
     95            self.youngest = None 
     96 
     97        # -- retrieve the youngest revision in the repository 
    7898        self.repos.clear() 
    79         youngest_stored = self.repos.get_youngest_rev_in_cache(self.db) 
     99        repos_youngest = self.repos.youngest_rev 
    80100 
    81         if youngest_stored != str(self.repos.youngest_rev): 
     101        # -- compare them and try to resync if different 
     102        if self.youngest != repos_youngest: 
     103            if self.youngest: 
     104                next_youngest = self.repos.next_rev(self.youngest) 
     105            else: 
     106                next_youngest = None 
     107                try: 
     108                    next_youngest = self.repos.oldest_rev 
     109                    next_youngest = self.repos.normalize_rev(next_youngest) 
     110                except TracError: 
     111                    pass 
     112 
     113            if next_youngest is None: # nothing to cache yet 
     114                return 
     115 
     116            # 0. first check if there's no (obvious) resync in progress 
     117            cursor.execute("SELECT rev FROM revision WHERE rev=%s", 
     118                           (str(next_youngest),)) 
     119            for rev, in cursor: 
     120                # already there, but in progress, so keep ''previous'' 
     121                # notion of 'youngest' 
     122                self.repos.clear(youngest_rev=self.youngest) 
     123                return 
     124 
     125            # 1. prepare for resyncing 
     126            #    (there still might be a race condition at this point) 
     127 
    82128            authz = self.repos.authz 
    83129            self.repos.authz = Authorizer() # remove permission checking 
    84130 
    85131            kindmap = dict(zip(_kindmap.values(), _kindmap.keys())) 
    86132            actionmap = dict(zip(_actionmap.values(), _actionmap.keys())) 
    87             self.log.info("Syncing with repository (%s to %s)" 
    88                           % (youngest_stored, self.repos.youngest_rev)) 
    89             if youngest_stored: 
    90                 current_rev = self.repos.next_rev(youngest_stored) 
    91             else: 
    92                 try: 
    93                     current_rev = self.repos.oldest_rev 
    94                     current_rev = self.repos.normalize_rev(current_rev) 
    95                 except TracError: 
    96                     current_rev = None 
    97             while current_rev is not None: 
    98                 changeset = self.repos.get_changeset(current_rev) 
    99                 cursor.execute("INSERT INTO revision (rev,time,author,message) " 
    100                                "VALUES (%s,%s,%s,%s)", (str(current_rev), 
    101                                                         to_timestamp(changeset.date), 
    102                                                         changeset.author, 
    103                                                         changeset.message)) 
    104                 for path,kind,action,base_path,base_rev in changeset.get_changes(): 
    105                     self.log.debug("Caching node change in [%s]: %s" 
    106                                    % (current_rev, (path, kind, action, 
    107                                       base_path, base_rev))) 
    108                     kind = kindmap[kind] 
    109                     action = actionmap[action] 
    110                     cursor.execute("INSERT INTO node_change (rev,path," 
    111                                    "node_type,change_type,base_path,base_rev) " 
    112                                    "VALUES (%s,%s,%s,%s,%s,%s)", 
    113                                    (str(current_rev), path, kind, action, 
    114                                    base_path, base_rev)) 
    115                 current_rev = self.repos.next_rev(current_rev) 
    116             self.db.commit() 
    117             self.repos.authz = authz # restore permission checking 
    118133 
     134            try: 
     135                while next_youngest is not None: 
     136                     
     137                    # 1.1 Attempt to resync the 'revision' table 
     138                    self.log.info("Trying to sync revision [%s]" % 
     139                                  next_youngest) 
     140                    cset = self.repos.get_changeset(next_youngest) 
     141                    try: 
     142                        cursor.execute("INSERT INTO revision " 
     143                                       " (rev,time,author,message) " 
     144                                       "VALUES (%s,%s,%s,%s)", 
     145                                       (str(next_youngest), 
     146                                        to_timestamp(cset.date), 
     147                                        cset.author, cset.message)) 
     148                        self.db.commit() 
     149                    except Exception, e: # *another* 1.1. resync attempt won  
     150                        self.log.warning('Revision %s already cached: %s' % 
     151                                         (next_youngest, e)) 
     152                        # also potentially in progress, so keep ''previous'' 
     153                        # notion of 'youngest' 
     154                        return 
     155 
     156                    # 1.2. now *only* one process was able to get there 
     157                    #      (i.e. there *shouldn't* be any race condition here) 
     158 
     159                    for path,kind,action,bpath,brev in cset.get_changes(): 
     160                        self.log.debug("Caching node change in [%s]: %s" 
     161                                       % (next_youngest, 
     162                                          (path,kind,action,bpath,brev))) 
     163                        kind = kindmap[kind] 
     164                        action = actionmap[action] 
     165                        cursor.execute("INSERT INTO node_change " 
     166                                       " (rev,path,node_type,change_type, " 
     167                                       "  base_path,base_rev) " 
     168                                       "VALUES (%s,%s,%s,%s,%s,%s)", 
     169                                       (str(next_youngest), 
     170                                        path, kind, action, bpath, brev)) 
     171 
     172                    # 1.3. iterate (1.1 should always succeed now) 
     173                    self.youngest = next_youngest                     
     174                    next_youngest = self.repos.next_rev(next_youngest) 
     175 
     176                # 2. update 'youngest_rev' metadata (minimize failures at 0.) 
     177                cursor.execute("UPDATE system SET value=%s WHERE name=%s", 
     178                               (str(self.youngest), CACHE_YOUNGEST_REV)) 
     179                self.db.commit() 
     180            finally: 
     181                # 3. restore permission checking (after 1.) 
     182                self.repos.authz = authz 
     183 
    119184    def get_node(self, path, rev=None): 
    120185        return self.repos.get_node(path, rev) 
    121186 
     
    126191        return self.repos.oldest_rev 
    127192 
    128193    def get_youngest_rev(self): 
    129         return self.repos.get_youngest_rev_in_cache(self.db) 
     194        return self.youngest 
    130195 
    131196    def previous_rev(self, rev): 
    132197        return self.repos.previous_rev(rev) 
  • trac/admin/console.py

     
    600600        cursor.execute("DELETE FROM revision") 
    601601        cursor.execute("DELETE FROM node_change") 
    602602        cursor.execute("DELETE FROM system WHERE name='repository_dir'") 
     603        cursor.execute("DELETE FROM system WHERE name='youngest_rev'") 
     604        cursor.execute("INSERT INTO system (name, value) " 
     605                       "VALUES ('youngest_rev', '')") 
    603606        repos = self.__env.get_repository() # this will do the sync() 
    604607        print 'Done.' 
    605608