Skip to content

Commit 0b2dd1a

Browse files
graingertoremanj
andcommitted
Add docs and update news
Co-Authored-By: oremanj <[email protected]>
1 parent b21bde6 commit 0b2dd1a

File tree

2 files changed

+56
-4
lines changed

2 files changed

+56
-4
lines changed

docs/source/reference-lowlevel.rst

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,40 @@ These transitions are accomplished using two function decorators:
377377
poorly-timed :exc:`KeyboardInterrupt` could leave the lock in an
378378
inconsistent state and cause a deadlock.
379379

380+
Since KeyboardInterrupt protection is tracked per code object, any attempt to
381+
conditionally protect the same block of code in different ways is unlikely to behave
382+
how you expect. If you try to conditionally protect a closure, it will be
383+
unconditionally protected instead::
384+
385+
def example(protect: bool) -> bool:
386+
def inner() -> bool:
387+
return trio.lowlevel.currently_ki_protected()
388+
if protect:
389+
inner = trio.lowlevel.enable_ki_protection(inner)
390+
return inner()
391+
392+
assert example(False) == False
393+
assert example(True) == True # once protected ...
394+
assert example(False) == True # ... always protected
395+
396+
If you really need conditional protection, you can achieve it by giving each
397+
KI-protected instance of the closure its own code object::
398+
399+
def example(protect: bool) -> bool:
400+
def inner() -> bool:
401+
return trio.lowlevel.currently_ki_protected()
402+
if protect:
403+
inner.__code__ = inner.__code__.replace()
404+
inner = trio.lowlevel.enable_ki_protection(inner)
405+
return inner()
406+
407+
assert example(False) == False
408+
assert example(True) == True
409+
assert example(False) == False
410+
411+
(This isn't done by default because it carries some memory overhead and reduces
412+
the potential for specializing optimizations in recent versions of CPython.)
413+
380414
.. autofunction:: currently_ki_protected
381415

382416

newsfragments/3108.bugfix.rst

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
Rework KeyboardInterrupt protection to track code objects, rather than frames,
2-
as protected or not. This should substantially reduce its overhead, and also fixes
3-
an issue with the previous implementation: locals' lifetimes will no longer be
4-
extended by materialization in the ``frame.f_locals`` dictionary that the previous
5-
KI protection logic needed to access to do its work.
2+
as protected or not. The new implementation no longer needs to access
3+
``frame.f_locals`` dictionaries, so it won't artificially extend the lifetime of
4+
local variables. Since KeyboardInterrupt protection is now imposed statically
5+
(when a protected function is defined) rather than each time the function runs,
6+
its previously-noticeable performance overhead should now be near zero.
7+
The lack of a call-time wrapper has some other benefits as well:
8+
9+
* :func:`inspect.iscoroutinefunction` and the like now give correct answers when
10+
called on KI-protected functions.
11+
12+
* Calling a synchronous KI-protected function no longer pushes an additional stack
13+
frame, so tracebacks are clearer.
14+
15+
* A synchronous KI-protected function invoked from C code (such as a weakref
16+
finalizer) is now guaranteed to start executing; previously there would be a brief
17+
window in which KeyboardInterrupt could be raised before the protection was
18+
established.
19+
20+
One minor drawback of the new approach is that it is no longer possible to apply
21+
different KI protection rules to different instances of the same closure. See the
22+
documentation of `@enable_ki_protection <trio.lowlevel.enable_ki_protection>`
23+
for more details and a workaround.

0 commit comments

Comments
 (0)