~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/changecache.py

  • Committer: Robey Pointer
  • Date: 2006-12-19 09:46:12 UTC
  • Revision ID: robey@lag.net-20061219094612-04ubkz8afzfgiqve
betterĀ readme

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#
2
 
# Copyright (C) 2006  Robey Pointer <robey@lag.net>
3
 
#
4
 
# This program is free software; you can redistribute it and/or modify
5
 
# it under the terms of the GNU General Public License as published by
6
 
# the Free Software Foundation; either version 2 of the License, or
7
 
# (at your option) any later version.
8
 
#
9
 
# This program is distributed in the hope that it will be useful,
10
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 
# GNU General Public License for more details.
13
 
#
14
 
# You should have received a copy of the GNU General Public License
15
 
# along with this program; if not, write to the Free Software
16
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
 
#
18
 
 
19
 
"""
20
 
a cache for chewed-up "change" data structures, which are basically just a
21
 
different way of storing a revision.  the cache improves lookup times 10x
22
 
over bazaar's xml revision structure, though, so currently still worth doing.
23
 
 
24
 
once a revision is committed in bazaar, it never changes, so once we have
25
 
cached a change, it's good forever.
26
 
"""
27
 
 
28
 
import logging
29
 
import os
30
 
import shelve
31
 
import threading
32
 
import time
33
 
 
34
 
from loggerhead import util
35
 
from loggerhead.util import decorator
36
 
from loggerhead.lockfile import LockFile
37
 
 
38
 
 
39
 
with_lock = util.with_lock('_lock', 'ChangeCache')
40
 
        
41
 
 
42
 
class ChangeCache (object):
43
 
    
44
 
    def __init__(self, history, cache_path):
45
 
        self.history = history
46
 
        self.log = history.log
47
 
        
48
 
        if not os.path.exists(cache_path):
49
 
            os.mkdir(cache_path)
50
 
 
51
 
        # keep a separate cache for the diffs, because they're very time-consuming to fetch.
52
 
        self._changes_filename = os.path.join(cache_path, 'changes')
53
 
        self._changes_diffs_filename = os.path.join(cache_path, 'changes-diffs')
54
 
        
55
 
        # use a lockfile since the cache folder could be shared across different processes.
56
 
        self._lock = LockFile(os.path.join(cache_path, 'lock'))
57
 
        self._closed = False
58
 
        
59
 
        # this is fluff; don't slow down startup time with it.
60
 
        def log_sizes():
61
 
            s1, s2 = self.sizes()
62
 
            self.log.info('Using change cache %s; %d/%d entries.' % (cache_path, s1, s2))
63
 
        threading.Thread(target=log_sizes).start()
64
 
    
65
 
    @with_lock
66
 
    def close(self):
67
 
        self.log.debug('Closing cache file.')
68
 
        self._closed = True
69
 
    
70
 
    @with_lock
71
 
    def closed(self):
72
 
        return self._closed
73
 
 
74
 
    @with_lock
75
 
    def flush(self):
76
 
        pass
77
 
    
78
 
    @with_lock
79
 
    def get_changes(self, revid_list, get_diffs=False):
80
 
        """
81
 
        get a list of changes by their revision_ids.  any changes missing
82
 
        from the cache are fetched by calling L{History.get_change_uncached}
83
 
        and inserted into the cache before returning.
84
 
        """
85
 
        if get_diffs:
86
 
            cache = shelve.open(self._changes_diffs_filename, 'c', protocol=2)
87
 
        else:
88
 
            cache = shelve.open(self._changes_filename, 'c', protocol=2)
89
 
 
90
 
        try:
91
 
            out = []
92
 
            fetch_list = []
93
 
            sfetch_list = []
94
 
            for revid in revid_list:
95
 
                # if the revid is in unicode, use the utf-8 encoding as the key
96
 
                srevid = util.to_utf8(revid)
97
 
                
98
 
                if srevid in cache:
99
 
                    out.append(cache[srevid])
100
 
                else:
101
 
                    #self.log.debug('Entry cache miss: %r' % (revid,))
102
 
                    out.append(None)
103
 
                    fetch_list.append(revid)
104
 
                    sfetch_list.append(srevid)
105
 
            
106
 
            if len(fetch_list) > 0:
107
 
                # some revisions weren't in the cache; fetch them
108
 
                changes = self.history.get_changes_uncached(fetch_list, get_diffs)
109
 
                if changes is None:
110
 
                    return changes
111
 
                for i in xrange(len(revid_list)):
112
 
                    if out[i] is None:
113
 
                        cache[sfetch_list.pop(0)] = out[i] = changes.pop(0)
114
 
            return out
115
 
        finally:
116
 
            cache.close()
117
 
    
118
 
    @with_lock
119
 
    def full(self, get_diffs=False):
120
 
        if get_diffs:
121
 
            cache = shelve.open(self._changes_diffs_filename, 'c', protocol=2)
122
 
        else:
123
 
            cache = shelve.open(self._changes_filename, 'c', protocol=2)
124
 
        try:
125
 
            return (len(cache) >= len(self.history.get_revision_history())) and (util.to_utf8(self.history.last_revid) in cache)
126
 
        finally:
127
 
            cache.close()
128
 
 
129
 
    @with_lock
130
 
    def sizes(self):
131
 
        cache = shelve.open(self._changes_filename, 'c', protocol=2)
132
 
        s1 = len(cache)
133
 
        cache.close()
134
 
        cache = shelve.open(self._changes_diffs_filename, 'c', protocol=2)
135
 
        s2 = len(cache)
136
 
        cache.close()
137
 
        return s1, s2
138
 
        
139
 
    def check_rebuild(self, max_time=3600):
140
 
        """
141
 
        check if we need to fill in any missing pieces of the cache.  pull in
142
 
        any missing changes, but don't work any longer than C{max_time}
143
 
        seconds.
144
 
        """
145
 
        if self.closed() or self.full():
146
 
            return
147
 
        
148
 
        self.log.info('Building revision cache...')
149
 
        start_time = time.time()
150
 
        last_update = time.time()
151
 
        count = 0
152
 
 
153
 
        work = list(self.history.get_revision_history())
154
 
        jump = 100
155
 
        for i in xrange(0, len(work), jump):
156
 
            r = work[i:i + jump]
157
 
            # must call into history so we grab the branch lock (otherwise, lock inversion)
158
 
            self.history.get_changes(r)
159
 
            if self.closed():
160
 
                self.flush()
161
 
                return
162
 
            count += jump
163
 
            now = time.time()
164
 
            if now - start_time > max_time:
165
 
                self.log.info('Cache rebuilding will pause for now.')
166
 
                self.flush()
167
 
                return
168
 
            if now - last_update > 60:
169
 
                self.log.info('Revision cache rebuilding continues: %d/%d' % (min(count, len(work)), len(work)))
170
 
                last_update = time.time()
171
 
                self.flush()
172
 
            # give someone else a chance at the lock
173
 
            time.sleep(1)
174
 
        self.log.info('Revision cache rebuild completed.')
175
 
        self.flush()
176
 
 
177