You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Differing behavior for sys.setrace between 3.12 and 3.13 #113728
The events for 3.13 look to be correct. The additional 'raise', 'exception_handled', 'unwind' events for 3.12 seem spurious.
Even if 3.13 is now correct we should at the very least document the change.
In 3.12 the generator is closed by calling throw() which causes the 'raise', 'exception_handled', 'unwind' sequence of events.
However, that isn't necessary for a simple generator, so we simply close it in 3.13.
@markshannon I think this and other sys.monitoring changes are going to require me to do a bit of a re-think. There are tests in the coverage.py test suite that check data based on the raw events. The final user-visible coverage data might come out the same, but the tests are failing now because of changes like this. I'll be working on this.
The change is that we no longer throw into generators to close them.
The events that you were seeing in 3.12 happened in the __del__() method of the generator, which is no longer called in 3.13.
Investigating other generator tracing failures in the coverage.py test suite, I find that the trace function is invoked slightly differently for the return event when yielding. The frame.last_i value is pointing to YIELD_VALUE in Python 3.12, but points to RESUME in 3.13. Is this intentional? Will it stay this way?
Test program:
importdis, linecache, sysprint(sys.version)
deftrace(frame, event, arg):
# The weird globals here is to avoid a NameError on shutdown...ifframe.f_code.co_filename==globals().get("__file__"):
lineno=frame.f_linenoline=linecache.getline(__file__, lineno).rstrip()
print("{} {}: {}".format(event[:4], lineno, line))
ifevent=="return":
code=frame.f_code.co_codelasti=frame.f_lastiprint(f"code[{lasti=}] is {dis.opname[code[lasti]]}")
returntracedefgen():
yield3dis.dis(gen)
sys.settrace(trace)
print(next(gen()))
tl;dr:
Yes, you can rely the semantics of last_i not changing from from now. But we do reserve the right to mess around with the bytecode (although it should be a lot more stable now that PEPs 659 and 669 are done).
Long answer:
The semantics of last_i were less than ideal for 3.11 and 3.12.
Historically (up to 3.10) last_i referred to the last instruction, except when set by frame.f_lineno = and a couple of other corner cases.
In 3.11, some instructions became larger, so that last_i no longer referred to the last instruction, but the last code unit within the last instruction. This meant that the expression dis.opname[code[lasti]] could be gibberish if the previous instruction was a CALL, LOAD_ATTR or other instruction containing more than one code unit.
@iritkatriel fixed all that in #109094 replacing the internal "previous code unit pointer" with a more understandable "instruction pointer". last_i inherits this behavior.
Now the instruction pointer (and last_i) points to the start of the last instruction that started executing, except during a call to a Python function or a send to a generator, when it points to the return/resume address. This is much like an instruction pointer that you might be familiar with from hardware.
Thanks for the detail. It's a bit odd to have f_lasti pointing to the next instruction to execute. I wonder if there's a way to document this? The frame object docs say "the precise instruction", which is ambiguous about next or last.
Now the instruction pointer (and last_i) points to the start of the last instruction that started executing, except during a call to a Python function or a send to a generator, when it points to the return/resume address.
During a call the lasti of the calling frame points to the beginning of the CALL instruction. Same with SEND.
Activity
markshannon commentedon Jan 5, 2024
This is a
sys.monitoringchange.sys.settracemerely reflects that.For
3.12:
3.13:
The events for 3.13 look to be correct. The additional
'raise', 'exception_handled', 'unwind'events for 3.12 seem spurious.Even if 3.13 is now correct we should at the very least document the change.
markshannon commentedon Jan 5, 2024
This has nothing to do with monitoring or tracing. It is about the lifetime of generators.
If we change
to
The events recorded are the same for 3.12 and 3.13. The additional events in 3.12 are from the generator being closed.
markshannon commentedon Jan 5, 2024
Bisected to #111069
markshannon commentedon Jan 5, 2024
So what's happening is this:
In 3.12 the generator is closed by calling
throw()which causes the'raise', 'exception_handled', 'unwind'sequence of events.However, that isn't necessary for a simple generator, so we simply close it in 3.13.
@nedbat is this a problem for coverage?
nedbat commentedon Jan 5, 2024
@markshannon I think this and other sys.monitoring changes are going to require me to do a bit of a re-think. There are tests in the coverage.py test suite that check data based on the raw events. The final user-visible coverage data might come out the same, but the tests are failing now because of changes like this. I'll be working on this.
markshannon commentedon Jan 5, 2024
This isn't a
sys.monitoringchange.The change is that we no longer
throwinto generators to close them.The events that you were seeing in 3.12 happened in the
__del__()method of the generator, which is no longer called in 3.13.nedbat commentedon Jan 5, 2024
I understand. I meant to say, this and also sys.monitoring changes are changing the way I collect data, and therefore the way I test.
gvanrossum commentedon Jan 6, 2024
So... There's nothing to see here, move along, and close the issue?
nedbat commentedon Jan 6, 2024
I will re-open if needed.
nedbat commentedon Jan 9, 2024
Investigating other generator tracing failures in the coverage.py test suite, I find that the trace function is invoked slightly differently for the return event when yielding. The frame.last_i value is pointing to YIELD_VALUE in Python 3.12, but points to RESUME in 3.13. Is this intentional? Will it stay this way?
Test program:
Running on 3.12:
Running on 3.13.0a2:
Running on the tip of 3.13:
markshannon commentedon Jan 9, 2024
tl;dr:
Yes, you can rely the semantics of
last_inot changing from from now. But we do reserve the right to mess around with the bytecode (although it should be a lot more stable now that PEPs 659 and 669 are done).Long answer:
The semantics of
last_iwere less than ideal for 3.11 and 3.12.Historically (up to 3.10)
last_ireferred to the last instruction, except when set byframe.f_lineno =and a couple of other corner cases.In 3.11, some instructions became larger, so that
last_ino longer referred to the last instruction, but the last code unit within the last instruction. This meant that the expressiondis.opname[code[lasti]]could be gibberish if the previous instruction was aCALL,LOAD_ATTRor other instruction containing more than one code unit.@iritkatriel fixed all that in #109094 replacing the internal "previous code unit pointer" with a more understandable "instruction pointer".
last_iinherits this behavior.Now the instruction pointer (and
last_i) points to the start of the last instruction that started executing, except during a call to a Python function or a send to a generator, when it points to the return/resume address. This is much like an instruction pointer that you might be familiar with from hardware.@iritkatriel is this summary correct?
nedbat commentedon Jan 9, 2024
Thanks for the detail. It's a bit odd to have
f_lastipointing to the next instruction to execute. I wonder if there's a way to document this? The frame object docs say "the precise instruction", which is ambiguous about next or last.iritkatriel commentedon Jan 9, 2024
During a call the lasti of the calling frame points to the beginning of the CALL instruction. Same with SEND.
fix: adapt to a 3.13 change in frame.f_lasti
fix: adapt to a 3.13 change in frame.f_lasti
fix: adapt to a 3.13 change in frame.f_lasti
fix: adapt to a 3.13 change in frame.f_lasti
markshannon commentedon Jun 18, 2024
I think this is all sorted.
@nedbat feel to reopen if there is anything else to be done.