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