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