--- a/mercurial/hgweb/hgwebdir_mod.py Wed Jul 28 13:45:07 2021 +0300
+++ b/mercurial/hgweb/hgwebdir_mod.py Tue Jul 20 17:20:19 2021 +0200
@@ -285,6 +285,7 @@
self.lastrefresh = 0
self.motd = None
self.refresh()
+ self.requests_count = 0
if not baseui:
# set up environment for new ui
extensions.loadall(self.ui)
@@ -341,6 +342,10 @@
self.repos = repos
self.ui = u
+ self.gc_full_collect_rate = self.ui.configint(
+ b'experimental', b'web.full-garbage-collection-rate'
+ )
+ self.gc_full_collections_done = 0
encoding.encoding = self.ui.config(b'web', b'encoding')
self.style = self.ui.config(b'web', b'style')
self.templatepath = self.ui.config(
@@ -383,12 +388,27 @@
finally:
# There are known cycles in localrepository that prevent
# those objects (and tons of held references) from being
- # collected through normal refcounting. We mitigate those
- # leaks by performing an explicit GC on every request.
- # TODO remove this once leaks are fixed.
- # TODO only run this on requests that create localrepository
- # instances instead of every request.
- gc.collect()
+ # collected through normal refcounting.
+ # In some cases, the resulting memory consumption can
+ # be tamed by performing explicit garbage collections.
+ # In presence of actual leaks or big long-lived caches, the
+ # impact on performance of such collections can become a
+ # problem, hence the rate shouldn't be set too low.
+ # See "Collecting the oldest generation" in
+ # https://devguide.python.org/garbage_collector
+ # for more about such trade-offs.
+ rate = self.gc_full_collect_rate
+
+ # this is not thread safe, but the consequence (skipping
+ # a garbage collection) is arguably better than risking
+ # to have several threads perform a collection in parallel
+ # (long useless wait on all threads).
+ self.requests_count += 1
+ if rate > 0 and self.requests_count % rate == 0:
+ gc.collect()
+ self.gc_full_collections_done += 1
+ else:
+ gc.collect(generation=1)
def _runwsgi(self, req, res):
try: