2
2
from importlib import abc
3
3
from importlib import util
4
4
import sys
5
+ import time
6
+ import threading
5
7
import types
6
8
import unittest
7
9
@@ -40,6 +42,7 @@ class TestingImporter(abc.MetaPathFinder, abc.Loader):
40
42
module_name = 'lazy_loader_test'
41
43
mutated_name = 'changed'
42
44
loaded = None
45
+ load_count = 0
43
46
source_code = 'attr = 42; __name__ = {!r}' .format (mutated_name )
44
47
45
48
def find_spec (self , name , path , target = None ):
@@ -48,8 +51,10 @@ def find_spec(self, name, path, target=None):
48
51
return util .spec_from_loader (name , util .LazyLoader (self ))
49
52
50
53
def exec_module (self , module ):
54
+ time .sleep (0.01 ) # Simulate a slow load.
51
55
exec (self .source_code , module .__dict__ )
52
56
self .loaded = module
57
+ self .load_count += 1
53
58
54
59
55
60
class LazyLoaderTests (unittest .TestCase ):
@@ -59,8 +64,9 @@ def test_init(self):
59
64
# Classes that don't define exec_module() trigger TypeError.
60
65
util .LazyLoader (object )
61
66
62
- def new_module (self , source_code = None ):
63
- loader = TestingImporter ()
67
+ def new_module (self , source_code = None , loader = None ):
68
+ if loader is None :
69
+ loader = TestingImporter ()
64
70
if source_code is not None :
65
71
loader .source_code = source_code
66
72
spec = util .spec_from_loader (TestingImporter .module_name ,
@@ -140,6 +146,36 @@ def test_module_already_in_sys(self):
140
146
# Force the load; just care that no exception is raised.
141
147
module .__name__
142
148
149
+ def test_module_load_race (self ):
150
+ with test_util .uncache (TestingImporter .module_name ):
151
+ loader = TestingImporter ()
152
+ module = self .new_module (loader = loader )
153
+ assert loader .load_count == 0
154
+
155
+ class RaisingThread (threading .Thread ):
156
+ exc = None
157
+ def run (self ):
158
+ try :
159
+ super ().run ()
160
+ except Exception as exc :
161
+ self .exc = exc
162
+
163
+ def access_module ():
164
+ return module .attr
165
+
166
+ threads = []
167
+ for _ in range (2 ):
168
+ threads .append (thread := RaisingThread (target = access_module ))
169
+ thread .start ()
170
+
171
+ # Races could cause errors
172
+ for thread in threads :
173
+ thread .join ()
174
+ assert thread .exc is None
175
+
176
+ # Or multiple load attempts
177
+ assert loader .load_count == 1
178
+
143
179
144
180
if __name__ == '__main__' :
145
181
unittest .main ()
0 commit comments