New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
GH-103082: Implementation of PEP 669: Low Impact Monitoring for CPython #103083
Conversation
@markshannon have you been able to take a look at the bug (#103083 (comment)) this PR introduced? Do you need help to reproduce it? |
I'm not really sure what the bug you describe actually is.
Do you have a test where |
I have a test, but it requires the debugger. If you get the sources to the debugger (clone https://github.com/fabioz/PyDev.Debugger/) You can then: and run:
and it'll fail with this PR applied because when stopping at a breakpoint at a given |
I'm seeing the same memory leak that the buildbots are reporting on main, so not a leak in this PR. |
That might mean that there is an extra line event. Do you have a function that produces this problem? (module level code is a pain to test) |
I was testing some things here and I got an easier repro: Create a file named:
If you run it with the master it'll print something as:
if you run it with this PR you'll get repeated lines, so, the output is something as:
|
Is there another module that behaves like this?
produces:
It's a module but I can't import it directly. |
For the record, I had the same issue when I was using it and I was confused. |
But does it matter? There is no promise that |
This happens basically whenever a module imports another module: >>> import ast
>>> type(ast.sys)
<class 'module'>
>>> import ast.sys
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'ast.sys'; 'ast' is not a package Perhaps a good mental model for |
It is a bit bit weird that I expect to (hopefully before 3.12) use some sort of lazy loading to avoid creating the monitoring object if it isn't needed. |
@fabioz Thanks for the reproducer. |
OK. I'm merging this. I think this is stable enough to merge, and we can probably get better bug reports with this merged than on a branch. |
@markshannon from the fix you seem to have put there, it'd still fail if the user set the tracing to |
I think that is the current behavior, is it not? If you restart tracing, then you want a line event for the current line. TBH, it is all an undocumented black box, so some guess work is required. |
Darn, I was just about to complete my second review.
|
Having stacktop <= 0 ensures that invalid | ||
values are not visible to the cycle GC. | ||
We choose -1 rather than 0 to assist debugging. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having stacktop <= 0 ensures that invalid | |
values are not visible to the cycle GC. | |
We choose -1 rather than 0 to assist debugging. */ | |
Having stacktop <= 0 ensures that invalid | |
values are not visible to the cycle GC. | |
We choose -1 rather than 0 to assist debugging. */ |
DTRACE_FUNCTION_ENTRY(); | ||
/* Because this avoids the RESUME, | ||
* we need to update instrumentation */ | ||
_Py_Instrument(frame->f_code, tstate->interp); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The return value of _Py_Instrument
is ignored here. Since it might set an exception, I'd expect a goto exit_unwind
on error here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are already handling an exception here, so the exception isn't lost, it replaces the thrown exception.
The question is whether to raise it back to the caller, or to inject into the coroutine/generator?
If unwinding were done in a separate function from evaluation, then the thrown exception would unwind the stack, then the new exception would be raised on continued execution, which would be better.
I'm inclined to leave it for now, and let it get fixed as a side effect of separating evaluation and unwinding.
}; | ||
|
||
static inline bool | ||
opcode_has_event(int opcode) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
opcode_has_event(int opcode) { | |
opcode_has_event(uint8_t opcode) | |
{ |
} | ||
|
||
static inline bool | ||
is_instrumented(int opcode) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is_instrumented(int opcode) { | |
is_instrumented(uint8_t opcode) | |
{ |
assert(test); \ | ||
} while (0) | ||
|
||
bool valid_opcode(int opcode) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bool valid_opcode(int opcode) { | |
bool valid_opcode(int opcode) | |
{ |
_PyInterpreterFrame *frame, _Py_CODEUNIT *instr, _Py_CODEUNIT *target | ||
) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_PyInterpreterFrame *frame, _Py_CODEUNIT *instr, _Py_CODEUNIT *target | |
) { | |
_PyInterpreterFrame *frame, _Py_CODEUNIT *instr, _Py_CODEUNIT *target) | |
{ |
|
||
#define C_RETURN_EVENTS \ | ||
((1 << PY_MONITORING_EVENT_C_RETURN) | \ | ||
(1 << PY_MONITORING_EVENT_C_RAISE)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(1 << PY_MONITORING_EVENT_C_RAISE)) | |
(1 << PY_MONITORING_EVENT_C_RAISE)) |
interp->monitoring_tool_names[tool_id] == NULL | ||
) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
interp->monitoring_tool_names[tool_id] == NULL | |
) { | |
interp->monitoring_tool_names[tool_id] == NULL) | |
{ |
else { | ||
return MOST_SIGNIFICANT_BITS[bits]; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
else { | |
return MOST_SIGNIFICANT_BITS[bits]; | |
} | |
return MOST_SIGNIFICANT_BITS[bits]; |
|
||
/* Should use instruction metadata for this */ | ||
static bool | ||
is_super_instruction(int opcode) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is_super_instruction(int opcode) { | |
is_super_instruction(uint8_t opcode) | |
{ |
Ok, I'll try another round of the debugger tests to see if there's more breakage -- that previous issue didn't really let me get further, so, I'll check and report back -- with bugs in the tracker if that's the case I guess ;) |
@markshannon I just checked and changing the tracing shouldn't duplicate line events in the new tracer (it should only report when the line actually changes). I created #103471 with a test case for this (which works in Python 3.11 and fails in the current master). |
|
… CPython (pythonGH-103083) * The majority of the monitoring code is in instrumentation.c * The new instrumentation bytecodes are in bytecodes.c * legacy_tracing.c adapts the new API to the old sys.setrace and sys.setprofile APIs
This implements PEP 669.
There are a couple of things missing, but no harm in early review.