From 3deff633c0e919b422a7a84ed603ed82eb8c869c Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 30 Mar 2019 10:51:39 +0200 Subject: [PATCH] Add the cachesreg module. --- Lib/_strptime.py | 2 + Lib/cachesreg.py | 8 +++ Lib/ctypes/__init__.py | 3 ++ Lib/distutils/dir_util.py | 2 + Lib/doctest.py | 6 +++ Lib/filecmp.py | 3 ++ Lib/functools.py | 2 + Lib/linecache.py | 2 + Lib/mimetypes.py | 2 + Lib/re.py | 3 ++ Lib/struct.py | 3 ++ Lib/test/libregrtest/refleak.py | 89 +-------------------------------- Lib/test/test_typing.py | 3 +- Lib/typing.py | 7 +++ Lib/urllib/parse.py | 2 + Lib/urllib/request.py | 3 ++ Lib/warnings.py | 10 ++++ Lib/xml/etree/ElementPath.py | 2 + 18 files changed, 63 insertions(+), 89 deletions(-) create mode 100644 Lib/cachesreg.py diff --git a/Lib/_strptime.py b/Lib/_strptime.py index f4f3c0b80c1d05..401a2857bdd0ad 100644 --- a/Lib/_strptime.py +++ b/Lib/_strptime.py @@ -10,6 +10,7 @@ strptime -- Calculates the time struct represented by the passed-in string """ +import cachesreg import time import locale import calendar @@ -268,6 +269,7 @@ def compile(self, format): _TimeRE_cache = TimeRE() _CACHE_MAX_SIZE = 5 # Max number of regexes stored in _regex_cache _regex_cache = {} +cachesreg.register(_regex_cache.clear) def _calc_julian_from_U_or_W(year, week_of_year, day_of_week, week_starts_Mon): """Calculate the Julian day based on the year, week of the year, and day of diff --git a/Lib/cachesreg.py b/Lib/cachesreg.py new file mode 100644 index 00000000000000..f31ce7d54de6d6 --- /dev/null +++ b/Lib/cachesreg.py @@ -0,0 +1,8 @@ +_clearers = [] + +def register(callback): + _clearers.append(callback) + +def clear_caches(): + for clear in _clearers[::-1]: + clear() diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index 4107db3e3972d7..8dae992833487c 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -12,6 +12,7 @@ from _ctypes import ArgumentError from struct import calcsize as _calcsize +import cachesreg as _cachesreg if __version__ != _ctypes_version: raise Exception("Version number mismatch", __version__, _ctypes_version) @@ -267,6 +268,8 @@ def _reset_cache(): POINTER(c_char).from_param = c_char_p.from_param _pointer_type_cache[None] = c_void_p +_cachesreg.register(_reset_cache) + def create_unicode_buffer(init, size=None): """create_unicode_buffer(aString) -> character array create_unicode_buffer(anInteger) -> character array diff --git a/Lib/distutils/dir_util.py b/Lib/distutils/dir_util.py index d5cd8e3e24f46a..303d7eb28d252f 100644 --- a/Lib/distutils/dir_util.py +++ b/Lib/distutils/dir_util.py @@ -3,6 +3,7 @@ Utility functions for manipulating directories and directory trees.""" import os +import cachesreg as _cachesreg import errno from distutils.errors import DistutilsFileError, DistutilsInternalError from distutils import log @@ -10,6 +11,7 @@ # cache for by mkpath() -- in addition to cheapening redundant calls, # eliminates redundant "creating /foo/bar/baz" messages in dry-run mode _path_created = {} +_cachesreg.register(_path_created.clear) # I don't use os.makedirs because a) it's new to Python 1.5.2, and # b) it blows up if the directory already exists (I want to silently diff --git a/Lib/doctest.py b/Lib/doctest.py index 79d91a040c2eee..972d3ef023b735 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -93,6 +93,7 @@ def _test(): ] import __future__ +import cachesreg import difflib import inspect import linecache @@ -1853,6 +1854,11 @@ def report_failure(self, out, test, example, got): # class, updated by testmod. master = None +def _reset_master(): + master = None + +cachesreg.register(_reset_master) + def testmod(m=None, name=None, globs=None, verbose=None, report=True, optionflags=0, extraglobs=None, raise_on_error=False, exclude_empty=False): diff --git a/Lib/filecmp.py b/Lib/filecmp.py index e5ad8397e4c539..81801e03f6cdfb 100644 --- a/Lib/filecmp.py +++ b/Lib/filecmp.py @@ -10,6 +10,7 @@ """ +import cachesreg import os import stat from itertools import filterfalse @@ -26,6 +27,8 @@ def clear_cache(): """Clear the filecmp cache.""" _cache.clear() +cachesreg.register(clear_cache) + def cmp(f1, f2, shallow=True): """Compare two files. diff --git a/Lib/functools.py b/Lib/functools.py index 426653f13f6d1e..e9e162501f2cdd 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -14,6 +14,7 @@ 'partialmethod', 'singledispatch', 'singledispatchmethod'] from abc import get_cache_token +import cachesreg from collections import namedtuple # import types, weakref # Deferred to single_dispatch() from reprlib import recursive_repr @@ -630,6 +631,7 @@ def cache_clear(): wrapper.cache_info = cache_info wrapper.cache_clear = cache_clear + cachesreg.register(cache_clear) return wrapper try: diff --git a/Lib/linecache.py b/Lib/linecache.py index 3afcce1f0a1456..4c24119e6ab7fa 100644 --- a/Lib/linecache.py +++ b/Lib/linecache.py @@ -5,6 +5,7 @@ that name. """ +import cachesreg import functools import sys import os @@ -33,6 +34,7 @@ def clearcache(): global cache cache = {} +cachesreg.register(clearcache) def getlines(filename, module_globals=None): """Get the lines for a Python source file from the cache. diff --git a/Lib/mimetypes.py b/Lib/mimetypes.py index 8861b75362dbd7..e8f45029612323 100644 --- a/Lib/mimetypes.py +++ b/Lib/mimetypes.py @@ -23,6 +23,7 @@ read_mime_types(file) -- parse one file, return a dictionary or None """ +import cachesreg import os import sys import posixpath @@ -548,6 +549,7 @@ def _default_mime_types(): } +cachesreg.register(_default_mime_types) _default_mime_types() diff --git a/Lib/re.py b/Lib/re.py index 68d62dc2a93b05..ffd835a7112bfc 100644 --- a/Lib/re.py +++ b/Lib/re.py @@ -119,6 +119,7 @@ """ +import cachesreg import enum import sre_compile import sre_parse @@ -238,6 +239,8 @@ def purge(): _cache.clear() _compile_repl.cache_clear() +cachesreg.register(purge) + def template(pattern, flags=0): "Compile a template pattern, returning a Pattern object" return _compile(pattern, flags|T) diff --git a/Lib/struct.py b/Lib/struct.py index d6bba588636498..7dfe55a05c3695 100644 --- a/Lib/struct.py +++ b/Lib/struct.py @@ -13,3 +13,6 @@ from _struct import * from _struct import _clearcache from _struct import __doc__ +import cachesreg + +cachesreg.register(_clearcache) diff --git a/Lib/test/libregrtest/refleak.py b/Lib/test/libregrtest/refleak.py index 6724488fcfb088..9d0a35a6af26f8 100644 --- a/Lib/test/libregrtest/refleak.py +++ b/Lib/test/libregrtest/refleak.py @@ -158,12 +158,7 @@ def dash_R_cleanup(fs, ps, pic, zdc, abcs): def clear_caches(): - import gc - - # Clear the warnings registry, so they can be displayed again - for mod in sys.modules.values(): - if hasattr(mod, '__warningregistry__'): - del mod.__warningregistry__ + import gc, cachesreg # Flush standard output, so that buffered data is sent to the OS and # associated Python objects are reclaimed. @@ -171,87 +166,7 @@ def clear_caches(): if stream is not None: stream.flush() - # Clear assorted module caches. - # Don't worry about resetting the cache if the module is not loaded - try: - distutils_dir_util = sys.modules['distutils.dir_util'] - except KeyError: - pass - else: - distutils_dir_util._path_created.clear() - re.purge() - - try: - _strptime = sys.modules['_strptime'] - except KeyError: - pass - else: - _strptime._regex_cache.clear() - - try: - urllib_parse = sys.modules['urllib.parse'] - except KeyError: - pass - else: - urllib_parse.clear_cache() - - try: - urllib_request = sys.modules['urllib.request'] - except KeyError: - pass - else: - urllib_request.urlcleanup() - - try: - linecache = sys.modules['linecache'] - except KeyError: - pass - else: - linecache.clearcache() - - try: - mimetypes = sys.modules['mimetypes'] - except KeyError: - pass - else: - mimetypes._default_mime_types() - - try: - filecmp = sys.modules['filecmp'] - except KeyError: - pass - else: - filecmp._cache.clear() - - try: - struct = sys.modules['struct'] - except KeyError: - pass - else: - struct._clearcache() - - try: - doctest = sys.modules['doctest'] - except KeyError: - pass - else: - doctest.master = None - - try: - ctypes = sys.modules['ctypes'] - except KeyError: - pass - else: - ctypes._reset_cache() - - try: - typing = sys.modules['typing'] - except KeyError: - pass - else: - for f in typing._cleanups: - f() - + cachesreg.clear_caches() gc.collect() diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 0d66ebbd18456e..c5322527c3c19f 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -45,8 +45,7 @@ def assertNotIsSubclass(self, cls, class_or_tuple, msg=None): raise self.failureException(message) def clear_caches(self): - for f in typing._cleanups: - f() + typing._clear_caches() class Employee: diff --git a/Lib/typing.py b/Lib/typing.py index 530d4633fe4c22..2a62cdf7866d17 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -19,6 +19,7 @@ """ from abc import abstractmethod, abstractproperty +import cachesreg import collections import collections.abc import contextlib @@ -233,6 +234,12 @@ def _remove_dups_flatten(parameters): _cleanups = [] +def _clear_caches(): + for f in _cleanups: + f() + +cachesreg.register(_clear_caches) + def _tp_cache(func): """Internal wrapper caching __getitem__ of generic types with a fallback to diff --git a/Lib/urllib/parse.py b/Lib/urllib/parse.py index 8b6c9b10609152..bc752659b2ae56 100644 --- a/Lib/urllib/parse.py +++ b/Lib/urllib/parse.py @@ -27,6 +27,7 @@ test_urlparse.py provides a good indicator of parsing behavior. """ +import cachesreg import re import sys import collections @@ -86,6 +87,7 @@ def clear_cache(): _parse_cache.clear() _safe_quoters.clear() +cachesreg.register(clear_cache) # Helpers for bytes handling # For 3.2, we deliberately require applications that diff --git a/Lib/urllib/request.py b/Lib/urllib/request.py index df2ff06f0fc9a2..73d6db2bb2f0a8 100644 --- a/Lib/urllib/request.py +++ b/Lib/urllib/request.py @@ -83,6 +83,7 @@ import base64 import bisect +import cachesreg import email import hashlib import http.client @@ -302,6 +303,8 @@ def urlcleanup(): if _opener: _opener = None +cachesreg.register(urlcleanup) + # copied from cookielib.py _cut_port_re = re.compile(r":\d+$", re.ASCII) def request_host(request): diff --git a/Lib/warnings.py b/Lib/warnings.py index 00f740ca3a95ba..2a62213ebf4714 100644 --- a/Lib/warnings.py +++ b/Lib/warnings.py @@ -1,6 +1,7 @@ """Python part of the warnings subsystem.""" import sys +import cachesreg __all__ = ["warn", "warn_explicit", "showwarning", @@ -538,6 +539,15 @@ def _filters_mutated(): _warnings_defaults = False + +def _clear_warning_registry(): + # Clear the warnings registry, so they can be displayed again + for mod in sys.modules.values(): + if hasattr(mod, '__warningregistry__'): + del mod.__warningregistry__ + +cachesreg.register(_clear_warning_registry) + # Module initialization _processoptions(sys.warnoptions) if not _warnings_defaults: diff --git a/Lib/xml/etree/ElementPath.py b/Lib/xml/etree/ElementPath.py index ef32917b14d41e..cec7a7326d4644 100644 --- a/Lib/xml/etree/ElementPath.py +++ b/Lib/xml/etree/ElementPath.py @@ -56,6 +56,7 @@ # you, if needed. ## +import cachesreg as _cachesreg import re xpath_tokenizer_re = re.compile( @@ -251,6 +252,7 @@ def select(context, result): } _cache = {} +_cachesreg.register(_cache.clear) class _SelectorContext: parent_map = None