##// END OF EJS Templates
vfs: Fix deprecation warning in Python 3.10 (issue6520)...
Karthikeyan Singaravelan -
r47952:63286e08 default
parent child Browse files
Show More
@@ -1,751 +1,751 b''
1 1 # vfs.py - Mercurial 'vfs' classes
2 2 #
3 3 # Copyright Olivia Mackall <olivia@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 return util.removedirs(self.join(path))
233 233
234 234 def rmdir(self, path=None):
235 235 """Remove an empty directory."""
236 236 return os.rmdir(self.join(path))
237 237
238 238 def rmtree(self, path=None, ignore_errors=False, forcibly=False):
239 239 """Remove a directory tree recursively
240 240
241 241 If ``forcibly``, this tries to remove READ-ONLY files, too.
242 242 """
243 243 if forcibly:
244 244
245 245 def onerror(function, path, excinfo):
246 246 if function is not os.remove:
247 247 raise
248 248 # read-only files cannot be unlinked under Windows
249 249 s = os.stat(path)
250 250 if (s.st_mode & stat.S_IWRITE) != 0:
251 251 raise
252 252 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
253 253 os.remove(path)
254 254
255 255 else:
256 256 onerror = None
257 257 return shutil.rmtree(
258 258 self.join(path), ignore_errors=ignore_errors, onerror=onerror
259 259 )
260 260
261 261 def setflags(self, path, l, x):
262 262 return util.setflags(self.join(path), l, x)
263 263
264 264 def stat(self, path=None):
265 265 return os.stat(self.join(path))
266 266
267 267 def unlink(self, path=None):
268 268 return util.unlink(self.join(path))
269 269
270 270 def tryunlink(self, path=None):
271 271 """Attempt to remove a file, ignoring missing file errors."""
272 272 util.tryunlink(self.join(path))
273 273
274 274 def unlinkpath(self, path=None, ignoremissing=False, rmdir=True):
275 275 return util.unlinkpath(
276 276 self.join(path), ignoremissing=ignoremissing, rmdir=rmdir
277 277 )
278 278
279 279 def utime(self, path=None, t=None):
280 280 return os.utime(self.join(path), t)
281 281
282 282 def walk(self, path=None, onerror=None):
283 283 """Yield (dirpath, dirs, files) tuple for each directories under path
284 284
285 285 ``dirpath`` is relative one from the root of this vfs. This
286 286 uses ``os.sep`` as path separator, even you specify POSIX
287 287 style ``path``.
288 288
289 289 "The root of this vfs" is represented as empty ``dirpath``.
290 290 """
291 291 root = os.path.normpath(self.join(None))
292 292 # when dirpath == root, dirpath[prefixlen:] becomes empty
293 293 # because len(dirpath) < prefixlen.
294 294 prefixlen = len(pathutil.normasprefix(root))
295 295 for dirpath, dirs, files in os.walk(self.join(path), onerror=onerror):
296 296 yield (dirpath[prefixlen:], dirs, files)
297 297
298 298 @contextlib.contextmanager
299 299 def backgroundclosing(self, ui, expectedcount=-1):
300 300 """Allow files to be closed asynchronously.
301 301
302 302 When this context manager is active, ``backgroundclose`` can be passed
303 303 to ``__call__``/``open`` to result in the file possibly being closed
304 304 asynchronously, on a background thread.
305 305 """
306 306 # Sharing backgroundfilecloser between threads is complex and using
307 307 # multiple instances puts us at risk of running out of file descriptors
308 308 # only allow to use backgroundfilecloser when in main thread.
309 309 if not isinstance(
310 threading.currentThread(),
310 threading.current_thread(),
311 311 threading._MainThread, # pytype: disable=module-attr
312 312 ):
313 313 yield
314 314 return
315 315 vfs = getattr(self, 'vfs', self)
316 316 if getattr(vfs, '_backgroundfilecloser', None):
317 317 raise error.Abort(
318 318 _(b'can only have 1 active background file closer')
319 319 )
320 320
321 321 with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc:
322 322 try:
323 323 vfs._backgroundfilecloser = (
324 324 bfc # pytype: disable=attribute-error
325 325 )
326 326 yield bfc
327 327 finally:
328 328 vfs._backgroundfilecloser = (
329 329 None # pytype: disable=attribute-error
330 330 )
331 331
332 332
333 333 class vfs(abstractvfs):
334 334 """Operate files relative to a base directory
335 335
336 336 This class is used to hide the details of COW semantics and
337 337 remote file access from higher level code.
338 338
339 339 'cacheaudited' should be enabled only if (a) vfs object is short-lived, or
340 340 (b) the base directory is managed by hg and considered sort-of append-only.
341 341 See pathutil.pathauditor() for details.
342 342 """
343 343
344 344 def __init__(
345 345 self,
346 346 base,
347 347 audit=True,
348 348 cacheaudited=False,
349 349 expandpath=False,
350 350 realpath=False,
351 351 ):
352 352 if expandpath:
353 353 base = util.expandpath(base)
354 354 if realpath:
355 355 base = os.path.realpath(base)
356 356 self.base = base
357 357 self._audit = audit
358 358 if audit:
359 359 self.audit = pathutil.pathauditor(self.base, cached=cacheaudited)
360 360 else:
361 361 self.audit = lambda path, mode=None: True
362 362 self.createmode = None
363 363 self._trustnlink = None
364 364 self.options = {}
365 365
366 366 @util.propertycache
367 367 def _cansymlink(self):
368 368 return util.checklink(self.base)
369 369
370 370 @util.propertycache
371 371 def _chmod(self):
372 372 return util.checkexec(self.base)
373 373
374 374 def _fixfilemode(self, name):
375 375 if self.createmode is None or not self._chmod:
376 376 return
377 377 os.chmod(name, self.createmode & 0o666)
378 378
379 379 def _auditpath(self, path, mode):
380 380 if self._audit:
381 381 if os.path.isabs(path) and path.startswith(self.base):
382 382 path = os.path.relpath(path, self.base)
383 383 r = util.checkosfilename(path)
384 384 if r:
385 385 raise error.Abort(b"%s: %r" % (r, path))
386 386 self.audit(path, mode=mode)
387 387
388 388 def __call__(
389 389 self,
390 390 path,
391 391 mode=b"r",
392 392 atomictemp=False,
393 393 notindexed=False,
394 394 backgroundclose=False,
395 395 checkambig=False,
396 396 auditpath=True,
397 397 makeparentdirs=True,
398 398 ):
399 399 """Open ``path`` file, which is relative to vfs root.
400 400
401 401 By default, parent directories are created as needed. Newly created
402 402 directories are marked as "not to be indexed by the content indexing
403 403 service", if ``notindexed`` is specified for "write" mode access.
404 404 Set ``makeparentdirs=False`` to not create directories implicitly.
405 405
406 406 If ``backgroundclose`` is passed, the file may be closed asynchronously.
407 407 It can only be used if the ``self.backgroundclosing()`` context manager
408 408 is active. This should only be specified if the following criteria hold:
409 409
410 410 1. There is a potential for writing thousands of files. Unless you
411 411 are writing thousands of files, the performance benefits of
412 412 asynchronously closing files is not realized.
413 413 2. Files are opened exactly once for the ``backgroundclosing``
414 414 active duration and are therefore free of race conditions between
415 415 closing a file on a background thread and reopening it. (If the
416 416 file were opened multiple times, there could be unflushed data
417 417 because the original file handle hasn't been flushed/closed yet.)
418 418
419 419 ``checkambig`` argument is passed to atomictempfile (valid
420 420 only for writing), and is useful only if target file is
421 421 guarded by any lock (e.g. repo.lock or repo.wlock).
422 422
423 423 To avoid file stat ambiguity forcibly, checkambig=True involves
424 424 copying ``path`` file opened in "append" mode (e.g. for
425 425 truncation), if it is owned by another. Therefore, use
426 426 combination of append mode and checkambig=True only in limited
427 427 cases (see also issue5418 and issue5584 for detail).
428 428 """
429 429 if auditpath:
430 430 self._auditpath(path, mode)
431 431 f = self.join(path)
432 432
433 433 if b"b" not in mode:
434 434 mode += b"b" # for that other OS
435 435
436 436 nlink = -1
437 437 if mode not in (b'r', b'rb'):
438 438 dirname, basename = util.split(f)
439 439 # If basename is empty, then the path is malformed because it points
440 440 # to a directory. Let the posixfile() call below raise IOError.
441 441 if basename:
442 442 if atomictemp:
443 443 if makeparentdirs:
444 444 util.makedirs(dirname, self.createmode, notindexed)
445 445 return util.atomictempfile(
446 446 f, mode, self.createmode, checkambig=checkambig
447 447 )
448 448 try:
449 449 if b'w' in mode:
450 450 util.unlink(f)
451 451 nlink = 0
452 452 else:
453 453 # nlinks() may behave differently for files on Windows
454 454 # shares if the file is open.
455 455 with util.posixfile(f):
456 456 nlink = util.nlinks(f)
457 457 if nlink < 1:
458 458 nlink = 2 # force mktempcopy (issue1922)
459 459 except (OSError, IOError) as e:
460 460 if e.errno != errno.ENOENT:
461 461 raise
462 462 nlink = 0
463 463 if makeparentdirs:
464 464 util.makedirs(dirname, self.createmode, notindexed)
465 465 if nlink > 0:
466 466 if self._trustnlink is None:
467 467 self._trustnlink = nlink > 1 or util.checknlink(f)
468 468 if nlink > 1 or not self._trustnlink:
469 469 util.rename(util.mktempcopy(f), f)
470 470 fp = util.posixfile(f, mode)
471 471 if nlink == 0:
472 472 self._fixfilemode(f)
473 473
474 474 if checkambig:
475 475 if mode in (b'r', b'rb'):
476 476 raise error.Abort(
477 477 _(
478 478 b'implementation error: mode %s is not'
479 479 b' valid for checkambig=True'
480 480 )
481 481 % mode
482 482 )
483 483 fp = checkambigatclosing(fp)
484 484
485 485 if backgroundclose and isinstance(
486 threading.currentThread(),
486 threading.current_thread(),
487 487 threading._MainThread, # pytype: disable=module-attr
488 488 ):
489 489 if (
490 490 not self._backgroundfilecloser # pytype: disable=attribute-error
491 491 ):
492 492 raise error.Abort(
493 493 _(
494 494 b'backgroundclose can only be used when a '
495 495 b'backgroundclosing context manager is active'
496 496 )
497 497 )
498 498
499 499 fp = delayclosedfile(
500 500 fp,
501 501 self._backgroundfilecloser, # pytype: disable=attribute-error
502 502 )
503 503
504 504 return fp
505 505
506 506 def symlink(self, src, dst):
507 507 self.audit(dst)
508 508 linkname = self.join(dst)
509 509 util.tryunlink(linkname)
510 510
511 511 util.makedirs(os.path.dirname(linkname), self.createmode)
512 512
513 513 if self._cansymlink:
514 514 try:
515 515 os.symlink(src, linkname)
516 516 except OSError as err:
517 517 raise OSError(
518 518 err.errno,
519 519 _(b'could not symlink to %r: %s')
520 520 % (src, encoding.strtolocal(err.strerror)),
521 521 linkname,
522 522 )
523 523 else:
524 524 self.write(dst, src)
525 525
526 526 def join(self, path, *insidef):
527 527 if path:
528 528 return os.path.join(self.base, path, *insidef)
529 529 else:
530 530 return self.base
531 531
532 532
533 533 opener = vfs
534 534
535 535
536 536 class proxyvfs(abstractvfs):
537 537 def __init__(self, vfs):
538 538 self.vfs = vfs
539 539
540 540 def _auditpath(self, path, mode):
541 541 return self.vfs._auditpath(path, mode)
542 542
543 543 @property
544 544 def options(self):
545 545 return self.vfs.options
546 546
547 547 @options.setter
548 548 def options(self, value):
549 549 self.vfs.options = value
550 550
551 551
552 552 class filtervfs(proxyvfs, abstractvfs):
553 553 '''Wrapper vfs for filtering filenames with a function.'''
554 554
555 555 def __init__(self, vfs, filter):
556 556 proxyvfs.__init__(self, vfs)
557 557 self._filter = filter
558 558
559 559 def __call__(self, path, *args, **kwargs):
560 560 return self.vfs(self._filter(path), *args, **kwargs)
561 561
562 562 def join(self, path, *insidef):
563 563 if path:
564 564 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
565 565 else:
566 566 return self.vfs.join(path)
567 567
568 568
569 569 filteropener = filtervfs
570 570
571 571
572 572 class readonlyvfs(proxyvfs):
573 573 '''Wrapper vfs preventing any writing.'''
574 574
575 575 def __init__(self, vfs):
576 576 proxyvfs.__init__(self, vfs)
577 577
578 578 def __call__(self, path, mode=b'r', *args, **kw):
579 579 if mode not in (b'r', b'rb'):
580 580 raise error.Abort(_(b'this vfs is read only'))
581 581 return self.vfs(path, mode, *args, **kw)
582 582
583 583 def join(self, path, *insidef):
584 584 return self.vfs.join(path, *insidef)
585 585
586 586
587 587 class closewrapbase(object):
588 588 """Base class of wrapper, which hooks closing
589 589
590 590 Do not instantiate outside of the vfs layer.
591 591 """
592 592
593 593 def __init__(self, fh):
594 594 object.__setattr__(self, '_origfh', fh)
595 595
596 596 def __getattr__(self, attr):
597 597 return getattr(self._origfh, attr)
598 598
599 599 def __setattr__(self, attr, value):
600 600 return setattr(self._origfh, attr, value)
601 601
602 602 def __delattr__(self, attr):
603 603 return delattr(self._origfh, attr)
604 604
605 605 def __enter__(self):
606 606 self._origfh.__enter__()
607 607 return self
608 608
609 609 def __exit__(self, exc_type, exc_value, exc_tb):
610 610 raise NotImplementedError('attempted instantiating ' + str(type(self)))
611 611
612 612 def close(self):
613 613 raise NotImplementedError('attempted instantiating ' + str(type(self)))
614 614
615 615
616 616 class delayclosedfile(closewrapbase):
617 617 """Proxy for a file object whose close is delayed.
618 618
619 619 Do not instantiate outside of the vfs layer.
620 620 """
621 621
622 622 def __init__(self, fh, closer):
623 623 super(delayclosedfile, self).__init__(fh)
624 624 object.__setattr__(self, '_closer', closer)
625 625
626 626 def __exit__(self, exc_type, exc_value, exc_tb):
627 627 self._closer.close(self._origfh)
628 628
629 629 def close(self):
630 630 self._closer.close(self._origfh)
631 631
632 632
633 633 class backgroundfilecloser(object):
634 634 """Coordinates background closing of file handles on multiple threads."""
635 635
636 636 def __init__(self, ui, expectedcount=-1):
637 637 self._running = False
638 638 self._entered = False
639 639 self._threads = []
640 640 self._threadexception = None
641 641
642 642 # Only Windows/NTFS has slow file closing. So only enable by default
643 643 # on that platform. But allow to be enabled elsewhere for testing.
644 644 defaultenabled = pycompat.iswindows
645 645 enabled = ui.configbool(b'worker', b'backgroundclose', defaultenabled)
646 646
647 647 if not enabled:
648 648 return
649 649
650 650 # There is overhead to starting and stopping the background threads.
651 651 # Don't do background processing unless the file count is large enough
652 652 # to justify it.
653 653 minfilecount = ui.configint(b'worker', b'backgroundcloseminfilecount')
654 654 # FUTURE dynamically start background threads after minfilecount closes.
655 655 # (We don't currently have any callers that don't know their file count)
656 656 if expectedcount > 0 and expectedcount < minfilecount:
657 657 return
658 658
659 659 maxqueue = ui.configint(b'worker', b'backgroundclosemaxqueue')
660 660 threadcount = ui.configint(b'worker', b'backgroundclosethreadcount')
661 661
662 662 ui.debug(
663 663 b'starting %d threads for background file closing\n' % threadcount
664 664 )
665 665
666 666 self._queue = pycompat.queue.Queue(maxsize=maxqueue)
667 667 self._running = True
668 668
669 669 for i in range(threadcount):
670 670 t = threading.Thread(target=self._worker, name='backgroundcloser')
671 671 self._threads.append(t)
672 672 t.start()
673 673
674 674 def __enter__(self):
675 675 self._entered = True
676 676 return self
677 677
678 678 def __exit__(self, exc_type, exc_value, exc_tb):
679 679 self._running = False
680 680
681 681 # Wait for threads to finish closing so open files don't linger for
682 682 # longer than lifetime of context manager.
683 683 for t in self._threads:
684 684 t.join()
685 685
686 686 def _worker(self):
687 687 """Main routine for worker thread."""
688 688 while True:
689 689 try:
690 690 fh = self._queue.get(block=True, timeout=0.100)
691 691 # Need to catch or the thread will terminate and
692 692 # we could orphan file descriptors.
693 693 try:
694 694 fh.close()
695 695 except Exception as e:
696 696 # Stash so can re-raise from main thread later.
697 697 self._threadexception = e
698 698 except pycompat.queue.Empty:
699 699 if not self._running:
700 700 break
701 701
702 702 def close(self, fh):
703 703 """Schedule a file for closing."""
704 704 if not self._entered:
705 705 raise error.Abort(
706 706 _(b'can only call close() when context manager active')
707 707 )
708 708
709 709 # If a background thread encountered an exception, raise now so we fail
710 710 # fast. Otherwise we may potentially go on for minutes until the error
711 711 # is acted on.
712 712 if self._threadexception:
713 713 e = self._threadexception
714 714 self._threadexception = None
715 715 raise e
716 716
717 717 # If we're not actively running, close synchronously.
718 718 if not self._running:
719 719 fh.close()
720 720 return
721 721
722 722 self._queue.put(fh, block=True, timeout=None)
723 723
724 724
725 725 class checkambigatclosing(closewrapbase):
726 726 """Proxy for a file object, to avoid ambiguity of file stat
727 727
728 728 See also util.filestat for detail about "ambiguity of file stat".
729 729
730 730 This proxy is useful only if the target file is guarded by any
731 731 lock (e.g. repo.lock or repo.wlock)
732 732
733 733 Do not instantiate outside of the vfs layer.
734 734 """
735 735
736 736 def __init__(self, fh):
737 737 super(checkambigatclosing, self).__init__(fh)
738 738 object.__setattr__(self, '_oldstat', util.filestat.frompath(fh.name))
739 739
740 740 def _checkambig(self):
741 741 oldstat = self._oldstat
742 742 if oldstat.stat:
743 743 _avoidambig(self._origfh.name, oldstat)
744 744
745 745 def __exit__(self, exc_type, exc_value, exc_tb):
746 746 self._origfh.__exit__(exc_type, exc_value, exc_tb)
747 747 self._checkambig()
748 748
749 749 def close(self):
750 750 self._origfh.close()
751 751 self._checkambig()
General Comments 0
You need to be logged in to leave comments. Login now