1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
import outcome
import pytest
import sys
import os
import signal
import threading
import contextlib
import time
 
from async_generator import (
    async_generator,
    yield_,
    isasyncgenfunction,
    asynccontextmanager,
)
 
from ... import _core
from ...testing import wait_all_tasks_blocked
from ..._util import signal_raise, is_main_thread
from ..._timeouts import sleep
from .tutil import slow
 
 
def ki_self():
    signal_raise(signal.SIGINT)
 
 
def test_ki_self():
    with pytest.raises(KeyboardInterrupt):
        ki_self()
 
 
async def test_ki_enabled():
    # Regular tasks aren't KI-protected
    assert not _core.currently_ki_protected()
 
    # Low-level call-soon callbacks are KI-protected
    token = _core.current_trio_token()
    record = []
 
    def check():
        record.append(_core.currently_ki_protected())
 
    token.run_sync_soon(check)
    await wait_all_tasks_blocked()
    assert record == [True]
 
    @_core.enable_ki_protection
    def protected():
        assert _core.currently_ki_protected()
        unprotected()
 
    @_core.disable_ki_protection
    def unprotected():
        assert not _core.currently_ki_protected()
 
    protected()
 
    @_core.enable_ki_protection
    async def aprotected():
        assert _core.currently_ki_protected()
        await aunprotected()
 
    @_core.disable_ki_protection
    async def aunprotected():
        assert not _core.currently_ki_protected()
 
    await aprotected()
 
    # make sure that the decorator here overrides the automatic manipulation
    # that start_soon() does:
    async with _core.open_nursery() as nursery:
        nursery.start_soon(aprotected)
        nursery.start_soon(aunprotected)
 
    @_core.enable_ki_protection
    def gen_protected():
        assert _core.currently_ki_protected()
        yield
 
    for _ in gen_protected():
        pass
 
    @_core.disable_ki_protection
    def gen_unprotected():
        assert not _core.currently_ki_protected()
        yield
 
    for _ in gen_unprotected():
        pass
 
 
# This used to be broken due to
#
#   https://bugs.python.org/issue29590
#
# Specifically, after a coroutine is resumed with .throw(), then the stack
# makes it look like the immediate caller is the function that called
# .throw(), not the actual caller. So child() here would have a caller deep in
# the guts of the run loop, and always be protected, even when it shouldn't
# have been. (Solution: we don't use .throw() anymore.)
async def test_ki_enabled_after_yield_briefly():
    @_core.enable_ki_protection
    async def protected():
        await child(True)
 
    @_core.disable_ki_protection
    async def unprotected():
        await child(False)
 
    async def child(expected):
        import traceback
 
        traceback.print_stack()
        assert _core.currently_ki_protected() == expected
        await _core.checkpoint()
        traceback.print_stack()
        assert _core.currently_ki_protected() == expected
 
    await protected()
    await unprotected()
 
 
# This also used to be broken due to
#   https://bugs.python.org/issue29590
async def test_generator_based_context_manager_throw():
    @contextlib.contextmanager
    @_core.enable_ki_protection
    def protected_manager():
        assert _core.currently_ki_protected()
        try:
            yield
        finally:
            assert _core.currently_ki_protected()
 
    with protected_manager():
        assert not _core.currently_ki_protected()
 
    with pytest.raises(KeyError):
        # This is the one that used to fail
        with protected_manager():
            raise KeyError
 
 
async def test_agen_protection():
    @_core.enable_ki_protection
    @async_generator
    async def agen_protected1():
        assert _core.currently_ki_protected()
        try:
            await yield_()
        finally:
            assert _core.currently_ki_protected()
 
    @_core.disable_ki_protection
    @async_generator
    async def agen_unprotected1():
        assert not _core.currently_ki_protected()
        try:
            await yield_()
        finally:
            assert not _core.currently_ki_protected()
 
    # Swap the order of the decorators:
    @async_generator
    @_core.enable_ki_protection
    async def agen_protected2():
        assert _core.currently_ki_protected()
        try:
            await yield_()
        finally:
            assert _core.currently_ki_protected()
 
    @async_generator
    @_core.disable_ki_protection
    async def agen_unprotected2():
        assert not _core.currently_ki_protected()
        try:
            await yield_()
        finally:
            assert not _core.currently_ki_protected()
 
    # Native async generators
    @_core.enable_ki_protection
    async def agen_protected3():
        assert _core.currently_ki_protected()
        try:
            yield
        finally:
            assert _core.currently_ki_protected()
 
    @_core.disable_ki_protection
    async def agen_unprotected3():
        assert not _core.currently_ki_protected()
        try:
            yield
        finally:
            assert not _core.currently_ki_protected()
 
    for agen_fn in [
        agen_protected1,
        agen_protected2,
        agen_protected3,
        agen_unprotected1,
        agen_unprotected2,
        agen_unprotected3,
    ]:
        async for _ in agen_fn():  # noqa
            assert not _core.currently_ki_protected()
 
        # asynccontextmanager insists that the function passed must itself be an
        # async gen function, not a wrapper around one
        if isasyncgenfunction(agen_fn):
            async with asynccontextmanager(agen_fn)():
                assert not _core.currently_ki_protected()
 
            # Another case that's tricky due to:
            #   https://bugs.python.org/issue29590
            with pytest.raises(KeyError):
                async with asynccontextmanager(agen_fn)():
                    raise KeyError
 
 
