Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit5f85b44

Browse files
authored
pythonGH-106176,pythonGH-104702: Fix reference leak when importing across multiple threads (python#108497)
1 parentc780698 commit5f85b44

File tree

2 files changed

+107
-12
lines changed

2 files changed

+107
-12
lines changed

‎Lib/importlib/_bootstrap.py‎

Lines changed: 103 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,18 +51,106 @@ def _new_module(name):
5151

5252
# Module-level locking ########################################################
5353

54-
# A dict mapping module names to weakrefs of _ModuleLock instances
55-
# Dictionary protected by the global import lock
54+
# For a list that can have a weakref to it.
55+
class_List(list):
56+
pass
57+
58+
59+
# Copied from weakref.py with some simplifications and modifications unique to
60+
# bootstrapping importlib. Many methods were simply deleting for simplicity, so if they
61+
# are needed in the future they may work if simply copied back in.
62+
class_WeakValueDictionary:
63+
64+
def__init__(self):
65+
self_weakref=_weakref.ref(self)
66+
67+
# Inlined to avoid issues with inheriting from _weakref.ref before _weakref is
68+
# set by _setup(). Since there's only one instance of this class, this is
69+
# not expensive.
70+
classKeyedRef(_weakref.ref):
71+
72+
__slots__="key",
73+
74+
def__new__(type,ob,key):
75+
self=super().__new__(type,ob,type.remove)
76+
self.key=key
77+
returnself
78+
79+
def__init__(self,ob,key):
80+
super().__init__(ob,self.remove)
81+
82+
@staticmethod
83+
defremove(wr):
84+
nonlocalself_weakref
85+
86+
self=self_weakref()
87+
ifselfisnotNone:
88+
ifself._iterating:
89+
self._pending_removals.append(wr.key)
90+
else:
91+
_weakref._remove_dead_weakref(self.data,wr.key)
92+
93+
self._KeyedRef=KeyedRef
94+
self.clear()
95+
96+
defclear(self):
97+
self._pending_removals= []
98+
self._iterating=set()
99+
self.data= {}
100+
101+
def_commit_removals(self):
102+
pop=self._pending_removals.pop
103+
d=self.data
104+
whileTrue:
105+
try:
106+
key=pop()
107+
exceptIndexError:
108+
return
109+
_weakref._remove_dead_weakref(d,key)
110+
111+
defget(self,key,default=None):
112+
ifself._pending_removals:
113+
self._commit_removals()
114+
try:
115+
wr=self.data[key]
116+
exceptKeyError:
117+
returndefault
118+
else:
119+
if (o:=wr())isNone:
120+
returndefault
121+
else:
122+
returno
123+
124+
defsetdefault(self,key,default=None):
125+
try:
126+
o=self.data[key]()
127+
exceptKeyError:
128+
o=None
129+
ifoisNone:
130+
ifself._pending_removals:
131+
self._commit_removals()
132+
self.data[key]=self._KeyedRef(default,key)
133+
returndefault
134+
else:
135+
returno
136+
137+
138+
# A dict mapping module names to weakrefs of _ModuleLock instances.
139+
# Dictionary protected by the global import lock.
56140
_module_locks= {}
57141

58-
# A dict mapping thread IDs to lists of _ModuleLock instances. This maps a
59-
# thread to the module locks it is blocking on acquiring. The values are
60-
# lists because a single thread could perform a re-entrant import and be "in
61-
# the process" of blocking on locks for more than one module. A thread can
62-
# be "in the process" because a thread cannot actually block on acquiring
63-
# more than one lock but it can have set up bookkeeping that reflects that
64-
# it intends to block on acquiring more than one lock.
65-
_blocking_on= {}
142+
# A dict mapping thread IDs to weakref'ed lists of _ModuleLock instances.
143+
# This maps a thread to the module locks it is blocking on acquiring. The
144+
# values are lists because a single thread could perform a re-entrant import
145+
# and be "in the process" of blocking on locks for more than one module. A
146+
# thread can be "in the process" because a thread cannot actually block on
147+
# acquiring more than one lock but it can have set up bookkeeping that reflects
148+
# that it intends to block on acquiring more than one lock.
149+
#
150+
# The dictionary uses a WeakValueDictionary to avoid keeping unnecessary
151+
# lists around, regardless of GC runs. This way there's no memory leak if
152+
# the list is no longer needed (GH-106176).
153+
_blocking_on=None
66154

67155

68156
class_BlockingOnManager:
@@ -79,7 +167,7 @@ def __enter__(self):
79167
# re-entrant (i.e., a single thread may take it more than once) so it
80168
# wouldn't help us be correct in the face of re-entrancy either.
81169

82-
self.blocked_on=_blocking_on.setdefault(self.thread_id,[])
170+
self.blocked_on=_blocking_on.setdefault(self.thread_id,_List())
83171
self.blocked_on.append(self.lock)
84172

85173
def__exit__(self,*args,**kwargs):
@@ -1409,7 +1497,7 @@ def _setup(sys_module, _imp_module):
14091497
modules, those two modules must be explicitly passed in.
14101498
14111499
"""
1412-
global_imp,sys
1500+
global_imp,sys,_blocking_on
14131501
_imp=_imp_module
14141502
sys=sys_module
14151503

@@ -1437,6 +1525,9 @@ def _setup(sys_module, _imp_module):
14371525
builtin_module=sys.modules[builtin_name]
14381526
setattr(self_module,builtin_name,builtin_module)
14391527

1528+
# Instantiation requires _weakref to have been set.
1529+
_blocking_on=_WeakValueDictionary()
1530+
14401531

14411532
def_install(sys_module,_imp_module):
14421533
"""Install importers for builtin and frozen modules"""
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Use a ``WeakValueDictionary`` to track the lists containing the modules each
2+
thread is currently importing. This helps avoid a reference leak from
3+
keeping the list around longer than necessary. Weakrefs are used as GC can't
4+
interrupt the cleanup.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp