Преглед на файлове

Get rid of a vector for memory leaks by avoiding the stupidity of the logging module's global registry.

Chris McDonough преди 18 години
родител
ревизия
3fcccb8464
променени са 2 файла, в които са добавени 33 реда и са изтрити 37 реда
  1. 32 5
      src/supervisor/loggers.py
  2. 1 32
      src/supervisor/tests/test_loggers.py

+ 32 - 5
src/supervisor/loggers.py

@@ -39,18 +39,29 @@ class FileHandler(logging.StreamHandler):
 
     Re-opening should be used instead of the 'rollover' feature of
     the FileHandler from the standard library's logging package.
+
+    
     """
 
     def __init__(self, filename, mode="a"):
-        logging.StreamHandler.__init__(self, open(filename, mode))
+        # we purposely *do not* call logging.StreamHandler's __init__
+        # here because it attempts to register the handler in its
+        # global handler list, which is bad because a) they tend to
+        # leak at the end of tests b) if we don't play by this scheme,
+        # making sure to call all the right super methods, it will
+        # lead to a memory leak and c) the initializer sets an rlock
+        # and the rest of the logging framework does thread locking
+        # using it, and we don't need that at all because we're
+        # completely single threaded.
+        self.stream = open(filename, mode)
         self.baseFilename = filename
+        self.formatter = None
+        self.level = logging.NOTSET
         self.mode = mode
+        self.filters = []
+        self.lock = None
 
     def close(self):
-        # logging module can be none at shutdown sys.exithook time (sigh),
-        # see my rant in test_loggers.py
-        if logging is not None: 
-            logging.StreamHandler(self).close()
         self.stream.close()
 
     def reopen(self):
@@ -80,6 +91,22 @@ class RawFileHandler(RawHandler, FileHandler):
     pass
 
 class RawStreamHandler(RawHandler, logging.StreamHandler):
+    def __init__(self, strm=None):
+        # we purposely *do not* call logging.StreamHandler's __init__
+        # here because it attempts to register the handler in its
+        # global handler list, which is bad because a) they tend to
+        # leak at the end of tests b) if we don't play by this scheme,
+        # making sure to call all the right super methods, it will
+        # lead to a memory leak and c) the initializer sets an rlock
+        # and the rest of the logging framework does thread locking
+        # using it, and we don't need that at all because we're
+        # completely single threaded.
+        self.stream = strm
+        self.formatter = None
+        self.level = logging.NOTSET
+        self.filters = []
+        self.lock = None
+        
     def remove(self):
         pass
 

+ 1 - 32
src/supervisor/tests/test_loggers.py

@@ -8,7 +8,6 @@ import logging
 from supervisor.tests.base import DummyStream
 
 class HandlerTests:
-    handler = None
     def setUp(self):
         self.basedir = tempfile.mkdtemp()
         self.filename = os.path.join(self.basedir, 'thelog')
@@ -18,40 +17,10 @@ class HandlerTests:
             shutil.rmtree(self.basedir)
         except OSError:
             pass
-        if self.handler:
-            # Behold, the logging module.  Since it registers an
-            # exitfunc at import time that tries to call close methods
-            # on handlers that are registered in a (not just one, but
-            # *two*, and private!) module globals at handler
-            # construction time, we have to clean up here so it doesnt
-            # spew tracebacks to stderr at shutdown (at least for 2.4
-            # and 2.5).  We can't prevent it the exit handler it
-            # registers from shitting itself at shutdown by using the
-            # logging API because when its exitfunc is run, at least
-            # in my test rig, it's run after sys.modules have been
-            # cleared, so there's no way to do anything meaningful in
-            # the handlers' close methods.  I am consoled only by the
-            # fact that this a complete waste of time for everyone who
-            # uses the logging module and not just me.  There is *no
-            # fucking way* that importing a stdlib module should have
-            # the side effect of registering an exitfunc.  Can you
-            # tell that this annoys me?
-            if hasattr(logging, '_handlerList'): # 2.3.5 doesnt have it
-                hl = []
-                for handler in logging._handlerList:
-                    if handler is not self.handler:
-                        hl.append(handler)
-                logging._handlerList = hl
-            try:
-                del logging._handlers[self.handler]
-            except KeyError:
-                pass
-            self.handler = None
 
     def _makeOne(self, *arg, **kw):
         klass = self._getTargetClass()
-        self.handler = klass(*arg, **kw)
-        return self.handler
+        return klass(*arg, **kw)
 
     def _makeLogRecord(self, msg):
         record = logging.LogRecord('name',