Skip to content

Commit b586b0b

Browse files
committed
gh-160936: Clear all cached loggers unconditionally in _clear_cache
Skipping loggers that are not in loggerDict kept them in the set forever, and per-item discard left the set's table large after a bulk clear, so later setLevel() calls stayed slower than on main. Clear every tracked logger and empty the set instead. This also fixes a stale isEnabledFor cache on loggers that are not registered with the manager.
1 parent d60e39c commit b586b0b

3 files changed

Lines changed: 26 additions & 5 deletions

File tree

‎Lib/logging/__init__.py‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1475,10 +1475,9 @@ def _clear_cache(self):
14751475
with _lock:
14761476
# Only loggers that have populated a cache are visited, so
14771477
# setLevel() costs O(loggers with cached results), not O(loggers).
1478-
for logger in list(self._cached_loggers):
1479-
if self.loggerDict.get(logger.name) is logger:
1480-
logger._cache.clear()
1481-
self._cached_loggers.discard(logger)
1478+
for logger in self._cached_loggers:
1479+
logger._cache.clear()
1480+
self._cached_loggers.clear()
14821481
self.root._cache.clear()
14831482

14841483
#---------------------------------------------------------------------------

‎Lib/test/test_logging.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6398,6 +6398,25 @@ def test_pickling(self):
63986398
unpickled = pickle.loads(s)
63996399
self.assertIs(unpickled, logger)
64006400

6401+
def test_caching_unregistered_logger(self):
6402+
# A logger that is not in loggerDict must not keep a stale cache.
6403+
logger = logging.Logger("unregistered")
6404+
logger.setLevel(logging.DEBUG)
6405+
self.assertTrue(logger.isEnabledFor(logging.DEBUG))
6406+
logger.setLevel(logging.ERROR)
6407+
self.assertEqual(logger._cache, {})
6408+
self.assertFalse(logger.isEnabledFor(logging.DEBUG))
6409+
6410+
def test_clear_cache_empties_cached_loggers(self):
6411+
manager = logging.root.manager
6412+
registered = logging.getLogger("cached.registered")
6413+
unregistered = logging.Logger("cached.unregistered")
6414+
for logger in (registered, unregistered):
6415+
logger.isEnabledFor(logging.DEBUG)
6416+
self.assertIn(logger, manager._cached_loggers)
6417+
registered.setLevel(logging.INFO)
6418+
self.assertEqual(len(manager._cached_loggers), 0)
6419+
64016420
def test_caching(self):
64026421
root = self.root_logger
64036422
logger1 = logging.getLogger("abc")
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
Speed up :meth:`logging.Logger.setLevel` and :func:`logging.disable` when there are many loggers: only loggers that have cached results are now cleared, instead of every logger.
1+
Speed up :meth:`logging.Logger.setLevel` and :func:`logging.disable` when
2+
there are many loggers: only loggers that have cached results are now cleared,
3+
instead of every logger. This also clears the cache of loggers that are not
4+
registered with the manager, which could previously keep a stale result.

0 commit comments

Comments
 (0)