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 <[email protected]>
<http://bugs.python.org/issue29988>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe:
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com