##// END OF EJS Templates
reduce memory used when appendfile appends to real file.
Vadim Gelfer -
r2236:d7f86678 default
parent child Browse files
Show More
@@ -1,162 +1,162 b''
1 # appendfile.py - special classes to make repo updates atomic
1 # appendfile.py - special classes to make repo updates atomic
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from demandload import *
8 from demandload import *
9 demandload(globals(), "cStringIO changelog errno manifest os tempfile util")
9 demandload(globals(), "cStringIO changelog errno manifest os tempfile util")
10
10
11 # writes to metadata files are ordered. reads: changelog, manifest,
11 # writes to metadata files are ordered. reads: changelog, manifest,
12 # normal files. writes: normal files, manifest, changelog.
12 # normal files. writes: normal files, manifest, changelog.
13
13
14 # manifest contains pointers to offsets in normal files. changelog
14 # manifest contains pointers to offsets in normal files. changelog
15 # contains pointers to offsets in manifest. if reader reads old
15 # contains pointers to offsets in manifest. if reader reads old
16 # changelog while manifest or normal files are written, it has no
16 # changelog while manifest or normal files are written, it has no
17 # pointers into new parts of those files that are maybe not consistent
17 # pointers into new parts of those files that are maybe not consistent
18 # yet, so will not read them.
18 # yet, so will not read them.
19
19
20 # localrepo.addchangegroup thinks it writes changelog first, then
20 # localrepo.addchangegroup thinks it writes changelog first, then
21 # manifest, then normal files (this is order they are available, and
21 # manifest, then normal files (this is order they are available, and
22 # needed for computing linkrev fields), but uses appendfile to hide
22 # needed for computing linkrev fields), but uses appendfile to hide
23 # updates from readers. data not written to manifest or changelog
23 # updates from readers. data not written to manifest or changelog
24 # until all normal files updated. write manifest first, then
24 # until all normal files updated. write manifest first, then
25 # changelog.
25 # changelog.
26
26
27 # with this write ordering, readers cannot see inconsistent view of
27 # with this write ordering, readers cannot see inconsistent view of
28 # repo during update.
28 # repo during update.
29
29
30 class appendfile(object):
30 class appendfile(object):
31 '''implement enough of file protocol to append to revlog file.
31 '''implement enough of file protocol to append to revlog file.
32 appended data is written to temp file. reads and seeks span real
32 appended data is written to temp file. reads and seeks span real
33 file and temp file. readers cannot see appended data until
33 file and temp file. readers cannot see appended data until
34 writedata called.'''
34 writedata called.'''
35
35
36 def __init__(self, fp, tmpname):
36 def __init__(self, fp, tmpname):
37 if tmpname:
37 if tmpname:
38 self.tmpname = tmpname
38 self.tmpname = tmpname
39 self.tmpfp = util.posixfile(self.tmpname, 'ab+')
39 self.tmpfp = util.posixfile(self.tmpname, 'ab+')
40 else:
40 else:
41 fd, self.tmpname = tempfile.mkstemp(prefix="hg-appendfile-")
41 fd, self.tmpname = tempfile.mkstemp(prefix="hg-appendfile-")
42 os.close(fd)
42 os.close(fd)
43 self.tmpfp = util.posixfile(self.tmpname, 'ab+')
43 self.tmpfp = util.posixfile(self.tmpname, 'ab+')
44 self.realfp = fp
44 self.realfp = fp
45 self.offset = fp.tell()
45 self.offset = fp.tell()
46 # real file is not written by anyone else. cache its size so
46 # real file is not written by anyone else. cache its size so
47 # seek and read can be fast.
47 # seek and read can be fast.
48 self.realsize = util.fstat(fp).st_size
48 self.realsize = util.fstat(fp).st_size
49 self.name = fp.name
49 self.name = fp.name
50
50
51 def end(self):
51 def end(self):
52 self.tmpfp.flush() # make sure the stat is correct
52 self.tmpfp.flush() # make sure the stat is correct
53 return self.realsize + util.fstat(self.tmpfp).st_size
53 return self.realsize + util.fstat(self.tmpfp).st_size
54
54
55 def tell(self):
55 def tell(self):
56 return self.offset
56 return self.offset
57
57
58 def flush(self):
58 def flush(self):
59 self.tmpfp.flush()
59 self.tmpfp.flush()
60
60
61 def close(self):
61 def close(self):
62 self.realfp.close()
62 self.realfp.close()
63 self.tmpfp.close()
63 self.tmpfp.close()
64
64
65 def seek(self, offset, whence=0):
65 def seek(self, offset, whence=0):
66 '''virtual file offset spans real file and temp file.'''
66 '''virtual file offset spans real file and temp file.'''
67 if whence == 0:
67 if whence == 0:
68 self.offset = offset
68 self.offset = offset
69 elif whence == 1:
69 elif whence == 1:
70 self.offset += offset
70 self.offset += offset
71 elif whence == 2:
71 elif whence == 2:
72 self.offset = self.end() + offset
72 self.offset = self.end() + offset
73
73
74 if self.offset < self.realsize:
74 if self.offset < self.realsize:
75 self.realfp.seek(self.offset)
75 self.realfp.seek(self.offset)
76 else:
76 else:
77 self.tmpfp.seek(self.offset - self.realsize)
77 self.tmpfp.seek(self.offset - self.realsize)
78
78
79 def read(self, count=-1):
79 def read(self, count=-1):
80 '''only trick here is reads that span real file and temp file.'''
80 '''only trick here is reads that span real file and temp file.'''
81 fp = cStringIO.StringIO()
81 fp = cStringIO.StringIO()
82 old_offset = self.offset
82 old_offset = self.offset
83 if self.offset < self.realsize:
83 if self.offset < self.realsize:
84 s = self.realfp.read(count)
84 s = self.realfp.read(count)
85 fp.write(s)
85 fp.write(s)
86 self.offset += len(s)
86 self.offset += len(s)
87 if count > 0:
87 if count > 0:
88 count -= len(s)
88 count -= len(s)
89 if count != 0:
89 if count != 0:
90 if old_offset != self.offset:
90 if old_offset != self.offset:
91 self.tmpfp.seek(self.offset - self.realsize)
91 self.tmpfp.seek(self.offset - self.realsize)
92 s = self.tmpfp.read(count)
92 s = self.tmpfp.read(count)
93 fp.write(s)
93 fp.write(s)
94 self.offset += len(s)
94 self.offset += len(s)
95 return fp.getvalue()
95 return fp.getvalue()
96
96
97 def write(self, s):
97 def write(self, s):
98 '''append to temp file.'''
98 '''append to temp file.'''
99 self.tmpfp.seek(0, 2)
99 self.tmpfp.seek(0, 2)
100 self.tmpfp.write(s)
100 self.tmpfp.write(s)
101 # all writes are appends, so offset must go to end of file.
101 # all writes are appends, so offset must go to end of file.
102 self.offset = self.realsize + self.tmpfp.tell()
102 self.offset = self.realsize + self.tmpfp.tell()
103
103
104 class appendopener(object):
104 class appendopener(object):
105 '''special opener for files that only read or append.'''
105 '''special opener for files that only read or append.'''
106
106
107 def __init__(self, opener):
107 def __init__(self, opener):
108 self.realopener = opener
108 self.realopener = opener
109 # key: file name, value: appendfile name
109 # key: file name, value: appendfile name
110 self.tmpnames = {}
110 self.tmpnames = {}
111
111
112 def __call__(self, name, mode='r'):
112 def __call__(self, name, mode='r'):
113 '''open file.'''
113 '''open file.'''
114
114
115 assert mode in 'ra+'
115 assert mode in 'ra+'
116 try:
116 try:
117 realfp = self.realopener(name, 'r')
117 realfp = self.realopener(name, 'r')
118 except IOError, err:
118 except IOError, err:
119 if err.errno != errno.ENOENT: raise
119 if err.errno != errno.ENOENT: raise
120 realfp = self.realopener(name, 'w+')
120 realfp = self.realopener(name, 'w+')
121 tmpname = self.tmpnames.get(name)
121 tmpname = self.tmpnames.get(name)
122 fp = appendfile(realfp, tmpname)
122 fp = appendfile(realfp, tmpname)
123 if tmpname is None:
123 if tmpname is None:
124 self.tmpnames[name] = fp.tmpname
124 self.tmpnames[name] = fp.tmpname
125 return fp
125 return fp
126
126
127 def writedata(self):
127 def writedata(self):
128 '''copy data from temp files to real files.'''
128 '''copy data from temp files to real files.'''
129 # write .d file before .i file.
129 # write .d file before .i file.
130 tmpnames = self.tmpnames.items()
130 tmpnames = self.tmpnames.items()
131 tmpnames.sort()
131 tmpnames.sort()
132 for name, tmpname in tmpnames:
132 for name, tmpname in tmpnames:
133 fp = open(tmpname, 'rb')
133 ifp = open(tmpname, 'rb')
134 s = fp.read()
134 ofp = self.realopener(name, 'a')
135 fp.close()
135 for chunk in util.filechunkiter(ifp):
136 ofp.write(chunk)
137 ifp.close()
136 os.unlink(tmpname)
138 os.unlink(tmpname)
137 del self.tmpnames[name]
139 del self.tmpnames[name]
138 fp = self.realopener(name, 'a')
140 ofp.close()
139 fp.write(s)
140 fp.close()
141
141
142 def cleanup(self):
142 def cleanup(self):
143 '''delete temp files (this discards unwritten data!)'''
143 '''delete temp files (this discards unwritten data!)'''
144 for tmpname in self.tmpnames.values():
144 for tmpname in self.tmpnames.values():
145 os.unlink(tmpname)
145 os.unlink(tmpname)
146
146
147 # files for changelog and manifest are in different appendopeners, so
147 # files for changelog and manifest are in different appendopeners, so
148 # not mixed up together.
148 # not mixed up together.
149
149
150 class appendchangelog(changelog.changelog, appendopener):
150 class appendchangelog(changelog.changelog, appendopener):
151 def __init__(self, opener, version):
151 def __init__(self, opener, version):
152 appendopener.__init__(self, opener)
152 appendopener.__init__(self, opener)
153 changelog.changelog.__init__(self, self, version)
153 changelog.changelog.__init__(self, self, version)
154 def checkinlinesize(self, fp, tr):
154 def checkinlinesize(self, fp, tr):
155 return
155 return
156
156
157 class appendmanifest(manifest.manifest, appendopener):
157 class appendmanifest(manifest.manifest, appendopener):
158 def __init__(self, opener, version):
158 def __init__(self, opener, version):
159 appendopener.__init__(self, opener)
159 appendopener.__init__(self, opener)
160 manifest.manifest.__init__(self, self, version)
160 manifest.manifest.__init__(self, self, version)
161 def checkinlinesize(self, fp, tr):
161 def checkinlinesize(self, fp, tr):
162 return
162 return
General Comments 0
You need to be logged in to leave comments. Login now