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