| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1377,9 +1377,10 @@ def getLogger(self, name): | |||
| 1377 | 1377 | raise TypeError('A logger name must be a string') | |
| 1378 | 1378 | # Fast path: an already-registered, non-placeholder logger can be | |
| 1379 | 1379 | # returned without taking the lock. dict.get() is atomic under both | |
| 1380 | - # the GIL and free threading, and a Logger is fully initialised before | ||
| 1381 | - # being inserted into loggerDict under the lock, so this never sees a | ||
| 1382 | - # partially-constructed object. | ||
| 1380 | + # the GIL and free threading. A Logger is inserted into loggerDict only | ||
| 1381 | + # after it is fully wired up (parent/child references fixed) under the | ||
| 1382 | + # lock, so the fast path never observes a logger whose parent is not yet | ||
| 1383 | + # set. | ||
| 1383 | 1384 | rv = self.loggerDict.get(name) | |
| 1384 | 1385 | if rv is not None and not isinstance(rv, PlaceHolder): | |
| 1385 | 1386 | return rv | |
@@ -1390,14 +1391,18 @@ def getLogger(self, name): | |||
| 1390 | 1391 | ph = rv | |
| 1391 | 1392 | rv = (self.loggerClass or _loggerClass)(name) | |
| 1392 | 1393 | rv.manager = self | |
| 1393 | - self.loggerDict[name] = rv | ||
| 1394 | 1394 | self._fixupChildren(ph, rv) | |
| 1395 | 1395 | self._fixupParents(rv) | |
| 1396 | + # Publish only after rv is fully wired: the fast path reads | ||
| 1397 | + # loggerDict without the lock. | ||
| 1398 | + self.loggerDict[name] = rv | ||
| 1396 | 1399 | else: | |
| 1397 | 1400 | rv = (self.loggerClass or _loggerClass)(name) | |
| 1398 | 1401 | rv.manager = self | |
| 1399 | - self.loggerDict[name] = rv | ||
| 1400 | 1402 | self._fixupParents(rv) | |
| 1403 | + # Publish only after rv is fully wired: the fast path reads | ||
| 1404 | + # loggerDict without the lock. | ||
| 1405 | + self.loggerDict[name] = rv | ||
| 1401 | 1406 | return rv | |
| 1402 | 1407 | ||
| 1403 | 1408 | def setLoggerClass(self, klass): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4269,6 +4269,43 @@ def test_set_log_record_factory(self): | |||
| 4269 | 4269 | man.setLogRecordFactory(expected) | |
| 4270 | 4270 | self.assertEqual(man.logRecordFactory, expected) | |
| 4271 | 4271 | ||
| 4272 | + @threading_helper.requires_working_threading() | ||
| 4273 | + def test_getLogger_fast_path_never_returns_unwired_logger(self): | ||
| 4274 | + # getLogger()'s lock-free fast path returns a logger straight out of | ||
| 4275 | + # loggerDict, so a logger must be published there only after | ||
| 4276 | + # _fixupParents() has set its parent; otherwise a concurrent caller | ||
| 4277 | + # observes it detached from the hierarchy (gh-150818 follow-up). | ||
| 4278 | + manager = logging.Manager(logging.RootLogger(logging.WARNING)) | ||
| 4279 | + name = 'a.b.c' | ||
| 4280 | + | ||
| 4281 | + paused = threading.Event() | ||
| 4282 | + seen = [] | ||
| 4283 | + real_fixup = manager._fixupParents | ||
| 4284 | + | ||
| 4285 | + # Pause the creating thread between publishing rv and wiring its | ||
| 4286 | + # parent, then read loggerDict the way the fast path does and snapshot | ||
| 4287 | + # the parent at that instant (rv is wired in place soon after). | ||
| 4288 | + def fixup(alogger): | ||
| 4289 | + paused.set() | ||
| 4290 | + reader.join() | ||
| 4291 | + real_fixup(alogger) | ||
| 4292 | + | ||
| 4293 | + def read(): | ||
| 4294 | + paused.wait() | ||
| 4295 | + rv = manager.loggerDict.get(name) | ||
| 4296 | + if rv is not None and not isinstance(rv, logging.PlaceHolder): | ||
| 4297 | + seen.append(rv.parent) | ||
| 4298 | + | ||
| 4299 | + reader = threading.Thread(target=read) | ||
| 4300 | + manager._fixupParents = fixup | ||
| 4301 | + try: | ||
| 4302 | + reader.start() | ||
| 4303 | + manager.getLogger(name) | ||
| 4304 | + finally: | ||
| 4305 | + manager._fixupParents = real_fixup | ||
| 4306 | + | ||
| 4307 | + self.assertNotIn(None, seen) | ||
| 4308 | + | ||
| 4272 | 4309 | class ChildLoggerTest(BaseTest): | |
| 4273 | 4310 | def test_child_loggers(self): | |
| 4274 | 4311 | r = logging.getLogger() | |
| Back | FazBrowse Home | New Git URL |
0 commit comments