Skip to content

Commit b04f63e

Browse files
gpsheadclaude
andcommitted
gh-157146: Make the archive lookup safe during interpreter shutdown
sys.path_importer_cache is set to None early in finalization, before the objects still referenced from sys attributes are released, so a __del__ can reach linecache while every module it imports is still available. Return early in that case, and treat an ImportError from the lookup like the other imports linecache makes lazily. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016oQeaTzLMBouqehAe3W2EU
1 parent 0ecb0df commit b04f63e

2 files changed

Lines changed: 27 additions & 3 deletions

File tree

Lib/linecache.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,11 @@ def updatecache(filename, module_globals=None):
158158
if data is None:
159159
# The file may be inside an archive on the module search path,
160160
# such as a zip file.
161-
data = _read_from_archive(fullname)
161+
try:
162+
data = _read_from_archive(fullname)
163+
except ImportError:
164+
# Can happen if the interpreter is shutting down.
165+
return []
162166
if data is not None:
163167
entry = (
164168
len(data),
@@ -215,14 +219,17 @@ def _read_from_archive(filename):
215219
"""
216220
import os
217221
import sys
222+
importers = sys.path_importer_cache
223+
if importers is None:
224+
# Cleared while the interpreter is shutting down.
225+
return None
218226
path = filename
219227
while True:
220228
parent = os.path.dirname(path)
221229
if parent == path:
222230
return None
223231
path = parent
224-
get_data = getattr(sys.path_importer_cache.get(path), 'get_data',
225-
None)
232+
get_data = getattr(importers.get(path), 'get_data', None)
226233
if get_data is None:
227234
continue
228235
try:

Lib/test/test_linecache.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,23 @@ def test_linecache_python_string(self):
360360
self.assertEqual(retcode, 0)
361361
self.assertEqual(stdout, b'')
362362
self.assertEqual(stderr, b'')
363+
def test_lookup_during_interpreter_shutdown(self):
364+
# sys.path_importer_cache is set to None early in interpreter
365+
# shutdown, before objects still referenced from sys attributes are
366+
# released, so a __del__ can end up here while the modules linecache
367+
# imports are still available.
368+
code = """if 1:
369+
import linecache, sys
370+
linecache.getlines('/nonexistent/warmup.py') # import tokenize
371+
class Holder:
372+
def __del__(self):
373+
lines = linecache.getlines('/nonexistent/x.py')
374+
print('lines:', lines, file=sys.__stderr__)
375+
sys.ps1 = Holder()
376+
"""
377+
retcode, stdout, stderr = assert_python_ok('-c', code)
378+
self.assertEqual(stderr.strip(), b'lines: []')
379+
363380

364381
class LineCacheInvalidationTests(unittest.TestCase):
365382
def setUp(self):

0 commit comments

Comments
 (0)