Fix fd leak from get_logger() in python 2.6
Calling get_logger({}) instantiates a logging.handlers.SyslogHandler, which opens and keeps a socket around (either /dev/log or UDP or whatever; not important). Under Python 2.6, all logging handlers instantiated anywhere at all will live for the entire lifetime of the program; they get stored in logging._handlerList and logging._handlers. Python 2.7 is very similar, but uses weakrefs instead of strong references in those module-level variables, so logging handlers can actually get cleaned up prior to program exit. The net effect is that any program that calls get_logger() more than a fixed number of times will leak file descriptors under Python 2.6. This commit throws encapsulation out the window and, under 2.6 only, replaces strong references with weakrefs in logging._handlerList and logging._handlers, thus avoiding the leak. Change-Id: I5dc0d1619c5a4500f892b898afd9e3668ec0ee7c
This commit is contained in:
parent
ace2aa33b1
commit
0ff39c14c3
@ -27,6 +27,7 @@ import threading as stdlib_threading
|
||||
import time
|
||||
import uuid
|
||||
import functools
|
||||
import weakref
|
||||
from hashlib import md5, sha1
|
||||
from random import random, shuffle
|
||||
from urllib import quote as _quote
|
||||
@ -772,6 +773,22 @@ def timing_stats(**dec_kwargs):
|
||||
return decorating_func
|
||||
|
||||
|
||||
class LoggingHandlerWeakRef(weakref.ref):
|
||||
"""
|
||||
Like a weak reference, but passes through a couple methods that logging
|
||||
handlers need.
|
||||
"""
|
||||
def close(self):
|
||||
referent = self()
|
||||
if referent:
|
||||
referent.close()
|
||||
|
||||
def flush(self):
|
||||
referent = self()
|
||||
if referent:
|
||||
referent.flush()
|
||||
|
||||
|
||||
# double inheritance to support property with setter
|
||||
class LogAdapter(logging.LoggerAdapter, object):
|
||||
"""
|
||||
@ -1055,6 +1072,32 @@ def get_logger(conf, name=None, log_to_console=False, log_route=None,
|
||||
print >>sys.stderr, 'Error calling custom handler [%s]' % hook
|
||||
except ValueError:
|
||||
print >>sys.stderr, 'Invalid custom handler format [%s]' % hook
|
||||
|
||||
# Python 2.6 has the undesirable property of keeping references to all log
|
||||
# handlers around forever in logging._handlers and logging._handlerList.
|
||||
# Combine that with handlers that keep file descriptors, and you get an fd
|
||||
# leak.
|
||||
#
|
||||
# And no, we can't share handlers; a SyslogHandler has a socket, and if
|
||||
# two greenthreads end up logging at the same time, you could get message
|
||||
# overlap that garbles the logs and makes eventlet complain.
|
||||
#
|
||||
# Python 2.7 uses weakrefs to avoid the leak, so let's do that too.
|
||||
if sys.version_info[0] == 2 and sys.version_info[1] <= 6:
|
||||
try:
|
||||
logging._acquireLock() # some thread-safety thing
|
||||
for handler in adapted_logger.logger.handlers:
|
||||
if handler in logging._handlers:
|
||||
wr = LoggingHandlerWeakRef(handler)
|
||||
del logging._handlers[handler]
|
||||
logging._handlers[wr] = 1
|
||||
for i, handler_ref in enumerate(logging._handlerList):
|
||||
if handler_ref is handler:
|
||||
logging._handlerList[i] = LoggingHandlerWeakRef(
|
||||
handler)
|
||||
finally:
|
||||
logging._releaseLock()
|
||||
|
||||
return adapted_logger
|
||||
|
||||
|
||||
|
@ -117,6 +117,10 @@ class MockSys():
|
||||
self.stdio_fds = [self.stdin.fileno(), self.stdout.fileno(),
|
||||
self.stderr.fileno()]
|
||||
|
||||
@property
|
||||
def version_info(self):
|
||||
return sys.version_info
|
||||
|
||||
|
||||
def reset_loggers():
|
||||
if hasattr(utils.get_logger, 'handler4logger'):
|
||||
|
Loading…
x
Reference in New Issue
Block a user