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