From 5a72e3dbb24aa81c4a5062ac3b078a59a4e6f682 2022-10-29 11:17:43 From: Ben Longbons Date: 2022-10-29 11:17:43 Subject: [PATCH] Stop monkeypatching `linecache` It turns out this has never been necessary if we just change the cache entry to forbid expiry in the first place. --- diff --git a/IPython/core/compilerop.py b/IPython/core/compilerop.py index b43e570..e3d77b1 100644 --- a/IPython/core/compilerop.py +++ b/IPython/core/compilerop.py @@ -73,25 +73,6 @@ class CachingCompiler(codeop.Compile): def __init__(self): codeop.Compile.__init__(self) - # This is ugly, but it must be done this way to allow multiple - # simultaneous ipython instances to coexist. Since Python itself - # directly accesses the data structures in the linecache module, and - # the cache therein is global, we must work with that data structure. - # We must hold a reference to the original checkcache routine and call - # that in our own check_cache() below, but the special IPython cache - # must also be shared by all IPython instances. If we were to hold - # separate caches (one in each CachingCompiler instance), any call made - # by Python itself to linecache.checkcache() would obliterate the - # cached data from the other IPython instances. - if not hasattr(linecache, '_ipython_cache'): - linecache._ipython_cache = {} - if not hasattr(linecache, '_checkcache_ori'): - linecache._checkcache_ori = linecache.checkcache - # Now, we must monkeypatch the linecache directly so that parts of the - # stdlib that call it outside our control go through our codepath - # (otherwise we'd lose our tracebacks). - linecache.checkcache = check_linecache_ipython - # Caching a dictionary { filename: execution_count } for nicely # rendered tracebacks. The filename corresponds to the filename # argument used for the builtins.compile function. @@ -161,14 +142,24 @@ class CachingCompiler(codeop.Compile): # Save the execution count self._filename_map[name] = number + # Since Python 2.5, setting mtime to `None` means the lines will + # never be removed by `linecache.checkcache`. This means all the + # monkeypatching has *never* been necessary, since this code was + # only added in 2010, at which point IPython had already stopped + # supporting Python 2.4. + # + # Note that `linecache.clearcache` and `linecache.updatecache` may + # still remove our code from the cache, but those show explicit + # intent, and we should not try to interfere. Normally the former + # is never called except when out of memory, and the latter is only + # called for lines *not* in the cache. entry = ( len(transformed_code), - time.time(), + None, [line + "\n" for line in transformed_code.splitlines()], name, ) linecache.cache[name] = entry - linecache._ipython_cache[name] = entry return name @contextmanager @@ -187,10 +178,20 @@ class CachingCompiler(codeop.Compile): def check_linecache_ipython(*args): - """Call linecache.checkcache() safely protecting our cached values. + """Deprecated since IPython 8.6. Call linecache.checkcache() directly. + + It was already not necessary to call this function directly. If no + CachingCompiler had been created, this function would fail badly. If + an instance had been created, this function would've been monkeypatched + into place. + + As of IPython 8.6, the monkeypatching has gone away entirely. But there + were still internal callers of this function, so maybe external callers + also existed? """ - # First call the original checkcache as intended - linecache._checkcache_ori(*args) - # Then, update back the cache with our data, so that tracebacks related - # to our compiled codes can be produced. - linecache.cache.update(linecache._ipython_cache) + import warnings + warnings.warn( + 'Just call linecache.checkcache() directly.', + DeprecationWarning + ) + linecache.checkcache() diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 6d04846..c4eecff 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -61,7 +61,7 @@ from IPython.core import magic, oinspect, page, prefilter, ultratb from IPython.core.alias import Alias, AliasManager from IPython.core.autocall import ExitAutocall from IPython.core.builtin_trap import BuiltinTrap -from IPython.core.compilerop import CachingCompiler, check_linecache_ipython +from IPython.core.compilerop import CachingCompiler from IPython.core.debugger import InterruptiblePdb from IPython.core.display_trap import DisplayTrap from IPython.core.displayhook import DisplayHook @@ -1810,7 +1810,6 @@ class InteractiveShell(SingletonConfigurable): self.InteractiveTB = ultratb.AutoFormattedTB(mode = 'Plain', color_scheme='NoColor', tb_offset = 1, - check_cache=check_linecache_ipython, debugger_cls=self.debugger_cls, parent=self) # The instance will store a pointer to the system-wide exception hook, diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index 8f40c63..e83e2b4 100644 --- a/IPython/core/ultratb.py +++ b/IPython/core/ultratb.py @@ -644,10 +644,8 @@ class VerboseTB(TBTools): self.long_header = long_header self.include_vars = include_vars # By default we use linecache.checkcache, but the user can provide a - # different check_cache implementation. This is used by the IPython - # kernel to provide tracebacks for interactive code that is cached, - # by a compiler instance that flushes the linecache but preserves its - # own code cache. + # different check_cache implementation. This was formerly used by the + # IPython kernel for interactive code, but is no longer necessary. if check_cache is None: check_cache = linecache.checkcache self.check_cache = check_cache