77import inspect
88import os
99import pkgutil
10+ import queue
1011
1112
1213ModuleProperties = NamedTuple ('ModuleProperties' , [
@@ -85,7 +86,10 @@ class ModuleInspect:
8586
8687 Reuse the process for multiple modules for efficiency. However, if there is an
8788 error, retry using a fresh process to avoid cross-contamination of state between
88- modules (important for certain 3rd-party packages).
89+ modules.
90+
91+ We use a separate process to isolate us from many side effects. For example, the
92+ import of a module may kill the current process, and we want to recover from that.
8993
9094 Always use in a with statement for proper clean-up:
9195
@@ -113,7 +117,11 @@ def get_package_properties(self, package_id: str) -> ModuleProperties:
113117 Raise InspectError if the target couldn't be imported.
114118 """
115119 self .q1 .put (package_id )
116- res = self .q2 .get ()
120+ res = self ._get_from_queue ()
121+ if res is None :
122+ # The process died; recover and report error.
123+ self ._start ()
124+ raise InspectError ('Process died when importing %r' % package_id )
117125 if isinstance (res , str ):
118126 # Error importing module
119127 if self .counter > 0 :
@@ -126,6 +134,18 @@ def get_package_properties(self, package_id: str) -> ModuleProperties:
126134 self .counter += 1
127135 return res
128136
137+ def _get_from_queue (self ) -> Union [ModuleProperties , str , None ]:
138+ """Get value from the queue.
139+
140+ Return the value read from the queue, or None if the process unexpectedly died.
141+ """
142+ while True :
143+ try :
144+ return self .q2 .get (timeout = 0.05 )
145+ except queue .Empty :
146+ if not self .proc .is_alive ():
147+ return None
148+
129149 def __enter__ (self ) -> 'ModuleInspect' :
130150 return self
131151
0 commit comments