Nathaniel Smith added the comment: > If all you need is that with foo: pass guarantees that either both or neither > of __enter__ and __exit__ are called, for C context managers, and only C > context managers, then the fix is trivial.
It would be nice to have it for 'async with foo: pass' as well, which is a little less trivial because the 'await' dance is inlined into the bytecode (see the initial post in this bug), but basically yes. > Do you have any way to reliably test for this failure mode? Unfortunately no, I haven't implemented one. Let's see, though... The test I wrote for issue30039 demonstrates one way to trigger a signal on a precise bytecode, by writing some code in C that calls raise(SIGNALNUMBER), and then calling it immediately before the bytecode where we want the signal to be raised (simulating the situation where the signal happens to arrive while the C function is running -- note that raise() is convenient because unlike kill() it works cross-platform, even on Windows). This might be sufficient for testing the 'async with' version; it looks like an __await__ method or iterator implemented in C and calling raise() would deliver a signal at a point that should be protected but isn't. The tricky case is plain 'with'. We can write something like: with lock: raise_signal() and this gives bytecode like: 1 0 LOAD_NAME 0 (lock) 2 SETUP_WITH 12 (to 16) 4 POP_TOP 2 6 LOAD_NAME 1 (raise_signal) 8 CALL_FUNCTION 0 10 POP_TOP 12 POP_BLOCK 14 LOAD_CONST 0 (None) >> 16 WITH_CLEANUP_START So the problem is that at offset 8 is where we can run arbitrary code, but the race condition is if a signal arrives between offsets 12 and 16. One possibility would be to set up a chain of Py_AddPendingCall handlers, something like: int chain1(void* _) { Py_AddPendingCall(chain2, 0); return 0; } int chain2(void* _) { Py_AddPendingCall(chain3, 0); return 0; } int chain3(void* _) { raise(SIGINT); return 0; } (or to reduce brittleness, maybe use the void* to hold an int controlling the length of the chain, which would make it easy to run tests with chains of length 1, 2, 3, ...) ......except consulting ceval.c I see that currently this won't work, because it looks like if you call Py_AddPendingCall from inside a pending call callback, then Py_MakePendingCalls will execute the newly added callback immediately after the first one returns. It could be made to work by having Py_MakePendingCalls do a first pass to check the current length of the queue, and then use that as the bound on how many calls it makes. ---------- _______________________________________ Python tracker <rep...@bugs.python.org> <http://bugs.python.org/issue29988> _______________________________________ _______________________________________________ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com