Michael Merickel
2017-03-04 98517a9ae1c10308fe71f79db4a005d11bc0a869
Merge pull request #2972 from mmerickel/backport-2967-to-1.7-branch

Fixed several reference cycles to prevent memory leaks. Added simple …
5 files modified
57 ■■■■ changed files
CHANGES.txt 14 ●●●● patch | view | raw | blame | history
CONTRIBUTORS.txt 2 ●●●●● patch | view | raw | blame | history
pyramid/registry.py 4 ●●● patch | view | raw | blame | history
pyramid/tests/test_integration.py 28 ●●●●● patch | view | raw | blame | history
pyramid/util.py 9 ●●●●● patch | view | raw | blame | history
CHANGES.txt
@@ -1,12 +1,18 @@
.. _changes_1.7.4:
Unreleased
==========
1.7.5 (unreleased)
==================
- HTTPException's accepts a detail kwarg that may be used to pass additional
  details to the exception. You may now pass objects so long as they have a
  valid __str__ method. See https://github.com/Pylons/pyramid/pull/2951 
- Fix a reference cycle causing memory leaks in which the registry
  would keep a ``Configurator`` instance alive even after the configurator
  was discarded. Another fix was also added for the ``global_registries``
  object in which the registry was stored in a closure preventing it from
  being deallocated. See https://github.com/Pylons/pyramid/pull/2972
.. _changes_1.7.4:
1.7.4 (2017-01-24)
==================
CONTRIBUTORS.txt
@@ -278,3 +278,5 @@
- Jon Davidson, 2016/07/18
- Keith Yang, 2016/07/22
- Kirill Kuzminykh, 2017/03/01
pyramid/registry.py
@@ -257,7 +257,9 @@
    @reify
    def value(self):
        return self.func()
        result = self.func()
        del self.func
        return result
    def resolve(self):
        return self.value
pyramid/tests/test_integration.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import datetime
import gc
import locale
import os
import unittest
@@ -8,6 +9,7 @@
from pyramid.wsgi import wsgiapp
from pyramid.view import view_config
from pyramid.static import static_view
from pyramid.testing import skip_on
from pyramid.compat import (
    text_,
    url_quote,
@@ -741,3 +743,29 @@
    data = data.replace(b'\r', b'')
    data = data.replace(b'\n', b'')
    assert(body == data)
class MemoryLeaksTest(unittest.TestCase):
    def tearDown(self):
        import pyramid.config
        pyramid.config.global_registries.empty()
    def get_gc_count(self):
        last_collected = 0
        while True:
            collected = gc.collect()
            if collected == last_collected:
                break
            last_collected = collected
        return len(gc.get_objects())
    @skip_on('pypy')
    def test_memory_leaks(self):
        from pyramid.config import Configurator
        Configurator().make_wsgi_app()  # Initialize all global objects
        initial_count = self.get_gc_count()
        Configurator().make_wsgi_app()
        current_count = self.get_gc_count()
        self.assertEqual(current_count, initial_count)
pyramid/util.py
@@ -220,17 +220,20 @@
            self._order.remove(oid)
            self._order.append(oid)
            return
        ref = weakref.ref(item, lambda x: self.remove(item))
        ref = weakref.ref(item, lambda x: self._remove_by_id(oid))
        self._items[oid] = ref
        self._order.append(oid)
    def remove(self, item):
    def _remove_by_id(self, oid):
        """ Remove an item from the set."""
        oid = id(item)
        if oid in self._items:
            del self._items[oid]
            self._order.remove(oid)
    def remove(self, item):
        """ Remove an item from the set."""
        self._remove_by_id(id(item))
    def empty(self):
        """ Clear all objects from the set."""
        self._items = {}