Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign up[PEP 558 - WIP] bpo-30744: Trace hooks no longer reset closure state #3640
Conversation
Previously, trace hooks running on a nested function could incorrectly reset the state of a closure cell. This avoids that by slightly changing the semantics of the locals namespace in functions, generators, and coroutines, such that closure references are represented in the locals namespace by their actual cell objects while a trace hook implemented in Python is running. Depends on PEP 558 and bpo-17960 to cover the underlying change to frame.f_locals semantics.
05a351e
to
7626a0e
rebase needed and build failures needed addressing. |
@njsmith Aside from the TODO's in the PR itself, this is a pretty complete implementation of the current version of PEP 558 now. |
One other known issue: the header file updates still need to be modified to conform with the new conventions. |
I don't know if this is already addressed elsewhere, but I'm getting a strange error. Running the below script with python.exe built with this branch (I removed an unused parameter to make it compile):
gives
but this error is absent for certain values of x, say:
|
Hmm, looks like I may have a refcounting bug, and the small integer cache is able to provoke it into crashing. |
I can report that not all small numbers trigger this error. On my machine |
Running |
Last push resolves the test_frame segfault, but adds a TODO noting that the locals proxy may still misbehave after a frame has been cleared (or is in the process of being destroyed). There's also a threading test which checks reference cycles aren't being created through the frames, which is currently failing (quite possibly for a related reason) |
Ugh, the refcyle issue detected by test_threading is kinda inherent in the current implementation: once I'm going to try forcibly breaking the cycle when an optimized frame exits (before the eval loop drops its own reference). |
ncoghlan@7078632 fixed the test_sys_settrace crash (mismanaged refcounts when local variables were updated via the write-through proxy). There's still another segfault in test_log_destroyed_pending_task in test_asyncio, though. |
It appears test_asyncio was hitting the same bug as test_frame (the pop/delete support in the write-through proxy wasn't quite right), as they're both passing for me after the latest change. |
This PR is now a solid proof of concept for the PEP, but there's a lot of cleanup to be done before it would be ready for merging (and that's assuming the PEP is accepted in its current form - the latest draft is still to be discussed on python-dev). I've made notes in the PR description of all the missing pieces that I'm already aware of (including the Mac OS X build failure, which is a clang vs gcc issue). |
…ls-closure-safe
Latest round of PEP review resulted in several changes to the proposed C API, including a clearer separation between additions to the stable ABI and additions to the full CPython C API. I created #18052 as a preparatory refactoring that migrates frameobject.h to the modern header layout that clearly separates the two APIs. |
I made enough progress on migrating to the redesigned API described in https://discuss.python.org/t/pep-558-defined-semantics-for-locals/2936/11?u=ncoghlan that next weekend I'll update the PEP to cover this version of the API (I didn't want to do that until I'd made the initial draft updates here). |
Codecov Report
@@ Coverage Diff @@
## master #3640 +/- ##
=======================================
Coverage 82.03% 82.03%
=======================================
Files 1956 1956
Lines 584511 584511
Branches 44467 44467
=======================================
Hits 479520 479520
Misses 95364 95364
Partials 9627 9627 Continue to review full report at Codecov.
|
…ls-closure-safe
…ls-closure-safe
Mark Shannon pointed out that if every access to the Python level frame f_locals attributes creates a new write-through proxy instance, there's no need to actually store the proxy itself on the frame. Instead, we can continue to store only the shared mapping returned by PyEval_GetLocals(), and avoid creating a circular reference that we then need to break when frame evaluation ends.
Thanks to a suggestion by @markshannon, I was able to get rid of the circular reference between the frame and the fast locals proxy objects (the frame has gone back to holding only the dynamic snapshot that it stores now, and the proxies are created on demand as part of the "frame.f_locals" get descriptor) |
Previously, trace hooks running on a nested function
could incorrectly reset the state of a closure cell.
This avoids that by changing the semantics of the
f_locals namespace on function frames to use a
write-through proxy, such that changes are made
immediately rather than being written back some
arbitrary time later.
PEP 558 and bpo-17960 cover additional changes
to locals() and frame.f_locals semantics that are
part of this update.
Remaining TODO items before this PR could be considered complete:
locals()
,vars()
, and potentially others (check alllocals
mentions)locals()
(and potentiallyvars()
)PyEval_*
C API documentation updatesPyFrame_*
C API documentation updatesFix C API header layout to follow modern conventions(#18052)PyFrameObject
redefinitioneval()
default locals namespace toPyLocals_Get()
exec()
default locals namespace toPyLocals_Get()
IMPORT_STAR
opcode is encountered in aCO_OPTIMIZED
framePyFrame_LocalsToFast()
runtime errorPyLocals_Get()
andPyEval_GetLocals()
at different scopessetdefault()
popitem()
clear()
update()
(theoretically already implemented, but needs explicit tests)Desirable code structure improvements (may be best handled as separate refactoring PRs rather than including them directly in this PR):
odictobject.c
DictProxy
code out ofdescrobject.c
https://bugs.python.org/issue30744