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:
Samuel Merritt 2013-12-19 10:53:04 -08:00
parent ace2aa33b1
commit 0ff39c14c3
2 changed files with 47 additions and 0 deletions
swift/common
test/unit/common

@ -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'):