Show More
@@ -0,0 +1,49 b'' | |||||
|
1 | from __future__ import absolute_import | |||
|
2 | ||||
|
3 | import os | |||
|
4 | from mercurial.hgweb import hgwebdir_mod | |||
|
5 | ||||
|
6 | hgwebdir = hgwebdir_mod.hgwebdir | |||
|
7 | ||||
|
8 | os.mkdir(b'webdir') | |||
|
9 | os.chdir(b'webdir') | |||
|
10 | ||||
|
11 | webdir = os.path.realpath(b'.') | |||
|
12 | ||||
|
13 | ||||
|
14 | def trivial_response(req, res): | |||
|
15 | return [] | |||
|
16 | ||||
|
17 | ||||
|
18 | def make_hgwebdir(gc_rate=None): | |||
|
19 | config = os.path.join(webdir, b'hgwebdir.conf') | |||
|
20 | with open(config, 'wb') as configfile: | |||
|
21 | configfile.write(b'[experimental]\n') | |||
|
22 | if gc_rate is not None: | |||
|
23 | configfile.write(b'web.full-garbage-collection-rate=%d\n' % gc_rate) | |||
|
24 | hg_wd = hgwebdir(config) | |||
|
25 | hg_wd._runwsgi = trivial_response | |||
|
26 | return hg_wd | |||
|
27 | ||||
|
28 | ||||
|
29 | def process_requests(webdir_instance, number): | |||
|
30 | # we don't care for now about passing realistic arguments | |||
|
31 | for _ in range(number): | |||
|
32 | for chunk in webdir_instance.run_wsgi(None, None): | |||
|
33 | pass | |||
|
34 | ||||
|
35 | ||||
|
36 | without_gc = make_hgwebdir(gc_rate=0) | |||
|
37 | process_requests(without_gc, 5) | |||
|
38 | assert without_gc.requests_count == 5 | |||
|
39 | assert without_gc.gc_full_collections_done == 0 | |||
|
40 | ||||
|
41 | with_gc = make_hgwebdir(gc_rate=2) | |||
|
42 | process_requests(with_gc, 5) | |||
|
43 | assert with_gc.requests_count == 5 | |||
|
44 | assert with_gc.gc_full_collections_done == 2 | |||
|
45 | ||||
|
46 | with_systematic_gc = make_hgwebdir() # default value of the setting | |||
|
47 | process_requests(with_systematic_gc, 3) | |||
|
48 | assert with_systematic_gc.requests_count == 3 | |||
|
49 | assert with_systematic_gc.gc_full_collections_done == 3 |
@@ -1266,6 +1266,11 b' coreconfigitem(' | |||||
1266 | ) |
|
1266 | ) | |
1267 | coreconfigitem( |
|
1267 | coreconfigitem( | |
1268 | b'experimental', |
|
1268 | b'experimental', | |
|
1269 | b'web.full-garbage-collection-rate', | |||
|
1270 | default=1, # still forcing a full collection on each request | |||
|
1271 | ) | |||
|
1272 | coreconfigitem( | |||
|
1273 | b'experimental', | |||
1269 | b'worker.wdir-get-thread-safe', |
|
1274 | b'worker.wdir-get-thread-safe', | |
1270 | default=False, |
|
1275 | default=False, | |
1271 | ) |
|
1276 | ) |
@@ -285,6 +285,7 b' class hgwebdir(object):' | |||||
285 | self.lastrefresh = 0 |
|
285 | self.lastrefresh = 0 | |
286 | self.motd = None |
|
286 | self.motd = None | |
287 | self.refresh() |
|
287 | self.refresh() | |
|
288 | self.requests_count = 0 | |||
288 | if not baseui: |
|
289 | if not baseui: | |
289 | # set up environment for new ui |
|
290 | # set up environment for new ui | |
290 | extensions.loadall(self.ui) |
|
291 | extensions.loadall(self.ui) | |
@@ -341,6 +342,10 b' class hgwebdir(object):' | |||||
341 |
|
342 | |||
342 | self.repos = repos |
|
343 | self.repos = repos | |
343 | self.ui = u |
|
344 | self.ui = u | |
|
345 | self.gc_full_collect_rate = self.ui.configint( | |||
|
346 | b'experimental', b'web.full-garbage-collection-rate' | |||
|
347 | ) | |||
|
348 | self.gc_full_collections_done = 0 | |||
344 | encoding.encoding = self.ui.config(b'web', b'encoding') |
|
349 | encoding.encoding = self.ui.config(b'web', b'encoding') | |
345 | self.style = self.ui.config(b'web', b'style') |
|
350 | self.style = self.ui.config(b'web', b'style') | |
346 | self.templatepath = self.ui.config( |
|
351 | self.templatepath = self.ui.config( | |
@@ -383,12 +388,27 b' class hgwebdir(object):' | |||||
383 | finally: |
|
388 | finally: | |
384 | # There are known cycles in localrepository that prevent |
|
389 | # There are known cycles in localrepository that prevent | |
385 | # those objects (and tons of held references) from being |
|
390 | # those objects (and tons of held references) from being | |
386 |
# collected through normal refcounting. |
|
391 | # collected through normal refcounting. | |
387 | # leaks by performing an explicit GC on every request. |
|
392 | # In some cases, the resulting memory consumption can | |
388 | # TODO remove this once leaks are fixed. |
|
393 | # be tamed by performing explicit garbage collections. | |
389 | # TODO only run this on requests that create localrepository |
|
394 | # In presence of actual leaks or big long-lived caches, the | |
390 | # instances instead of every request. |
|
395 | # impact on performance of such collections can become a | |
391 | gc.collect() |
|
396 | # problem, hence the rate shouldn't be set too low. | |
|
397 | # See "Collecting the oldest generation" in | |||
|
398 | # https://devguide.python.org/garbage_collector | |||
|
399 | # for more about such trade-offs. | |||
|
400 | rate = self.gc_full_collect_rate | |||
|
401 | ||||
|
402 | # this is not thread safe, but the consequence (skipping | |||
|
403 | # a garbage collection) is arguably better than risking | |||
|
404 | # to have several threads perform a collection in parallel | |||
|
405 | # (long useless wait on all threads). | |||
|
406 | self.requests_count += 1 | |||
|
407 | if rate > 0 and self.requests_count % rate == 0: | |||
|
408 | gc.collect() | |||
|
409 | self.gc_full_collections_done += 1 | |||
|
410 | else: | |||
|
411 | gc.collect(generation=1) | |||
392 |
|
412 | |||
393 | def _runwsgi(self, req, res): |
|
413 | def _runwsgi(self, req, res): | |
394 | try: |
|
414 | try: |
General Comments 0
You need to be logged in to leave comments.
Login now