##// END OF EJS Templates
workers: don't use backgroundfilecloser in threads...
Wojciech Lis -
r35426:60f2a215 default
parent child Browse files
Show More
@@ -1,649 +1,654
1 # vfs.py - Mercurial 'vfs' classes
1 # vfs.py - Mercurial 'vfs' classes
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import contextlib
9 import contextlib
10 import errno
10 import errno
11 import os
11 import os
12 import shutil
12 import shutil
13 import stat
13 import stat
14 import tempfile
14 import tempfile
15 import threading
15 import threading
16
16
17 from .i18n import _
17 from .i18n import _
18 from . import (
18 from . import (
19 encoding,
19 encoding,
20 error,
20 error,
21 pathutil,
21 pathutil,
22 pycompat,
22 pycompat,
23 util,
23 util,
24 )
24 )
25
25
26 def _avoidambig(path, oldstat):
26 def _avoidambig(path, oldstat):
27 """Avoid file stat ambiguity forcibly
27 """Avoid file stat ambiguity forcibly
28
28
29 This function causes copying ``path`` file, if it is owned by
29 This function causes copying ``path`` file, if it is owned by
30 another (see issue5418 and issue5584 for detail).
30 another (see issue5418 and issue5584 for detail).
31 """
31 """
32 def checkandavoid():
32 def checkandavoid():
33 newstat = util.filestat.frompath(path)
33 newstat = util.filestat.frompath(path)
34 # return whether file stat ambiguity is (already) avoided
34 # return whether file stat ambiguity is (already) avoided
35 return (not newstat.isambig(oldstat) or
35 return (not newstat.isambig(oldstat) or
36 newstat.avoidambig(path, oldstat))
36 newstat.avoidambig(path, oldstat))
37 if not checkandavoid():
37 if not checkandavoid():
38 # simply copy to change owner of path to get privilege to
38 # simply copy to change owner of path to get privilege to
39 # advance mtime (see issue5418)
39 # advance mtime (see issue5418)
40 util.rename(util.mktempcopy(path), path)
40 util.rename(util.mktempcopy(path), path)
41 checkandavoid()
41 checkandavoid()
42
42
43 class abstractvfs(object):
43 class abstractvfs(object):
44 """Abstract base class; cannot be instantiated"""
44 """Abstract base class; cannot be instantiated"""
45
45
46 def __init__(self, *args, **kwargs):
46 def __init__(self, *args, **kwargs):
47 '''Prevent instantiation; don't call this from subclasses.'''
47 '''Prevent instantiation; don't call this from subclasses.'''
48 raise NotImplementedError('attempted instantiating ' + str(type(self)))
48 raise NotImplementedError('attempted instantiating ' + str(type(self)))
49
49
50 def tryread(self, path):
50 def tryread(self, path):
51 '''gracefully return an empty string for missing files'''
51 '''gracefully return an empty string for missing files'''
52 try:
52 try:
53 return self.read(path)
53 return self.read(path)
54 except IOError as inst:
54 except IOError as inst:
55 if inst.errno != errno.ENOENT:
55 if inst.errno != errno.ENOENT:
56 raise
56 raise
57 return ""
57 return ""
58
58
59 def tryreadlines(self, path, mode='rb'):
59 def tryreadlines(self, path, mode='rb'):
60 '''gracefully return an empty array for missing files'''
60 '''gracefully return an empty array for missing files'''
61 try:
61 try:
62 return self.readlines(path, mode=mode)
62 return self.readlines(path, mode=mode)
63 except IOError as inst:
63 except IOError as inst:
64 if inst.errno != errno.ENOENT:
64 if inst.errno != errno.ENOENT:
65 raise
65 raise
66 return []
66 return []
67
67
68 @util.propertycache
68 @util.propertycache
69 def open(self):
69 def open(self):
70 '''Open ``path`` file, which is relative to vfs root.
70 '''Open ``path`` file, which is relative to vfs root.
71
71
72 Newly created directories are marked as "not to be indexed by
72 Newly created directories are marked as "not to be indexed by
73 the content indexing service", if ``notindexed`` is specified
73 the content indexing service", if ``notindexed`` is specified
74 for "write" mode access.
74 for "write" mode access.
75 '''
75 '''
76 return self.__call__
76 return self.__call__
77
77
78 def read(self, path):
78 def read(self, path):
79 with self(path, 'rb') as fp:
79 with self(path, 'rb') as fp:
80 return fp.read()
80 return fp.read()
81
81
82 def readlines(self, path, mode='rb'):
82 def readlines(self, path, mode='rb'):
83 with self(path, mode=mode) as fp:
83 with self(path, mode=mode) as fp:
84 return fp.readlines()
84 return fp.readlines()
85
85
86 def write(self, path, data, backgroundclose=False):
86 def write(self, path, data, backgroundclose=False):
87 with self(path, 'wb', backgroundclose=backgroundclose) as fp:
87 with self(path, 'wb', backgroundclose=backgroundclose) as fp:
88 return fp.write(data)
88 return fp.write(data)
89
89
90 def writelines(self, path, data, mode='wb', notindexed=False):
90 def writelines(self, path, data, mode='wb', notindexed=False):
91 with self(path, mode=mode, notindexed=notindexed) as fp:
91 with self(path, mode=mode, notindexed=notindexed) as fp:
92 return fp.writelines(data)
92 return fp.writelines(data)
93
93
94 def append(self, path, data):
94 def append(self, path, data):
95 with self(path, 'ab') as fp:
95 with self(path, 'ab') as fp:
96 return fp.write(data)
96 return fp.write(data)
97
97
98 def basename(self, path):
98 def basename(self, path):
99 """return base element of a path (as os.path.basename would do)
99 """return base element of a path (as os.path.basename would do)
100
100
101 This exists to allow handling of strange encoding if needed."""
101 This exists to allow handling of strange encoding if needed."""
102 return os.path.basename(path)
102 return os.path.basename(path)
103
103
104 def chmod(self, path, mode):
104 def chmod(self, path, mode):
105 return os.chmod(self.join(path), mode)
105 return os.chmod(self.join(path), mode)
106
106
107 def dirname(self, path):
107 def dirname(self, path):
108 """return dirname element of a path (as os.path.dirname would do)
108 """return dirname element of a path (as os.path.dirname would do)
109
109
110 This exists to allow handling of strange encoding if needed."""
110 This exists to allow handling of strange encoding if needed."""
111 return os.path.dirname(path)
111 return os.path.dirname(path)
112
112
113 def exists(self, path=None):
113 def exists(self, path=None):
114 return os.path.exists(self.join(path))
114 return os.path.exists(self.join(path))
115
115
116 def fstat(self, fp):
116 def fstat(self, fp):
117 return util.fstat(fp)
117 return util.fstat(fp)
118
118
119 def isdir(self, path=None):
119 def isdir(self, path=None):
120 return os.path.isdir(self.join(path))
120 return os.path.isdir(self.join(path))
121
121
122 def isfile(self, path=None):
122 def isfile(self, path=None):
123 return os.path.isfile(self.join(path))
123 return os.path.isfile(self.join(path))
124
124
125 def islink(self, path=None):
125 def islink(self, path=None):
126 return os.path.islink(self.join(path))
126 return os.path.islink(self.join(path))
127
127
128 def isfileorlink(self, path=None):
128 def isfileorlink(self, path=None):
129 '''return whether path is a regular file or a symlink
129 '''return whether path is a regular file or a symlink
130
130
131 Unlike isfile, this doesn't follow symlinks.'''
131 Unlike isfile, this doesn't follow symlinks.'''
132 try:
132 try:
133 st = self.lstat(path)
133 st = self.lstat(path)
134 except OSError:
134 except OSError:
135 return False
135 return False
136 mode = st.st_mode
136 mode = st.st_mode
137 return stat.S_ISREG(mode) or stat.S_ISLNK(mode)
137 return stat.S_ISREG(mode) or stat.S_ISLNK(mode)
138
138
139 def reljoin(self, *paths):
139 def reljoin(self, *paths):
140 """join various elements of a path together (as os.path.join would do)
140 """join various elements of a path together (as os.path.join would do)
141
141
142 The vfs base is not injected so that path stay relative. This exists
142 The vfs base is not injected so that path stay relative. This exists
143 to allow handling of strange encoding if needed."""
143 to allow handling of strange encoding if needed."""
144 return os.path.join(*paths)
144 return os.path.join(*paths)
145
145
146 def split(self, path):
146 def split(self, path):
147 """split top-most element of a path (as os.path.split would do)
147 """split top-most element of a path (as os.path.split would do)
148
148
149 This exists to allow handling of strange encoding if needed."""
149 This exists to allow handling of strange encoding if needed."""
150 return os.path.split(path)
150 return os.path.split(path)
151
151
152 def lexists(self, path=None):
152 def lexists(self, path=None):
153 return os.path.lexists(self.join(path))
153 return os.path.lexists(self.join(path))
154
154
155 def lstat(self, path=None):
155 def lstat(self, path=None):
156 return os.lstat(self.join(path))
156 return os.lstat(self.join(path))
157
157
158 def listdir(self, path=None):
158 def listdir(self, path=None):
159 return os.listdir(self.join(path))
159 return os.listdir(self.join(path))
160
160
161 def makedir(self, path=None, notindexed=True):
161 def makedir(self, path=None, notindexed=True):
162 return util.makedir(self.join(path), notindexed)
162 return util.makedir(self.join(path), notindexed)
163
163
164 def makedirs(self, path=None, mode=None):
164 def makedirs(self, path=None, mode=None):
165 return util.makedirs(self.join(path), mode)
165 return util.makedirs(self.join(path), mode)
166
166
167 def makelock(self, info, path):
167 def makelock(self, info, path):
168 return util.makelock(info, self.join(path))
168 return util.makelock(info, self.join(path))
169
169
170 def mkdir(self, path=None):
170 def mkdir(self, path=None):
171 return os.mkdir(self.join(path))
171 return os.mkdir(self.join(path))
172
172
173 def mkstemp(self, suffix='', prefix='tmp', dir=None, text=False):
173 def mkstemp(self, suffix='', prefix='tmp', dir=None, text=False):
174 fd, name = tempfile.mkstemp(suffix=suffix, prefix=prefix,
174 fd, name = tempfile.mkstemp(suffix=suffix, prefix=prefix,
175 dir=self.join(dir), text=text)
175 dir=self.join(dir), text=text)
176 dname, fname = util.split(name)
176 dname, fname = util.split(name)
177 if dir:
177 if dir:
178 return fd, os.path.join(dir, fname)
178 return fd, os.path.join(dir, fname)
179 else:
179 else:
180 return fd, fname
180 return fd, fname
181
181
182 def readdir(self, path=None, stat=None, skip=None):
182 def readdir(self, path=None, stat=None, skip=None):
183 return util.listdir(self.join(path), stat, skip)
183 return util.listdir(self.join(path), stat, skip)
184
184
185 def readlock(self, path):
185 def readlock(self, path):
186 return util.readlock(self.join(path))
186 return util.readlock(self.join(path))
187
187
188 def rename(self, src, dst, checkambig=False):
188 def rename(self, src, dst, checkambig=False):
189 """Rename from src to dst
189 """Rename from src to dst
190
190
191 checkambig argument is used with util.filestat, and is useful
191 checkambig argument is used with util.filestat, and is useful
192 only if destination file is guarded by any lock
192 only if destination file is guarded by any lock
193 (e.g. repo.lock or repo.wlock).
193 (e.g. repo.lock or repo.wlock).
194
194
195 To avoid file stat ambiguity forcibly, checkambig=True involves
195 To avoid file stat ambiguity forcibly, checkambig=True involves
196 copying ``src`` file, if it is owned by another. Therefore, use
196 copying ``src`` file, if it is owned by another. Therefore, use
197 checkambig=True only in limited cases (see also issue5418 and
197 checkambig=True only in limited cases (see also issue5418 and
198 issue5584 for detail).
198 issue5584 for detail).
199 """
199 """
200 srcpath = self.join(src)
200 srcpath = self.join(src)
201 dstpath = self.join(dst)
201 dstpath = self.join(dst)
202 oldstat = checkambig and util.filestat.frompath(dstpath)
202 oldstat = checkambig and util.filestat.frompath(dstpath)
203 if oldstat and oldstat.stat:
203 if oldstat and oldstat.stat:
204 ret = util.rename(srcpath, dstpath)
204 ret = util.rename(srcpath, dstpath)
205 _avoidambig(dstpath, oldstat)
205 _avoidambig(dstpath, oldstat)
206 return ret
206 return ret
207 return util.rename(srcpath, dstpath)
207 return util.rename(srcpath, dstpath)
208
208
209 def readlink(self, path):
209 def readlink(self, path):
210 return os.readlink(self.join(path))
210 return os.readlink(self.join(path))
211
211
212 def removedirs(self, path=None):
212 def removedirs(self, path=None):
213 """Remove a leaf directory and all empty intermediate ones
213 """Remove a leaf directory and all empty intermediate ones
214 """
214 """
215 return util.removedirs(self.join(path))
215 return util.removedirs(self.join(path))
216
216
217 def rmtree(self, path=None, ignore_errors=False, forcibly=False):
217 def rmtree(self, path=None, ignore_errors=False, forcibly=False):
218 """Remove a directory tree recursively
218 """Remove a directory tree recursively
219
219
220 If ``forcibly``, this tries to remove READ-ONLY files, too.
220 If ``forcibly``, this tries to remove READ-ONLY files, too.
221 """
221 """
222 if forcibly:
222 if forcibly:
223 def onerror(function, path, excinfo):
223 def onerror(function, path, excinfo):
224 if function is not os.remove:
224 if function is not os.remove:
225 raise
225 raise
226 # read-only files cannot be unlinked under Windows
226 # read-only files cannot be unlinked under Windows
227 s = os.stat(path)
227 s = os.stat(path)
228 if (s.st_mode & stat.S_IWRITE) != 0:
228 if (s.st_mode & stat.S_IWRITE) != 0:
229 raise
229 raise
230 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
230 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
231 os.remove(path)
231 os.remove(path)
232 else:
232 else:
233 onerror = None
233 onerror = None
234 return shutil.rmtree(self.join(path),
234 return shutil.rmtree(self.join(path),
235 ignore_errors=ignore_errors, onerror=onerror)
235 ignore_errors=ignore_errors, onerror=onerror)
236
236
237 def setflags(self, path, l, x):
237 def setflags(self, path, l, x):
238 return util.setflags(self.join(path), l, x)
238 return util.setflags(self.join(path), l, x)
239
239
240 def stat(self, path=None):
240 def stat(self, path=None):
241 return os.stat(self.join(path))
241 return os.stat(self.join(path))
242
242
243 def unlink(self, path=None):
243 def unlink(self, path=None):
244 return util.unlink(self.join(path))
244 return util.unlink(self.join(path))
245
245
246 def tryunlink(self, path=None):
246 def tryunlink(self, path=None):
247 """Attempt to remove a file, ignoring missing file errors."""
247 """Attempt to remove a file, ignoring missing file errors."""
248 util.tryunlink(self.join(path))
248 util.tryunlink(self.join(path))
249
249
250 def unlinkpath(self, path=None, ignoremissing=False):
250 def unlinkpath(self, path=None, ignoremissing=False):
251 return util.unlinkpath(self.join(path), ignoremissing=ignoremissing)
251 return util.unlinkpath(self.join(path), ignoremissing=ignoremissing)
252
252
253 def utime(self, path=None, t=None):
253 def utime(self, path=None, t=None):
254 return os.utime(self.join(path), t)
254 return os.utime(self.join(path), t)
255
255
256 def walk(self, path=None, onerror=None):
256 def walk(self, path=None, onerror=None):
257 """Yield (dirpath, dirs, files) tuple for each directories under path
257 """Yield (dirpath, dirs, files) tuple for each directories under path
258
258
259 ``dirpath`` is relative one from the root of this vfs. This
259 ``dirpath`` is relative one from the root of this vfs. This
260 uses ``os.sep`` as path separator, even you specify POSIX
260 uses ``os.sep`` as path separator, even you specify POSIX
261 style ``path``.
261 style ``path``.
262
262
263 "The root of this vfs" is represented as empty ``dirpath``.
263 "The root of this vfs" is represented as empty ``dirpath``.
264 """
264 """
265 root = os.path.normpath(self.join(None))
265 root = os.path.normpath(self.join(None))
266 # when dirpath == root, dirpath[prefixlen:] becomes empty
266 # when dirpath == root, dirpath[prefixlen:] becomes empty
267 # because len(dirpath) < prefixlen.
267 # because len(dirpath) < prefixlen.
268 prefixlen = len(pathutil.normasprefix(root))
268 prefixlen = len(pathutil.normasprefix(root))
269 for dirpath, dirs, files in os.walk(self.join(path), onerror=onerror):
269 for dirpath, dirs, files in os.walk(self.join(path), onerror=onerror):
270 yield (dirpath[prefixlen:], dirs, files)
270 yield (dirpath[prefixlen:], dirs, files)
271
271
272 @contextlib.contextmanager
272 @contextlib.contextmanager
273 def backgroundclosing(self, ui, expectedcount=-1):
273 def backgroundclosing(self, ui, expectedcount=-1):
274 """Allow files to be closed asynchronously.
274 """Allow files to be closed asynchronously.
275
275
276 When this context manager is active, ``backgroundclose`` can be passed
276 When this context manager is active, ``backgroundclose`` can be passed
277 to ``__call__``/``open`` to result in the file possibly being closed
277 to ``__call__``/``open`` to result in the file possibly being closed
278 asynchronously, on a background thread.
278 asynchronously, on a background thread.
279 """
279 """
280 # This is an arbitrary restriction and could be changed if we ever
280 # Sharing backgroundfilecloser between threads is complex and using
281 # have a use case.
281 # multiple instances puts us at risk of running out of file descriptors
282 # only allow to use backgroundfilecloser when in main thread.
283 if not isinstance(threading.currentThread(), threading._MainThread):
284 yield
285 return
282 vfs = getattr(self, 'vfs', self)
286 vfs = getattr(self, 'vfs', self)
283 if getattr(vfs, '_backgroundfilecloser', None):
287 if getattr(vfs, '_backgroundfilecloser', None):
284 raise error.Abort(
288 raise error.Abort(
285 _('can only have 1 active background file closer'))
289 _('can only have 1 active background file closer'))
286
290
287 with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc:
291 with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc:
288 try:
292 try:
289 vfs._backgroundfilecloser = bfc
293 vfs._backgroundfilecloser = bfc
290 yield bfc
294 yield bfc
291 finally:
295 finally:
292 vfs._backgroundfilecloser = None
296 vfs._backgroundfilecloser = None
293
297
294 class vfs(abstractvfs):
298 class vfs(abstractvfs):
295 '''Operate files relative to a base directory
299 '''Operate files relative to a base directory
296
300
297 This class is used to hide the details of COW semantics and
301 This class is used to hide the details of COW semantics and
298 remote file access from higher level code.
302 remote file access from higher level code.
299
303
300 'cacheaudited' should be enabled only if (a) vfs object is short-lived, or
304 'cacheaudited' should be enabled only if (a) vfs object is short-lived, or
301 (b) the base directory is managed by hg and considered sort-of append-only.
305 (b) the base directory is managed by hg and considered sort-of append-only.
302 See pathutil.pathauditor() for details.
306 See pathutil.pathauditor() for details.
303 '''
307 '''
304 def __init__(self, base, audit=True, cacheaudited=False, expandpath=False,
308 def __init__(self, base, audit=True, cacheaudited=False, expandpath=False,
305 realpath=False):
309 realpath=False):
306 if expandpath:
310 if expandpath:
307 base = util.expandpath(base)
311 base = util.expandpath(base)
308 if realpath:
312 if realpath:
309 base = os.path.realpath(base)
313 base = os.path.realpath(base)
310 self.base = base
314 self.base = base
311 self._audit = audit
315 self._audit = audit
312 if audit:
316 if audit:
313 self.audit = pathutil.pathauditor(self.base, cached=cacheaudited)
317 self.audit = pathutil.pathauditor(self.base, cached=cacheaudited)
314 else:
318 else:
315 self.audit = (lambda path, mode=None: True)
319 self.audit = (lambda path, mode=None: True)
316 self.createmode = None
320 self.createmode = None
317 self._trustnlink = None
321 self._trustnlink = None
318
322
319 @util.propertycache
323 @util.propertycache
320 def _cansymlink(self):
324 def _cansymlink(self):
321 return util.checklink(self.base)
325 return util.checklink(self.base)
322
326
323 @util.propertycache
327 @util.propertycache
324 def _chmod(self):
328 def _chmod(self):
325 return util.checkexec(self.base)
329 return util.checkexec(self.base)
326
330
327 def _fixfilemode(self, name):
331 def _fixfilemode(self, name):
328 if self.createmode is None or not self._chmod:
332 if self.createmode is None or not self._chmod:
329 return
333 return
330 os.chmod(name, self.createmode & 0o666)
334 os.chmod(name, self.createmode & 0o666)
331
335
332 def __call__(self, path, mode="r", text=False, atomictemp=False,
336 def __call__(self, path, mode="r", text=False, atomictemp=False,
333 notindexed=False, backgroundclose=False, checkambig=False,
337 notindexed=False, backgroundclose=False, checkambig=False,
334 auditpath=True):
338 auditpath=True):
335 '''Open ``path`` file, which is relative to vfs root.
339 '''Open ``path`` file, which is relative to vfs root.
336
340
337 Newly created directories are marked as "not to be indexed by
341 Newly created directories are marked as "not to be indexed by
338 the content indexing service", if ``notindexed`` is specified
342 the content indexing service", if ``notindexed`` is specified
339 for "write" mode access.
343 for "write" mode access.
340
344
341 If ``backgroundclose`` is passed, the file may be closed asynchronously.
345 If ``backgroundclose`` is passed, the file may be closed asynchronously.
342 It can only be used if the ``self.backgroundclosing()`` context manager
346 It can only be used if the ``self.backgroundclosing()`` context manager
343 is active. This should only be specified if the following criteria hold:
347 is active. This should only be specified if the following criteria hold:
344
348
345 1. There is a potential for writing thousands of files. Unless you
349 1. There is a potential for writing thousands of files. Unless you
346 are writing thousands of files, the performance benefits of
350 are writing thousands of files, the performance benefits of
347 asynchronously closing files is not realized.
351 asynchronously closing files is not realized.
348 2. Files are opened exactly once for the ``backgroundclosing``
352 2. Files are opened exactly once for the ``backgroundclosing``
349 active duration and are therefore free of race conditions between
353 active duration and are therefore free of race conditions between
350 closing a file on a background thread and reopening it. (If the
354 closing a file on a background thread and reopening it. (If the
351 file were opened multiple times, there could be unflushed data
355 file were opened multiple times, there could be unflushed data
352 because the original file handle hasn't been flushed/closed yet.)
356 because the original file handle hasn't been flushed/closed yet.)
353
357
354 ``checkambig`` argument is passed to atomictemplfile (valid
358 ``checkambig`` argument is passed to atomictemplfile (valid
355 only for writing), and is useful only if target file is
359 only for writing), and is useful only if target file is
356 guarded by any lock (e.g. repo.lock or repo.wlock).
360 guarded by any lock (e.g. repo.lock or repo.wlock).
357
361
358 To avoid file stat ambiguity forcibly, checkambig=True involves
362 To avoid file stat ambiguity forcibly, checkambig=True involves
359 copying ``path`` file opened in "append" mode (e.g. for
363 copying ``path`` file opened in "append" mode (e.g. for
360 truncation), if it is owned by another. Therefore, use
364 truncation), if it is owned by another. Therefore, use
361 combination of append mode and checkambig=True only in limited
365 combination of append mode and checkambig=True only in limited
362 cases (see also issue5418 and issue5584 for detail).
366 cases (see also issue5418 and issue5584 for detail).
363 '''
367 '''
364 if auditpath:
368 if auditpath:
365 if self._audit:
369 if self._audit:
366 r = util.checkosfilename(path)
370 r = util.checkosfilename(path)
367 if r:
371 if r:
368 raise error.Abort("%s: %r" % (r, path))
372 raise error.Abort("%s: %r" % (r, path))
369 self.audit(path, mode=mode)
373 self.audit(path, mode=mode)
370 f = self.join(path)
374 f = self.join(path)
371
375
372 if not text and "b" not in mode:
376 if not text and "b" not in mode:
373 mode += "b" # for that other OS
377 mode += "b" # for that other OS
374
378
375 nlink = -1
379 nlink = -1
376 if mode not in ('r', 'rb'):
380 if mode not in ('r', 'rb'):
377 dirname, basename = util.split(f)
381 dirname, basename = util.split(f)
378 # If basename is empty, then the path is malformed because it points
382 # If basename is empty, then the path is malformed because it points
379 # to a directory. Let the posixfile() call below raise IOError.
383 # to a directory. Let the posixfile() call below raise IOError.
380 if basename:
384 if basename:
381 if atomictemp:
385 if atomictemp:
382 util.makedirs(dirname, self.createmode, notindexed)
386 util.makedirs(dirname, self.createmode, notindexed)
383 return util.atomictempfile(f, mode, self.createmode,
387 return util.atomictempfile(f, mode, self.createmode,
384 checkambig=checkambig)
388 checkambig=checkambig)
385 try:
389 try:
386 if 'w' in mode:
390 if 'w' in mode:
387 util.unlink(f)
391 util.unlink(f)
388 nlink = 0
392 nlink = 0
389 else:
393 else:
390 # nlinks() may behave differently for files on Windows
394 # nlinks() may behave differently for files on Windows
391 # shares if the file is open.
395 # shares if the file is open.
392 with util.posixfile(f):
396 with util.posixfile(f):
393 nlink = util.nlinks(f)
397 nlink = util.nlinks(f)
394 if nlink < 1:
398 if nlink < 1:
395 nlink = 2 # force mktempcopy (issue1922)
399 nlink = 2 # force mktempcopy (issue1922)
396 except (OSError, IOError) as e:
400 except (OSError, IOError) as e:
397 if e.errno != errno.ENOENT:
401 if e.errno != errno.ENOENT:
398 raise
402 raise
399 nlink = 0
403 nlink = 0
400 util.makedirs(dirname, self.createmode, notindexed)
404 util.makedirs(dirname, self.createmode, notindexed)
401 if nlink > 0:
405 if nlink > 0:
402 if self._trustnlink is None:
406 if self._trustnlink is None:
403 self._trustnlink = nlink > 1 or util.checknlink(f)
407 self._trustnlink = nlink > 1 or util.checknlink(f)
404 if nlink > 1 or not self._trustnlink:
408 if nlink > 1 or not self._trustnlink:
405 util.rename(util.mktempcopy(f), f)
409 util.rename(util.mktempcopy(f), f)
406 fp = util.posixfile(f, mode)
410 fp = util.posixfile(f, mode)
407 if nlink == 0:
411 if nlink == 0:
408 self._fixfilemode(f)
412 self._fixfilemode(f)
409
413
410 if checkambig:
414 if checkambig:
411 if mode in ('r', 'rb'):
415 if mode in ('r', 'rb'):
412 raise error.Abort(_('implementation error: mode %s is not'
416 raise error.Abort(_('implementation error: mode %s is not'
413 ' valid for checkambig=True') % mode)
417 ' valid for checkambig=True') % mode)
414 fp = checkambigatclosing(fp)
418 fp = checkambigatclosing(fp)
415
419
416 if backgroundclose:
420 if (backgroundclose and
421 isinstance(threading.currentThread(), threading._MainThread)):
417 if not self._backgroundfilecloser:
422 if not self._backgroundfilecloser:
418 raise error.Abort(_('backgroundclose can only be used when a '
423 raise error.Abort(_('backgroundclose can only be used when a '
419 'backgroundclosing context manager is active')
424 'backgroundclosing context manager is active')
420 )
425 )
421
426
422 fp = delayclosedfile(fp, self._backgroundfilecloser)
427 fp = delayclosedfile(fp, self._backgroundfilecloser)
423
428
424 return fp
429 return fp
425
430
426 def symlink(self, src, dst):
431 def symlink(self, src, dst):
427 self.audit(dst)
432 self.audit(dst)
428 linkname = self.join(dst)
433 linkname = self.join(dst)
429 util.tryunlink(linkname)
434 util.tryunlink(linkname)
430
435
431 util.makedirs(os.path.dirname(linkname), self.createmode)
436 util.makedirs(os.path.dirname(linkname), self.createmode)
432
437
433 if self._cansymlink:
438 if self._cansymlink:
434 try:
439 try:
435 os.symlink(src, linkname)
440 os.symlink(src, linkname)
436 except OSError as err:
441 except OSError as err:
437 raise OSError(err.errno, _('could not symlink to %r: %s') %
442 raise OSError(err.errno, _('could not symlink to %r: %s') %
438 (src, encoding.strtolocal(err.strerror)),
443 (src, encoding.strtolocal(err.strerror)),
439 linkname)
444 linkname)
440 else:
445 else:
441 self.write(dst, src)
446 self.write(dst, src)
442
447
443 def join(self, path, *insidef):
448 def join(self, path, *insidef):
444 if path:
449 if path:
445 return os.path.join(self.base, path, *insidef)
450 return os.path.join(self.base, path, *insidef)
446 else:
451 else:
447 return self.base
452 return self.base
448
453
449 opener = vfs
454 opener = vfs
450
455
451 class proxyvfs(object):
456 class proxyvfs(object):
452 def __init__(self, vfs):
457 def __init__(self, vfs):
453 self.vfs = vfs
458 self.vfs = vfs
454
459
455 @property
460 @property
456 def options(self):
461 def options(self):
457 return self.vfs.options
462 return self.vfs.options
458
463
459 @options.setter
464 @options.setter
460 def options(self, value):
465 def options(self, value):
461 self.vfs.options = value
466 self.vfs.options = value
462
467
463 class filtervfs(abstractvfs, proxyvfs):
468 class filtervfs(abstractvfs, proxyvfs):
464 '''Wrapper vfs for filtering filenames with a function.'''
469 '''Wrapper vfs for filtering filenames with a function.'''
465
470
466 def __init__(self, vfs, filter):
471 def __init__(self, vfs, filter):
467 proxyvfs.__init__(self, vfs)
472 proxyvfs.__init__(self, vfs)
468 self._filter = filter
473 self._filter = filter
469
474
470 def __call__(self, path, *args, **kwargs):
475 def __call__(self, path, *args, **kwargs):
471 return self.vfs(self._filter(path), *args, **kwargs)
476 return self.vfs(self._filter(path), *args, **kwargs)
472
477
473 def join(self, path, *insidef):
478 def join(self, path, *insidef):
474 if path:
479 if path:
475 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
480 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
476 else:
481 else:
477 return self.vfs.join(path)
482 return self.vfs.join(path)
478
483
479 filteropener = filtervfs
484 filteropener = filtervfs
480
485
481 class readonlyvfs(abstractvfs, proxyvfs):
486 class readonlyvfs(abstractvfs, proxyvfs):
482 '''Wrapper vfs preventing any writing.'''
487 '''Wrapper vfs preventing any writing.'''
483
488
484 def __init__(self, vfs):
489 def __init__(self, vfs):
485 proxyvfs.__init__(self, vfs)
490 proxyvfs.__init__(self, vfs)
486
491
487 def __call__(self, path, mode='r', *args, **kw):
492 def __call__(self, path, mode='r', *args, **kw):
488 if mode not in ('r', 'rb'):
493 if mode not in ('r', 'rb'):
489 raise error.Abort(_('this vfs is read only'))
494 raise error.Abort(_('this vfs is read only'))
490 return self.vfs(path, mode, *args, **kw)
495 return self.vfs(path, mode, *args, **kw)
491
496
492 def join(self, path, *insidef):
497 def join(self, path, *insidef):
493 return self.vfs.join(path, *insidef)
498 return self.vfs.join(path, *insidef)
494
499
495 class closewrapbase(object):
500 class closewrapbase(object):
496 """Base class of wrapper, which hooks closing
501 """Base class of wrapper, which hooks closing
497
502
498 Do not instantiate outside of the vfs layer.
503 Do not instantiate outside of the vfs layer.
499 """
504 """
500 def __init__(self, fh):
505 def __init__(self, fh):
501 object.__setattr__(self, r'_origfh', fh)
506 object.__setattr__(self, r'_origfh', fh)
502
507
503 def __getattr__(self, attr):
508 def __getattr__(self, attr):
504 return getattr(self._origfh, attr)
509 return getattr(self._origfh, attr)
505
510
506 def __setattr__(self, attr, value):
511 def __setattr__(self, attr, value):
507 return setattr(self._origfh, attr, value)
512 return setattr(self._origfh, attr, value)
508
513
509 def __delattr__(self, attr):
514 def __delattr__(self, attr):
510 return delattr(self._origfh, attr)
515 return delattr(self._origfh, attr)
511
516
512 def __enter__(self):
517 def __enter__(self):
513 return self._origfh.__enter__()
518 return self._origfh.__enter__()
514
519
515 def __exit__(self, exc_type, exc_value, exc_tb):
520 def __exit__(self, exc_type, exc_value, exc_tb):
516 raise NotImplementedError('attempted instantiating ' + str(type(self)))
521 raise NotImplementedError('attempted instantiating ' + str(type(self)))
517
522
518 def close(self):
523 def close(self):
519 raise NotImplementedError('attempted instantiating ' + str(type(self)))
524 raise NotImplementedError('attempted instantiating ' + str(type(self)))
520
525
521 class delayclosedfile(closewrapbase):
526 class delayclosedfile(closewrapbase):
522 """Proxy for a file object whose close is delayed.
527 """Proxy for a file object whose close is delayed.
523
528
524 Do not instantiate outside of the vfs layer.
529 Do not instantiate outside of the vfs layer.
525 """
530 """
526 def __init__(self, fh, closer):
531 def __init__(self, fh, closer):
527 super(delayclosedfile, self).__init__(fh)
532 super(delayclosedfile, self).__init__(fh)
528 object.__setattr__(self, r'_closer', closer)
533 object.__setattr__(self, r'_closer', closer)
529
534
530 def __exit__(self, exc_type, exc_value, exc_tb):
535 def __exit__(self, exc_type, exc_value, exc_tb):
531 self._closer.close(self._origfh)
536 self._closer.close(self._origfh)
532
537
533 def close(self):
538 def close(self):
534 self._closer.close(self._origfh)
539 self._closer.close(self._origfh)
535
540
536 class backgroundfilecloser(object):
541 class backgroundfilecloser(object):
537 """Coordinates background closing of file handles on multiple threads."""
542 """Coordinates background closing of file handles on multiple threads."""
538 def __init__(self, ui, expectedcount=-1):
543 def __init__(self, ui, expectedcount=-1):
539 self._running = False
544 self._running = False
540 self._entered = False
545 self._entered = False
541 self._threads = []
546 self._threads = []
542 self._threadexception = None
547 self._threadexception = None
543
548
544 # Only Windows/NTFS has slow file closing. So only enable by default
549 # Only Windows/NTFS has slow file closing. So only enable by default
545 # on that platform. But allow to be enabled elsewhere for testing.
550 # on that platform. But allow to be enabled elsewhere for testing.
546 defaultenabled = pycompat.iswindows
551 defaultenabled = pycompat.iswindows
547 enabled = ui.configbool('worker', 'backgroundclose', defaultenabled)
552 enabled = ui.configbool('worker', 'backgroundclose', defaultenabled)
548
553
549 if not enabled:
554 if not enabled:
550 return
555 return
551
556
552 # There is overhead to starting and stopping the background threads.
557 # There is overhead to starting and stopping the background threads.
553 # Don't do background processing unless the file count is large enough
558 # Don't do background processing unless the file count is large enough
554 # to justify it.
559 # to justify it.
555 minfilecount = ui.configint('worker', 'backgroundcloseminfilecount')
560 minfilecount = ui.configint('worker', 'backgroundcloseminfilecount')
556 # FUTURE dynamically start background threads after minfilecount closes.
561 # FUTURE dynamically start background threads after minfilecount closes.
557 # (We don't currently have any callers that don't know their file count)
562 # (We don't currently have any callers that don't know their file count)
558 if expectedcount > 0 and expectedcount < minfilecount:
563 if expectedcount > 0 and expectedcount < minfilecount:
559 return
564 return
560
565
561 maxqueue = ui.configint('worker', 'backgroundclosemaxqueue')
566 maxqueue = ui.configint('worker', 'backgroundclosemaxqueue')
562 threadcount = ui.configint('worker', 'backgroundclosethreadcount')
567 threadcount = ui.configint('worker', 'backgroundclosethreadcount')
563
568
564 ui.debug('starting %d threads for background file closing\n' %
569 ui.debug('starting %d threads for background file closing\n' %
565 threadcount)
570 threadcount)
566
571
567 self._queue = util.queue(maxsize=maxqueue)
572 self._queue = util.queue(maxsize=maxqueue)
568 self._running = True
573 self._running = True
569
574
570 for i in range(threadcount):
575 for i in range(threadcount):
571 t = threading.Thread(target=self._worker, name='backgroundcloser')
576 t = threading.Thread(target=self._worker, name='backgroundcloser')
572 self._threads.append(t)
577 self._threads.append(t)
573 t.start()
578 t.start()
574
579
575 def __enter__(self):
580 def __enter__(self):
576 self._entered = True
581 self._entered = True
577 return self
582 return self
578
583
579 def __exit__(self, exc_type, exc_value, exc_tb):
584 def __exit__(self, exc_type, exc_value, exc_tb):
580 self._running = False
585 self._running = False
581
586
582 # Wait for threads to finish closing so open files don't linger for
587 # Wait for threads to finish closing so open files don't linger for
583 # longer than lifetime of context manager.
588 # longer than lifetime of context manager.
584 for t in self._threads:
589 for t in self._threads:
585 t.join()
590 t.join()
586
591
587 def _worker(self):
592 def _worker(self):
588 """Main routine for worker thread."""
593 """Main routine for worker thread."""
589 while True:
594 while True:
590 try:
595 try:
591 fh = self._queue.get(block=True, timeout=0.100)
596 fh = self._queue.get(block=True, timeout=0.100)
592 # Need to catch or the thread will terminate and
597 # Need to catch or the thread will terminate and
593 # we could orphan file descriptors.
598 # we could orphan file descriptors.
594 try:
599 try:
595 fh.close()
600 fh.close()
596 except Exception as e:
601 except Exception as e:
597 # Stash so can re-raise from main thread later.
602 # Stash so can re-raise from main thread later.
598 self._threadexception = e
603 self._threadexception = e
599 except util.empty:
604 except util.empty:
600 if not self._running:
605 if not self._running:
601 break
606 break
602
607
603 def close(self, fh):
608 def close(self, fh):
604 """Schedule a file for closing."""
609 """Schedule a file for closing."""
605 if not self._entered:
610 if not self._entered:
606 raise error.Abort(_('can only call close() when context manager '
611 raise error.Abort(_('can only call close() when context manager '
607 'active'))
612 'active'))
608
613
609 # If a background thread encountered an exception, raise now so we fail
614 # If a background thread encountered an exception, raise now so we fail
610 # fast. Otherwise we may potentially go on for minutes until the error
615 # fast. Otherwise we may potentially go on for minutes until the error
611 # is acted on.
616 # is acted on.
612 if self._threadexception:
617 if self._threadexception:
613 e = self._threadexception
618 e = self._threadexception
614 self._threadexception = None
619 self._threadexception = None
615 raise e
620 raise e
616
621
617 # If we're not actively running, close synchronously.
622 # If we're not actively running, close synchronously.
618 if not self._running:
623 if not self._running:
619 fh.close()
624 fh.close()
620 return
625 return
621
626
622 self._queue.put(fh, block=True, timeout=None)
627 self._queue.put(fh, block=True, timeout=None)
623
628
624 class checkambigatclosing(closewrapbase):
629 class checkambigatclosing(closewrapbase):
625 """Proxy for a file object, to avoid ambiguity of file stat
630 """Proxy for a file object, to avoid ambiguity of file stat
626
631
627 See also util.filestat for detail about "ambiguity of file stat".
632 See also util.filestat for detail about "ambiguity of file stat".
628
633
629 This proxy is useful only if the target file is guarded by any
634 This proxy is useful only if the target file is guarded by any
630 lock (e.g. repo.lock or repo.wlock)
635 lock (e.g. repo.lock or repo.wlock)
631
636
632 Do not instantiate outside of the vfs layer.
637 Do not instantiate outside of the vfs layer.
633 """
638 """
634 def __init__(self, fh):
639 def __init__(self, fh):
635 super(checkambigatclosing, self).__init__(fh)
640 super(checkambigatclosing, self).__init__(fh)
636 object.__setattr__(self, r'_oldstat', util.filestat.frompath(fh.name))
641 object.__setattr__(self, r'_oldstat', util.filestat.frompath(fh.name))
637
642
638 def _checkambig(self):
643 def _checkambig(self):
639 oldstat = self._oldstat
644 oldstat = self._oldstat
640 if oldstat.stat:
645 if oldstat.stat:
641 _avoidambig(self._origfh.name, oldstat)
646 _avoidambig(self._origfh.name, oldstat)
642
647
643 def __exit__(self, exc_type, exc_value, exc_tb):
648 def __exit__(self, exc_type, exc_value, exc_tb):
644 self._origfh.__exit__(exc_type, exc_value, exc_tb)
649 self._origfh.__exit__(exc_type, exc_value, exc_tb)
645 self._checkambig()
650 self._checkambig()
646
651
647 def close(self):
652 def close(self):
648 self._origfh.close()
653 self._origfh.close()
649 self._checkambig()
654 self._checkambig()
General Comments 0
You need to be logged in to leave comments. Login now