Edgewall Software

Ticket #4586: sync_using_youngest_rev_metadata-r4871.2.diff

File sync_using_youngest_rev_metadata-r4871.2.diff, 13.8 KB (added by cboos, 5 years ago)

Take 2': don't require an environment upgrade (do it transparently) and don't substitute the db argument with an env argument in the CachedRepository? constructor (that will be a separate patch). The trac.web changes are also stripped out. (repost)

  • 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        # -- retrieve the youngest revision in the repository 
    7893        self.repos.clear() 
    79         youngest_stored = self.repos.get_youngest_rev_in_cache(self.db) 
     94        repos_youngest = self.repos.youngest_rev 
    8095 
    81         if youngest_stored != str(self.repos.youngest_rev): 
     96        # -- compare them and try to resync if different 
     97        if self.youngest != str(repos_youngest): 
     98            if self.youngest: 
     99                next_youngest = self.repos.next_rev(self.youngest) 
     100            else: 
     101                next_youngest = None 
     102                try: 
     103                    next_youngest = self.repos.oldest_rev 
     104                    next_youngest = self.repos.normalize_rev(next_youngest) 
     105                except TracError: 
     106                    pass 
     107 
     108            if next_youngest is None: # nothing to cache yet 
     109                return 
     110 
     111            # 0. first check if there's no (obvious) resync in progress 
     112            cursor.execute("SELECT rev FROM revision WHERE rev=%s", 
     113                           (str(next_youngest),)) 
     114            for rev, in cursor: 
     115                # already there, but in progress, so keep ''previous'' 
     116                # notion of 'youngest' 
     117                self.repos.clear(youngest_rev=self.youngest) 
     118                return 
     119 
     120            # 1. prepare for resyncing 
     121            #    (there still might be a race condition at this point) 
     122 
    82123            authz = self.repos.authz 
    83124            self.repos.authz = Authorizer() # remove permission checking 
    84125 
    85126            kindmap = dict(zip(_kindmap.values(), _kindmap.keys())) 
    86127            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 
    118128 
     129            try: 
     130                while next_youngest is not None: 
     131                     
     132                    # 1.1 Attempt to resync the 'revision' table 
     133                    self.log.info("Trying to sync revision [%s]" % 
     134                                  next_youngest) 
     135                    cset = self.repos.get_changeset(next_youngest) 
     136                    try: 
     137                        cursor.execute("INSERT INTO revision " 
     138                                       " (rev,time,author,message) " 
     139                                       "VALUES (%s,%s,%s,%s)", 
     140                                       (str(next_youngest), 
     141                                        to_timestamp(cset.date), 
     142                                        cset.author, cset.message)) 
     143                        self.db.commit() 
     144                    except Exception, e: # *another* 1.1. resync attempt won  
     145                        log.warning('Revision %s already cached: %s' % e) 
     146                        # also potentially in progress, so keep ''previous'' 
     147                        # notion of 'youngest' 
     148                        return 
     149 
     150                    # 1.2. now *only* one process was able to get there 
     151                    #      (i.e. there *shouldn't* be any race condition here) 
     152 
     153                    self.youngest = str(next_youngest) 
     154 
     155                    for path,kind,action,bpath,brev in cset.get_changes(): 
     156                        self.log.debug("Caching node change in [%s]: %s" 
     157                                       % (next_youngest, 
     158                                          (path,kind,action,bpath,brev))) 
     159                        kind = kindmap[kind] 
     160                        action = actionmap[action] 
     161                        cursor.execute("INSERT INTO node_change " 
     162                                       " (rev,path,node_type,change_type, " 
     163                                       "  base_path,base_rev) " 
     164                                       "VALUES (%s,%s,%s,%s,%s,%s)", 
     165                                       (self.youngest, 
     166                                        path, kind, action, bpath, brev)) 
     167 
     168                    # 1.3. iterate (1.1 should always succeed now) 
     169                    next_youngest = self.repos.next_rev(next_youngest) 
     170 
     171                # 2. update 'youngest_rev' metadata (minimize failures at 0.) 
     172                cursor.execute("UPDATE system SET value=%s WHERE name=%s", 
     173                               (self.youngest, CACHE_YOUNGEST_REV)) 
     174                self.db.commit() 
     175            finally: 
     176                # 3. restore permission checking (after 1.) 
     177                self.repos.authz = authz 
     178 
    119179    def get_node(self, path, rev=None): 
    120180        return self.repos.get_node(path, rev) 
    121181 
     
    126186        return self.repos.oldest_rev 
    127187 
    128188    def get_youngest_rev(self): 
    129         return self.repos.get_youngest_rev_in_cache(self.db) 
     189        return self.youngest 
    130190 
    131191    def previous_rev(self, rev): 
    132192        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