# Test the case where there's no magic local anywhere in the call stack
def test_ki_disabled_out_of_context():
    assert _core.currently_ki_protected()
 
 
def test_ki_disabled_in_del():
    def nestedfunction():
        return _core.currently_ki_protected()
 
    def __del__():
        assert _core.currently_ki_protected()
        assert nestedfunction()
 
    @_core.disable_ki_protection
    def outerfunction():
        assert not _core.currently_ki_protected()
        assert not nestedfunction()
        __del__()
 
    __del__()
    outerfunction()
    assert nestedfunction()
 
 
def test_ki_protection_works():
    async def sleeper(name, record):
        try:
            while True:
                await _core.checkpoint()
        except _core.Cancelled:
            record.add(name + " ok")
 
    async def raiser(name, record):
        try:
            # os.kill runs signal handlers before returning, so we don't need
            # to worry that the handler will be delayed
            print("killing, protection =", _core.currently_ki_protected())
            ki_self()
        except KeyboardInterrupt:
            print("raised!")
            # Make sure we aren't getting cancelled as well as siginted
            await _core.checkpoint()
            record.add(name + " raise ok")
            raise
        else:
            print("didn't raise!")
            # If we didn't raise (b/c protected), then we *should* get
            # cancelled at the next opportunity
            try:
                await _core.wait_task_rescheduled(lambda _: _core.Abort.SUCCEEDED)
            except _core.Cancelled:
                record.add(name + " cancel ok")
 
    # simulated control-C during raiser, which is *unprotected*
    print("check 1")
    record = set()
 
    async def check_unprotected_kill():
        async with _core.open_nursery() as nursery:
            nursery.start_soon(sleeper, "s1", record)
            nursery.start_soon(sleeper, "s2", record)
            nursery.start_soon(raiser, "r1", record)
 
    with pytest.raises(KeyboardInterrupt):
        _core.run(check_unprotected_kill)
    assert record == {"s1 ok", "s2 ok", "r1 raise ok"}
 
    # simulated control-C during raiser, which is *protected*, so the KI gets
    # delivered to the main task instead
    print("check 2")
    record = set()
 
    async def check_protected_kill():
        async with _core.open_nursery() as nursery:
            nursery.start_soon(sleeper, "s1", record)
            nursery.start_soon(sleeper, "s2", record)
            nursery.start_soon(_core.enable_ki_protection(raiser), "r1", record)
            # __aexit__ blocks, and then receives the KI
 
    with pytest.raises(KeyboardInterrupt):
        _core.run(check_protected_kill)
    assert record == {"s1 ok", "s2 ok", "r1 cancel ok"}
 
    # kill at last moment still raises (run_sync_soon until it raises an
    # error, then kill)
    print("check 3")
 
    async def check_kill_during_shutdown():
        token = _core.current_trio_token()
 
        def kill_during_shutdown():
            assert _core.currently_ki_protected()
            try:
                token.run_sync_soon(kill_during_shutdown)
            except _core.RunFinishedError:
                # it's too late for regular handling! handle this!
                print("kill! kill!")
                ki_self()
 
        token.run_sync_soon(kill_during_shutdown)
 
    with pytest.raises(KeyboardInterrupt):
        _core.run(check_kill_during_shutdown)
 
    # KI arrives very early, before main is even spawned
    print("check 4")
 
    class InstrumentOfDeath:
        def before_run(self):
            ki_self()
 
    async def main():
        await _core.checkpoint()
 
    with pytest.raises(KeyboardInterrupt):
        _core.run(main, instruments=[InstrumentOfDeath()])
 
    # checkpoint_if_cancelled notices pending KI
    print("check 5")
 
    @_core.enable_ki_protection
    async def main():
        assert _core.currently_ki_protected()
        ki_self()
        with pytest.raises(KeyboardInterrupt):
            await _core.checkpoint_if_cancelled()
 
    _core.run(main)
 
    # KI arrives while main task is not abortable, b/c already scheduled
    print("check 6")
 
    @_core.enable_ki_protection
    async def main():
        assert _core.currently_ki_protected()
        ki_self()
        await _core.cancel_shielded_checkpoint()
        await _core.cancel_shielded_checkpoint()
        await _core.cancel_shielded_checkpoint()
        with pytest.raises(KeyboardInterrupt):
            await _core.checkpoint()
 
    _core.run(main)
 
    # KI arrives while main task is not abortable, b/c refuses to be aborted
    print("check 7")
 
    @_core.enable_ki_protection
    async def main():
        assert _core.currently_ki_protected()
        ki_self()
        task = _core.current_task()
 
        def abort(_):
            _core.reschedule(task, outcome.Value(1))
            return _core.Abort.FAILED
 
        assert await _core.wait_task_rescheduled(abort) == 1
        with pytest.raises(KeyboardInterrupt):
            await _core.checkpoint()
 
    _core.run(main)
 
    # KI delivered via slow abort
    print("check 8")
 
    @_core.enable_ki_protection
    async def main():
        assert _core.currently_ki_protected()
        ki_self()
        task = _core.current_task()
 
        def abort(raise_cancel):
            result = outcome.capture(raise_cancel)
            _core.reschedule(task, result)
            return _core.Abort.FAILED
 
        with pytest.raises(KeyboardInterrupt):
            assert await _core.wait_task_rescheduled(abort)
        await _core.checkpoint()
 
    _core.run(main)
 
    # KI arrives just before main task exits, so the run_sync_soon machinery
    # is still functioning and will accept the callback to deliver the KI, but
    # by the time the callback is actually run, main has exited and can't be
    # aborted.
    print("check 9")
 
    @_core.enable_ki_protection
    async def main():
        ki_self()
 
    with pytest.raises(KeyboardInterrupt):
        _core.run(main)
 
    print("check 10")
    # KI in unprotected code, with
    # restrict_keyboard_interrupt_to_checkpoints=True
    record = []
 
    async def main():
        # We're not KI protected...
        assert not _core.currently_ki_protected()
        ki_self()
        # ...but even after the KI, we keep running uninterrupted...
        record.append("ok")
        # ...until we hit a checkpoint:
        with pytest.raises(KeyboardInterrupt):
            await sleep(10)
 
    _core.run(main, restrict_keyboard_interrupt_to_checkpoints=True)
    assert record == ["ok"]
    record = []
    # Exact same code raises KI early if we leave off the argument, doesn't
    # even reach the record.append call:
    with pytest.raises(KeyboardInterrupt):
        _core.run(main)
    assert record == []
 
    # KI arrives while main task is inside a cancelled cancellation scope
    # the KeyboardInterrupt should take priority
    print("check 11")
 
    @_core.enable_ki_protection
    async def main():
        assert _core.currently_ki_protected()
        with _core.CancelScope() as cancel_scope:
            cancel_scope.cancel()
            with pytest.raises(_core.Cancelled):
                await _core.checkpoint()
            ki_self()
            with pytest.raises(KeyboardInterrupt):
                await _core.checkpoint()
            with pytest.raises(_core.Cancelled):
                await _core.checkpoint()
 
    _core.run(main)
 
 
def test_ki_is_good_neighbor():
    # in the unlikely event someone overwrites our signal handler, we leave
    # the overwritten one be
    try:
        orig = signal.getsignal(signal.SIGINT)
 
        def my_handler(signum, frame):  # pragma: no cover
            pass
 
        async def main():
            signal.signal(signal.SIGINT, my_handler)
 
        _core.run(main)
 
        assert signal.getsignal(signal.SIGINT) is my_handler
    finally:
        signal.signal(signal.SIGINT, orig)
 
 
# Regression test for #461
def test_ki_with_broken_threads():
    thread = threading.main_thread()
 
    # scary!
    original = threading._active[thread.ident]
 
    # put this in a try finally so we don't have a chance of cascading a
    # breakage down to everything else
    try:
        del threading._active[thread.ident]
 
        @_core.enable_ki_protection
        async def inner():
            assert signal.getsignal(signal.SIGINT) != signal.default_int_handler
 
        _core.run(inner)
    finally:
        threading._active[thread.ident] = original