##// END OF EJS Templates
work around python bug on solaris 10....
Vadim Gelfer -
r2027:94d31703 default
parent child Browse files
Show More
@@ -1,171 +1,172
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 manifest os tempfile")
9 demandload(globals(), "cStringIO changelog manifest os tempfile")
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):
36 def __init__(self, fp):
37 fd, self.tmpname = tempfile.mkstemp()
37 fd, self.tmpname = tempfile.mkstemp()
38 self.tmpfp = os.fdopen(fd, 'ab+')
38 self.tmpfp = os.fdopen(fd, 'ab+')
39 self.realfp = fp
39 self.realfp = fp
40 self.offset = fp.tell()
40 self.offset = fp.tell()
41 # real file is not written by anyone else. cache its size so
41 # real file is not written by anyone else. cache its size so
42 # seek and read can be fast.
42 # seek and read can be fast.
43 self.fpsize = os.fstat(fp.fileno()).st_size
43 self.fpsize = os.fstat(fp.fileno()).st_size
44
44
45 def seek(self, offset):
45 def seek(self, offset):
46 '''virtual file offset spans real file and temp file.'''
46 '''virtual file offset spans real file and temp file.'''
47 self.offset = offset
47 self.offset = offset
48 if self.offset < self.fpsize:
48 if self.offset < self.fpsize:
49 self.realfp.seek(self.offset)
49 self.realfp.seek(self.offset)
50 else:
50 else:
51 self.tmpfp.seek(self.offset - self.fpsize)
51 self.tmpfp.seek(self.offset - self.fpsize)
52
52
53 def read(self, count=-1):
53 def read(self, count=-1):
54 '''only trick here is reads that span real file and temp file.'''
54 '''only trick here is reads that span real file and temp file.'''
55 fp = cStringIO.StringIO()
55 fp = cStringIO.StringIO()
56 old_offset = self.offset
56 old_offset = self.offset
57 if self.offset < self.fpsize:
57 if self.offset < self.fpsize:
58 s = self.realfp.read(count)
58 s = self.realfp.read(count)
59 fp.write(s)
59 fp.write(s)
60 self.offset += len(s)
60 self.offset += len(s)
61 if count > 0:
61 if count > 0:
62 count -= len(s)
62 count -= len(s)
63 if count != 0:
63 if count != 0:
64 if old_offset != self.offset:
64 if old_offset != self.offset:
65 self.tmpfp.seek(self.offset - self.fpsize)
65 self.tmpfp.seek(self.offset - self.fpsize)
66 s = self.tmpfp.read(count)
66 s = self.tmpfp.read(count)
67 fp.write(s)
67 fp.write(s)
68 self.offset += len(s)
68 self.offset += len(s)
69 return fp.getvalue()
69 return fp.getvalue()
70
70
71 def write(self, s):
71 def write(self, s):
72 '''append to temp file.'''
72 '''append to temp file.'''
73 self.tmpfp.seek(0, 2)
73 self.tmpfp.write(s)
74 self.tmpfp.write(s)
74 # all writes are appends, so offset must go to end of file.
75 # all writes are appends, so offset must go to end of file.
75 self.offset = self.fpsize + self.tmpfp.tell()
76 self.offset = self.fpsize + self.tmpfp.tell()
76
77
77 def writedata(self):
78 def writedata(self):
78 '''copy data from temp file to real file.'''
79 '''copy data from temp file to real file.'''
79 self.tmpfp.seek(0)
80 self.tmpfp.seek(0)
80 s = self.tmpfp.read()
81 s = self.tmpfp.read()
81 self.tmpfp.close()
82 self.tmpfp.close()
82 self.realfp.seek(0, 2)
83 self.realfp.seek(0, 2)
83 # small race here. we write all new data in one call, but
84 # small race here. we write all new data in one call, but
84 # reader can see partial update due to python or os. file
85 # reader can see partial update due to python or os. file
85 # locking no help: slow, not portable, not reliable over nfs.
86 # locking no help: slow, not portable, not reliable over nfs.
86 # only safe thing is write to temp file every time and rename,
87 # only safe thing is write to temp file every time and rename,
87 # but performance bad when manifest or changelog gets big.
88 # but performance bad when manifest or changelog gets big.
88 self.realfp.write(s)
89 self.realfp.write(s)
89 self.realfp.close()
90 self.realfp.close()
90
91
91 def __del__(self):
92 def __del__(self):
92 '''delete temp file even if exception raised.'''
93 '''delete temp file even if exception raised.'''
93 try: os.unlink(self.tmpname)
94 try: os.unlink(self.tmpname)
94 except: pass
95 except: pass
95
96
96 class sharedfile(object):
97 class sharedfile(object):
97 '''let file objects share a single appendfile safely. each
98 '''let file objects share a single appendfile safely. each
98 sharedfile has own offset, syncs up with appendfile offset before
99 sharedfile has own offset, syncs up with appendfile offset before
99 read and after read and write.'''
100 read and after read and write.'''
100
101
101 def __init__(self, fp):
102 def __init__(self, fp):
102 self.fp = fp
103 self.fp = fp
103 self.offset = 0
104 self.offset = 0
104
105
105 def seek(self, offset):
106 def seek(self, offset):
106 self.offset = offset
107 self.offset = offset
107
108
108 def read(self, count=-1):
109 def read(self, count=-1):
109 try:
110 try:
110 if self.offset != self.fp.offset:
111 if self.offset != self.fp.offset:
111 self.fp.seek(self.offset)
112 self.fp.seek(self.offset)
112 return self.fp.read(count)
113 return self.fp.read(count)
113 finally:
114 finally:
114 self.offset = self.fp.offset
115 self.offset = self.fp.offset
115
116
116 def write(self, s):
117 def write(self, s):
117 try:
118 try:
118 return self.fp.write(s)
119 return self.fp.write(s)
119 finally:
120 finally:
120 self.offset = self.fp.offset
121 self.offset = self.fp.offset
121
122
122 def close(self):
123 def close(self):
123 # revlog wants this.
124 # revlog wants this.
124 pass
125 pass
125
126
126 def flush(self):
127 def flush(self):
127 # revlog wants this.
128 # revlog wants this.
128 pass
129 pass
129
130
130 def writedata(self):
131 def writedata(self):
131 self.fp.writedata()
132 self.fp.writedata()
132
133
133 class appendopener(object):
134 class appendopener(object):
134 '''special opener for files that only read or append.'''
135 '''special opener for files that only read or append.'''
135
136
136 def __init__(self, opener):
137 def __init__(self, opener):
137 self.realopener = opener
138 self.realopener = opener
138 # key: file name, value: appendfile object
139 # key: file name, value: appendfile object
139 self.fps = {}
140 self.fps = {}
140
141
141 def __call__(self, name, mode='r'):
142 def __call__(self, name, mode='r'):
142 '''open file. return same cached appendfile object for every
143 '''open file. return same cached appendfile object for every
143 later call.'''
144 later call.'''
144
145
145 assert mode in 'ra'
146 assert mode in 'ra'
146 fp = self.fps.get(name)
147 fp = self.fps.get(name)
147 if fp is None:
148 if fp is None:
148 fp = appendfile(self.realopener(name, 'a+'))
149 fp = appendfile(self.realopener(name, 'a+'))
149 self.fps[name] = fp
150 self.fps[name] = fp
150 return sharedfile(fp)
151 return sharedfile(fp)
151
152
152 def writedata(self):
153 def writedata(self):
153 '''copy data from temp files to real files.'''
154 '''copy data from temp files to real files.'''
154 # write .d file before .i file.
155 # write .d file before .i file.
155 fps = self.fps.items()
156 fps = self.fps.items()
156 fps.sort()
157 fps.sort()
157 for name, fp in fps:
158 for name, fp in fps:
158 fp.writedata()
159 fp.writedata()
159
160
160 # files for changelog and manifest are in different appendopeners, so
161 # files for changelog and manifest are in different appendopeners, so
161 # not mixed up together.
162 # not mixed up together.
162
163
163 class appendchangelog(changelog.changelog, appendopener):
164 class appendchangelog(changelog.changelog, appendopener):
164 def __init__(self, opener):
165 def __init__(self, opener):
165 appendopener.__init__(self, opener)
166 appendopener.__init__(self, opener)
166 changelog.changelog.__init__(self, self)
167 changelog.changelog.__init__(self, self)
167
168
168 class appendmanifest(manifest.manifest, appendopener):
169 class appendmanifest(manifest.manifest, appendopener):
169 def __init__(self, opener):
170 def __init__(self, opener):
170 appendopener.__init__(self, opener)
171 appendopener.__init__(self, opener)
171 manifest.manifest.__init__(self, self)
172 manifest.manifest.__init__(self, self)
General Comments 0
You need to be logged in to leave comments. Login now