comparison mercurial/localrepo.py @ 33389:7e89bd0cfb86

localrepo: cache types for filtered repos (issue5043) Python introduces a reference cycle on dynamically created types via __mro__, making them very easy to leak. See https://bugs.python.org/issue17950. Previously, repo.filtered() created a type on every invocation. Long-running processes (like `hg convert`) could call this function thousands of times, leading to a steady memory leak. Since we're Unable to stop the leak because this is a bug in Python, the next best thing is to contain it. This patch adds a cache of of the dynamically generated repoview/filter types on the localrepo object. Since we only generate each type once, we cap the amount of memory that can leak to something reasonable. After this change, `hg convert` no longer leaks memory on every revision. The process will likely grow memory usage over time due to e.g. larger manifests. But there are no leaks.
author Gregory Szorc <gregory.szorc@gmail.com>
date Sat, 01 Jul 2017 20:51:19 -0700
parents b107a7660f4e
children 1bb209d08a34
comparison
equal deleted inserted replaced
33388:0823f0983eaa 33389:7e89bd0cfb86
428 self.filteredrevcache = {} 428 self.filteredrevcache = {}
429 429
430 # post-dirstate-status hooks 430 # post-dirstate-status hooks
431 self._postdsstatus = [] 431 self._postdsstatus = []
432 432
433 # Cache of types representing filtered repos.
434 self._filteredrepotypes = weakref.WeakKeyDictionary()
435
433 # generic mapping between names and nodes 436 # generic mapping between names and nodes
434 self.names = namespaces.namespaces() 437 self.names = namespaces.namespaces()
435 438
436 # Key to signature value. 439 # Key to signature value.
437 self._sparsesignaturecache = {} 440 self._sparsesignaturecache = {}
537 Intended to be overwritten by filtered repo.""" 540 Intended to be overwritten by filtered repo."""
538 return self 541 return self
539 542
540 def filtered(self, name): 543 def filtered(self, name):
541 """Return a filtered version of a repository""" 544 """Return a filtered version of a repository"""
542 # build a new class with the mixin and the current class 545 # Python <3.4 easily leaks types via __mro__. See
543 # (possibly subclass of the repo) 546 # https://bugs.python.org/issue17950. We cache dynamically
544 class filteredrepo(repoview.repoview, self.unfiltered().__class__): 547 # created types so this method doesn't leak on every
545 pass 548 # invocation.
546 return filteredrepo(self, name) 549
550 key = self.unfiltered().__class__
551 if key not in self._filteredrepotypes:
552 # Build a new type with the repoview mixin and the base
553 # class of this repo. Give it a name containing the
554 # filter name to aid debugging.
555 bases = (repoview.repoview, key)
556 cls = type('%sfilteredrepo' % name, bases, {})
557 self._filteredrepotypes[key] = cls
558
559 return self._filteredrepotypes[key](self, name)
547 560
548 @repofilecache('bookmarks', 'bookmarks.current') 561 @repofilecache('bookmarks', 'bookmarks.current')
549 def _bookmarks(self): 562 def _bookmarks(self):
550 return bookmarks.bmstore(self) 563 return bookmarks.bmstore(self)
551 564