143143
144144
145145import abc
146+ import _imp
146147from annotationlib import Format , ForwardRef
147148from annotationlib import get_annotations # re-exported
148149import ast
@@ -925,7 +926,65 @@ def getabsfile(object, _filename=None):
925926
926927modulesbyfile = {}
927928_filesbymodname = {}
928- _modulesbyfile_snapshot = None
929+ # (registry version, filename map, per-module metadata, volatile metadata)
930+ _modulesbyfile_cache = (None , {}, {}, ())
931+ _module_cache_missing = object ()
932+
933+
934+ def _get_volatile_module_metadata (module ):
935+ """Return unwatchable module metadata used to validate the cache."""
936+ try :
937+ module_file = getattr (module , '__file__' )
938+ except AttributeError :
939+ return _module_cache_missing , _module_cache_missing
940+ if module_file is None :
941+ return None , _module_cache_missing
942+ return module_file , getattr (module , '__name__' )
943+
944+
945+ def _volatile_modules_unchanged (modules , volatile_modules ):
946+ for modname , module_file , module_name in volatile_modules :
947+ module = modules .get (modname , _module_cache_missing )
948+ if not ismodule (module ):
949+ return False
950+ current_file , current_name = _get_volatile_module_metadata (module )
951+ if current_file != module_file or current_name != module_name :
952+ return False
953+ return True
954+
955+
956+ def _module_cache_is_current (modules , version , cache ):
957+ if (modules is not sys .modules or
958+ version is None or version != cache [0 ]):
959+ return False
960+ volatile_modules = cache [3 ]
961+ if not volatile_modules :
962+ return True
963+ if not _volatile_modules_unchanged (modules , volatile_modules ):
964+ return False
965+ # Dynamic metadata may execute arbitrary Python code.
966+ return (modules is sys .modules and
967+ version == _imp ._get_module_registry_version (modules ))
968+
969+
970+ def _publish_module_cache (version , modules_by_file , files_by_module ,
971+ volatile_modules ):
972+ """Publish a consistent filename cache and update its legacy mirrors."""
973+ global _modulesbyfile_cache
974+
975+ # Keep these longstanding, externally visible dictionaries alive, but do
976+ # not use them as the authoritative cache: updating two dictionaries and a
977+ # version cannot be made atomic for concurrent getmodule() calls.
978+ modulesbyfile .clear ()
979+ modulesbyfile .update (modules_by_file )
980+ _filesbymodname .clear ()
981+ _filesbymodname .update (
982+ (modname , metadata [0 ])
983+ for modname , metadata in files_by_module .items ()
984+ )
985+ # This single assignment is the authoritative publication point.
986+ _modulesbyfile_cache = (
987+ version , modules_by_file , files_by_module , volatile_modules )
929988
930989
931990def _getframemodule (frame , _filename = None ):
@@ -937,17 +996,29 @@ def _getframemodule(frame, _filename=None):
937996 module = sys .modules .get (module_name )
938997 if not (ismodule (module ) and module .__dict__ is frame_globals ):
939998 return None
940- module_file = getattr (module , '__file__' , None )
999+ try :
1000+ if type (module ) is types .ModuleType :
1001+ module_file = module .__file__
1002+ else :
1003+ # A module subclass can supply dynamic metadata through
1004+ # descriptors. getattr() avoids a module-attribute specialization
1005+ # that may read the subclass's underlying module dict.
1006+ module_file = getattr (module , '__file__' )
1007+ except AttributeError :
1008+ return None
9411009 if module_file is None :
9421010 return None
1011+ frame_filename = (
1012+ frame .f_code .co_filename if _filename is None else _filename
1013+ )
9431014 try :
944- file = getabsfile (frame , _filename )
1015+ file = getabsfile (frame , frame_filename )
9451016 except (TypeError , FileNotFoundError ):
9461017 return None
947- if frame . f_code . co_filename == module_file :
1018+ if frame_filename == module_file :
9481019 return module
9491020 try :
950- module_file = getabsfile (module )
1021+ module_file = getabsfile (module , module_file )
9511022 except (TypeError , FileNotFoundError ):
9521023 return None
9531024 if file == module_file or file == os .path .realpath (module_file ):
@@ -970,45 +1041,172 @@ def getmodule(object, _filename=None):
9701041 if module is not None :
9711042 return module
9721043
973- # Try the filename to modulename cache
974- if _filename is not None and _filename in modulesbyfile :
975- return sys .modules .get (modulesbyfile [_filename ])
1044+ modules = sys .modules
1045+ version = _imp ._get_module_registry_version (modules )
1046+ cache = _modulesbyfile_cache
1047+ cache_is_current = _module_cache_is_current (modules , version , cache )
1048+ cached_modulesbyfile = cache [1 ]
1049+
1050+ # Try the filename to modulename cache. Recheck the version after reading
1051+ # the module so a concurrent registry mutation cannot make the result
1052+ # stale.
1053+ if (cache_is_current and _filename is not None and
1054+ _filename in cached_modulesbyfile ):
1055+ module = modules .get (cached_modulesbyfile [_filename ])
1056+ if _module_cache_is_current (
1057+ modules , _imp ._get_module_registry_version (modules ), cache ):
1058+ return module
1059+ cache_is_current = False
1060+
9761061 # Try the cache again with the absolute file name
9771062 try :
9781063 file = getabsfile (object , _filename )
9791064 except (TypeError , FileNotFoundError ):
9801065 return None
981- if file in modulesbyfile :
982- return sys .modules .get (modulesbyfile [file ])
983- # Update the filename to module name cache only when sys.modules is
984- # replaced or its module names have changed since the previous scan.
985- # Retaining module values in the snapshot would keep removed modules alive.
986- global _modulesbyfile_snapshot
987- modules = sys .modules
988- modules_id = id (modules )
989- try :
990- snapshot = (modules_id , tuple (modules ))
991- except RuntimeError :
992- # The mapping changed while its keys were being copied.
993- snapshot = None
994- if snapshot is None or snapshot != _modulesbyfile_snapshot :
995- # Copy sys.modules in order to cope with changes while iterating.
996- modules = modules .copy ()
997- snapshot = (modules_id , tuple (modules ))
998- for modname , module in modules .items ():
999- if ismodule (module ) and hasattr (module , '__file__' ):
1000- f = module .__file__
1001- if f == _filesbymodname .get (modname , None ):
1002- # Have already mapped this module, so skip it
1066+ if modules is not sys .modules :
1067+ # getabsfile() may execute user code through dynamic module metadata.
1068+ modules = sys .modules
1069+ cache_is_current = False
1070+ if not cache_is_current :
1071+ # getabsfile() can recurse through getsourcefile() and populate this
1072+ # cache. Avoid rebuilding it a second time in the outer call.
1073+ version = _imp ._get_module_registry_version (modules )
1074+ cache = _modulesbyfile_cache
1075+ cache_is_current = _module_cache_is_current (modules , version , cache )
1076+ cached_modulesbyfile = cache [1 ]
1077+ if cache_is_current and file in cached_modulesbyfile :
1078+ module = modules .get (cached_modulesbyfile [file ])
1079+ if _module_cache_is_current (
1080+ modules , _imp ._get_module_registry_version (modules ), cache ):
1081+ return module
1082+ cache_is_current = False
1083+
1084+ if cache_is_current :
1085+ # A stable cache miss is a negative-cache hit. Validate it after the
1086+ # lookup just as for a positive hit.
1087+ cache_is_current = _module_cache_is_current (
1088+ modules , _imp ._get_module_registry_version (modules ), cache )
1089+
1090+ # Update the filename to module name cache when the canonical module
1091+ # registry or relevant module metadata has changed. Replacement
1092+ # sys.modules mappings cannot be watched, so scan those on every miss.
1093+ if not cache_is_current :
1094+ # Retry once if a mutation overlaps the scan. This handles imports
1095+ # triggered by the first scan without spinning under sustained churn.
1096+ for _ in range (2 ):
1097+ # Copy sys.modules in order to cope with changes while iterating.
1098+ modules = sys .modules
1099+ version = _imp ._get_module_registry_version (modules )
1100+ modules_copy = modules .copy ()
1101+ new_modulesbyfile = {}
1102+ new_filesbymodname = {}
1103+ volatile_modules = []
1104+ cached_filesbymodname = _modulesbyfile_cache [2 ]
1105+ for modname , module in modules_copy .items ():
1106+ if ismodule (module ):
1107+ if type (module ) is types .ModuleType :
1108+ module_dict = module .__dict__
1109+ try :
1110+ f = module .__file__
1111+ except AttributeError :
1112+ if '__getattr__' in module_dict :
1113+ volatile_modules .append ((
1114+ modname ,
1115+ _module_cache_missing ,
1116+ _module_cache_missing ,
1117+ ))
1118+ continue
1119+ volatile = (
1120+ module_dict .get (
1121+ '__file__' , _module_cache_missing ) is not f
1122+ )
1123+ else :
1124+ # Module subclasses may resolve __file__ or __name__
1125+ # dynamically. Validate just these entries on cache
1126+ # hits instead of making every fallback rescan all of
1127+ # sys.modules.
1128+ module_dict = None
1129+ volatile = True
1130+ try :
1131+ f = getattr (module , '__file__' )
1132+ except AttributeError :
1133+ volatile_modules .append ((
1134+ modname ,
1135+ _module_cache_missing ,
1136+ _module_cache_missing ,
1137+ ))
1138+ continue
1139+ if f is None :
1140+ if volatile :
1141+ volatile_modules .append ((
1142+ modname , None , _module_cache_missing ))
1143+ continue
1144+ if module_dict is None :
1145+ module_name = getattr (module , '__name__' )
1146+ else :
1147+ module_name = module .__name__
1148+ if (module_dict is not None and
1149+ module_dict .get (
1150+ '__name__' , _module_cache_missing )
1151+ is not module_name ):
1152+ volatile = True
1153+ if volatile :
1154+ volatile_modules .append (
1155+ (modname , f , module_name ))
1156+ cached = cached_filesbymodname .get (modname )
1157+ if (isinstance (cached , tuple ) and len (cached ) == 4 and
1158+ f == cached [0 ] and module_name == cached [1 ]):
1159+ _ , _ , absfile , realpath = cached
1160+ else :
1161+ # Canonicalize the same metadata value that was
1162+ # validated above. Re-reading __file__ here can
1163+ # observe a different dynamic value.
1164+ absfile = getabsfile (module , f )
1165+ realpath = os .path .realpath (absfile )
1166+ new_filesbymodname [modname ] = (
1167+ f , module_name , absfile , realpath )
1168+ # Always map to the name the module knows itself by.
1169+ new_modulesbyfile [absfile ] = new_modulesbyfile [
1170+ realpath ] = module_name
1171+ module = None
1172+ found = file in new_modulesbyfile
1173+ if found :
1174+ module = modules .get (new_modulesbyfile [file ])
1175+ # Watcher callbacks run before their mutations. Validate both the
1176+ # watched generation and the small set of dynamic metadata before
1177+ # retaining the rebuilt caches.
1178+ current_version = _imp ._get_module_registry_version (modules )
1179+ if version is not None :
1180+ if (modules is not sys .modules or
1181+ version != current_version ):
10031182 continue
1004- _filesbymodname [modname ] = f
1005- f = getabsfile (module )
1006- # Always map to the name the module knows itself by
1007- modulesbyfile [f ] = modulesbyfile [
1008- os .path .realpath (f )] = module .__name__
1009- _modulesbyfile_snapshot = snapshot
1010- if file in modulesbyfile :
1011- return sys .modules .get (modulesbyfile [file ])
1183+ volatile_current = _volatile_modules_unchanged (
1184+ modules , volatile_modules )
1185+ current_version = _imp ._get_module_registry_version (modules )
1186+ if (modules is not sys .modules or
1187+ version != current_version or
1188+ not volatile_current ):
1189+ continue
1190+ _publish_module_cache (
1191+ version ,
1192+ new_modulesbyfile ,
1193+ new_filesbymodname ,
1194+ tuple (volatile_modules ),
1195+ )
1196+ if found :
1197+ return module
1198+ break
1199+ if modules is sys .modules :
1200+ # A replacement sys.modules mapping cannot be watched, but its
1201+ # just-scanned result is still useful for this call.
1202+ _publish_module_cache (
1203+ None , new_modulesbyfile , new_filesbymodname ,
1204+ tuple (volatile_modules ))
1205+ if found :
1206+ return module
1207+ break
1208+ # If both attempts overlap mutations, fall through rather than using
1209+ # a snapshot that could not be validated.
10121210 # Check the main module
10131211 main = sys .modules ['__main__' ]
10141212 if not hasattr (object , '__name__' ):
0 commit comments