@@ -189,6 +189,51 @@ def _get_executable_linenos(code):
189189 return linenos
190190
191191
192+ # filename: (size, mtime, executable_linenos, fullname)
193+ _executable_linenos_cache = {}
194+
195+
196+ def _check_executable_linenos_cache (filename = None ):
197+ if filename is None :
198+ filenames = tuple (_executable_linenos_cache )
199+ else :
200+ filenames = tuple (filename )
201+
202+ for filename in filenames :
203+ if (entry := _executable_linenos_cache .get (filename )) is None :
204+ continue
205+ size , mtime , _ , fullname = entry
206+ if mtime is None :
207+ continue
208+ try :
209+ stat = os .stat (fullname )
210+ except (OSError , ValueError ):
211+ _executable_linenos_cache .pop (filename , None )
212+ continue
213+ if size != stat .st_size or mtime != stat .st_mtime :
214+ _executable_linenos_cache .pop (filename , None )
215+
216+
217+ def _set_executable_linenos_cache_entry (
218+ filename , executable_linenos , source , linecache_entry
219+ ):
220+ if linecache_entry is not None and len (linecache_entry ) != 1 :
221+ size , mtime , _ , fullname = linecache_entry
222+ else :
223+ fullname = filename
224+ try :
225+ stat = os .stat (filename )
226+ except (OSError , ValueError ):
227+ size = len (source )
228+ mtime = None
229+ else :
230+ size = stat .st_size
231+ mtime = stat .st_mtime
232+ _executable_linenos_cache [filename ] = (
233+ size , mtime , executable_linenos , fullname
234+ )
235+
236+
192237class Bdb :
193238 """Generic Python debugger base class.
194239
@@ -207,7 +252,6 @@ def __init__(self, skip=None, backend='settrace'):
207252 self .skip = set (skip ) if skip else None
208253 self .breaks = {}
209254 self .fncache = {}
210- self .executable_linenos_cache = {}
211255 self .frame_trace_lines_opcodes = {}
212256 self .frame_returning = None
213257 self .trace_opcodes = False
@@ -258,6 +302,7 @@ def reset(self):
258302 """Set values of attributes as ready to start debugging."""
259303 import linecache
260304 linecache .checkcache ()
305+ _check_executable_linenos_cache ()
261306 self .botframe = None
262307 self ._set_stopinfo (None , None )
263308
@@ -681,16 +726,25 @@ def set_break(self, filename, lineno, temporary=False, cond=None,
681726 """
682727 filename = self .canonic (filename )
683728 import linecache # Import as late as possible
729+ linecache .checkcache (filename )
730+ _check_executable_linenos_cache (filename )
684731 line = linecache .getline (filename , lineno )
685732 if not line :
686733 return 'Line %s:%d does not exist' % (filename , lineno )
687- if filename not in self . executable_linenos_cache :
734+ if filename not in _executable_linenos_cache :
688735 source = '' .join (linecache .getlines (filename ))
689736 if source :
690737 with suppress (SyntaxError ):
691738 code = compile (source , filename , 'exec' )
692- self .executable_linenos_cache [filename ] = _get_executable_linenos (code )
693- executable_lines = self .executable_linenos_cache .get (filename )
739+ executable_lines = _get_executable_linenos (code )
740+ _set_executable_linenos_cache_entry (
741+ filename ,
742+ executable_lines ,
743+ source ,
744+ linecache .cache .get (filename ),
745+ )
746+ cache_entry = _executable_linenos_cache .get (filename )
747+ executable_lines = cache_entry [2 ] if cache_entry else None
694748 if executable_lines and lineno not in executable_lines :
695749 return 'Line %d has no code associated with it' % lineno
696750 self ._add_to_breaks (filename , lineno )
0 commit comments