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