mirror of
https://github.com/python/cpython.git
synced 2026-06-07 10:21:09 +00:00
gh-150818: Wire logger parent before publishing it in getLogger() (GH-150941)
This commit is contained in:
parent
0036565e81
commit
3835fca3f5
2 changed files with 47 additions and 5 deletions
|
|
@ -1377,9 +1377,10 @@ def getLogger(self, name):
|
|||
raise TypeError('A logger name must be a string')
|
||||
# Fast path: an already-registered, non-placeholder logger can be
|
||||
# returned without taking the lock. dict.get() is atomic under both
|
||||
# the GIL and free threading, and a Logger is fully initialised before
|
||||
# being inserted into loggerDict under the lock, so this never sees a
|
||||
# partially-constructed object.
|
||||
# the GIL and free threading. A Logger is inserted into loggerDict only
|
||||
# after it is fully wired up (parent/child references fixed) under the
|
||||
# lock, so the fast path never observes a logger whose parent is not yet
|
||||
# set.
|
||||
rv = self.loggerDict.get(name)
|
||||
if rv is not None and not isinstance(rv, PlaceHolder):
|
||||
return rv
|
||||
|
|
@ -1390,14 +1391,18 @@ def getLogger(self, name):
|
|||
ph = rv
|
||||
rv = (self.loggerClass or _loggerClass)(name)
|
||||
rv.manager = self
|
||||
self.loggerDict[name] = rv
|
||||
self._fixupChildren(ph, rv)
|
||||
self._fixupParents(rv)
|
||||
# Publish only after rv is fully wired: the fast path reads
|
||||
# loggerDict without the lock.
|
||||
self.loggerDict[name] = rv
|
||||
else:
|
||||
rv = (self.loggerClass or _loggerClass)(name)
|
||||
rv.manager = self
|
||||
self.loggerDict[name] = rv
|
||||
self._fixupParents(rv)
|
||||
# Publish only after rv is fully wired: the fast path reads
|
||||
# loggerDict without the lock.
|
||||
self.loggerDict[name] = rv
|
||||
return rv
|
||||
|
||||
def setLoggerClass(self, klass):
|
||||
|
|
|
|||
|
|
@ -4269,6 +4269,43 @@ def test_set_log_record_factory(self):
|
|||
man.setLogRecordFactory(expected)
|
||||
self.assertEqual(man.logRecordFactory, expected)
|
||||
|
||||
@threading_helper.requires_working_threading()
|
||||
def test_getLogger_fast_path_never_returns_unwired_logger(self):
|
||||
# getLogger()'s lock-free fast path returns a logger straight out of
|
||||
# loggerDict, so a logger must be published there only after
|
||||
# _fixupParents() has set its parent; otherwise a concurrent caller
|
||||
# observes it detached from the hierarchy (gh-150818 follow-up).
|
||||
manager = logging.Manager(logging.RootLogger(logging.WARNING))
|
||||
name = 'a.b.c'
|
||||
|
||||
paused = threading.Event()
|
||||
seen = []
|
||||
real_fixup = manager._fixupParents
|
||||
|
||||
# Pause the creating thread between publishing rv and wiring its
|
||||
# parent, then read loggerDict the way the fast path does and snapshot
|
||||
# the parent at that instant (rv is wired in place soon after).
|
||||
def fixup(alogger):
|
||||
paused.set()
|
||||
reader.join()
|
||||
real_fixup(alogger)
|
||||
|
||||
def read():
|
||||
paused.wait()
|
||||
rv = manager.loggerDict.get(name)
|
||||
if rv is not None and not isinstance(rv, logging.PlaceHolder):
|
||||
seen.append(rv.parent)
|
||||
|
||||
reader = threading.Thread(target=read)
|
||||
manager._fixupParents = fixup
|
||||
try:
|
||||
reader.start()
|
||||
manager.getLogger(name)
|
||||
finally:
|
||||
manager._fixupParents = real_fixup
|
||||
|
||||
self.assertNotIn(None, seen)
|
||||
|
||||
class ChildLoggerTest(BaseTest):
|
||||
def test_child_loggers(self):
|
||||
r = logging.getLogger()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue