# A lightweight cooperative timeout mechanism for synchronous code \`with timeout(seconds):\`

**URL:** <https://discuss.python.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811>\
**Category:** Ideas\
**Created:** [June 19, 2026, 4:50am UTC](https://discuss.python.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811 "2026-06-19T04:50:02Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![zang-langyan](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zang-langyan/32/35503_2.png) [@zang-langyan](https://discuss.python.org/u/zang-langyan)\
**Post date:** [June 19, 2026, 4:50am UTC](https://discuss.python.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811/1 "2026-06-19T04:50:02Z")

</div>

I’ve been thinking about a problem that many of us might have run into in practice: a piece of code runs for too long, and you don’t know whether it is the os problem or the code logic problem or it is just stuck. Hence, I’d like to propose a built-in **cooperative timeout** for synchronous code. The idea is a `with timeout(seconds):` context manager that works on any thread, introduces negligible overhead when not used, and provides a clear pattern for C extensions to become interruptible.

## Current gap

The existing tools are either restricted to the main thread (`signal`), only for async code (`asyncio.timeout`), or don’t actually stop the work (thread pool futures).  
More fundamentally, there is no standard **cancellation check** that C extensions can call to cooperate with Python-level timeouts.

## What I have so far?

I have a working prototype(maybe a bit ugly) which leverages CPython’s existing “eval breaker” mechanism:

- A new per‑thread field `timeout_block` (a linked list for nested timeouts) is added to `PyThreadState`.

- `with timeout(seconds):` pushes a deadline onto this list and sets a dedicated `_PY_EVAL_TIMEOUT_BIT` in the thread’s `eval_breaker`.

- The interpreter loop already calls `_Py_HandlePending` at safe points (between bytecodes, back‑edges, function calls). My patch adds a tiny check there: if the timeout bit is set, it compares the current time against the innermost deadline and, if expired, raises `TimeoutError`.

- When the `with` block exits, it pops the deadline. If the list becomes empty, the timeout bit is cleared and the thread goes back to zero‑overhead execution.

**Key properties:**

- **Zero overhead when not in use** : the `eval_breaker` bit stays 0, so the interpreter skips the time check entirely.

- **No background thread** : everything is driven synchronously by the bytecode loop, making it simple and efficient.

- **Works with any thread** : the state is thread‑local; no GIL assumptions required.

- **Fully compatible with the free‑threaded build** : each thread checks its own `timeout_block`, no cross‑thread coordination is needed.

It looks like this:

 ![image](https://us1.discourse-cdn.com/flex002/uploads/python1/original/3X/d/b/db89d0ed1605bd6ec626c157517bf458eef9a720.png)

## What about C extensions?

Pure Python loops are automatically interrupted at the bytecode boundary. For C extensions that perform long computations (e.g. regular expressions, compression, JSON parsing, etc.) to become timeout‑aware, they need to **cooperatively call** `Py_CheckTimeOut(tstate)` at regular intervals.  
Similar to

- C++20: `stop_token`.
- Go: `context.WithTimeout` + `ctx.Done()` checks
- C#: `CancellationToken`

I’d love to get some feedbacks on this before I open an Issue/PR. Is this a feature you’d support? Are there design concerns or use cases I’m missing?

---

<div class="post-metadata">

**Author:** ![Rosuav](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rosuav/32/3429_2.png) [@Rosuav](https://discuss.python.org/u/Rosuav)\
**Post date:** [June 19, 2026, 7:24am UTC](https://discuss.python.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811/2 "2026-06-19T07:24:14Z")

</div>

> [@zang-langyan](#):
>
> - The interpreter loop already calls `_Py_HandlePending` at safe points (between bytecodes, back‑edges, function calls). My patch adds a tiny check there: if the timeout bit is set, it compares the current time against the innermost deadline and, if expired, raises `TimeoutError`.

How frequently is this called? How much overhead does a time-of-day check add?

---

<div class="post-metadata">

**Author:** ![barry-scott](https://avatars.discourse-cdn.com/v4/letter/b/e9c0ed/32.png) [@barry-scott](https://discuss.python.org/u/barry-scott)\
**Post date:** [June 19, 2026, 9:27am UTC](https://discuss.python.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811/3 "2026-06-19T09:27:33Z")

</div>

> [@Rosuav](#):
>
> How much overhead does a time-of-day check add?

On all OS time of day has been optimised to the point it’s negligible.

---

<div class="post-metadata">

**Author:** ![vstinner](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/vstinner/32/15130_2.png) [@vstinner](https://discuss.python.org/u/vstinner)\
**Post date:** [June 19, 2026, 9:54am UTC](https://discuss.python.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811/4 "2026-06-19T09:54:00Z")

</div>

> [@barry-scott](#):
>
> On all OS time of day has been optimised to the point it’s negligible.

On my Fedora 44 laptop, calling `PyTime_PerfCounterRaw()` takes `22.0 ns +- 1.1 ns`, it’s not cheap. For example, calling `len("abc")` takes `19.5 ns +- 0.9 ns`.

Calling frequently `PyTime_PerfCounterRaw()` can introduce a significant slowdown.

---

<div class="post-metadata">

**Author:** ![barry-scott](https://avatars.discourse-cdn.com/v4/letter/b/e9c0ed/32.png) [@barry-scott](https://discuss.python.org/u/barry-scott)\
**Post date:** [June 19, 2026, 10:45am UTC](https://discuss.python.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811/5 "2026-06-19T10:45:55Z")

</div>

But gettimeofday() is very fast as it only has to read out of shared memory on Linux and I think something similar on Windows.  
e.g. no syscall over head.

If the timeout is based on the time of day calls then you get 1ms accuracy I think.

---

<div class="post-metadata">

**Author:** ![zang-langyan](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zang-langyan/32/35503_2.png) [@zang-langyan](https://discuss.python.org/u/zang-langyan)\
**Post date:** [June 19, 2026, 4:25pm UTC](https://discuss.python.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811/6 "2026-06-19T16:25:03Z")

</div>

> [@vstinner](#):
>
> Calling frequently `PyTime_PerfCounterRaw()` can introduce a significant slowdown.

I agree that calling `PyTime_PerfCounterRaw()` frequently would be too expensive, given the numbers you showed.

> [@zang-langyan](#):
>
> - **Zero overhead when not in use** : the `eval_breaker` bit stays 0, so the interpreter skips the time check entirely.

However, When no timeout is active, no time check is performed. As for in the timeout block, we can reduce the frequency to check the time (e.g., every N bytecodes or every function call, not every single instruction). C extensions like `_sre` can check every K steps or every M bytes of input, instead of every state transition.

So the bottom line is: no overhead without a timeout, and tunable overhead when a timeout is active.

If the performance impact can be kept negligible, do you think this would be a worthwhile feature to have?

---

<div class="post-metadata">

**Author:** ![gdchinacat](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/gdchinacat/32/37254_2.png) [@gdchinacat](https://discuss.python.org/u/gdchinacat)\
**Post date:** [June 19, 2026, 4:30pm UTC](https://discuss.python.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811/7 "2026-06-19T16:30:54Z")

</div>

\_PY\_EVAL\_TIMEOUT\_BIT seems to simply indicate whether timeout\_block has a non-zero length. Is the expense of checking the length of the list enough to justify caching it in the timeout bit, or is there another reason for the bit?

---

<div class="post-metadata">

**Author:** ![barry-scott](https://avatars.discourse-cdn.com/v4/letter/b/e9c0ed/32.png) [@barry-scott](https://discuss.python.org/u/barry-scott)\
**Post date:** [June 19, 2026, 6:00pm UTC](https://discuss.python.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811/8 "2026-06-19T18:00:16Z")

</div>

FYI: On my F44 desktop I see 11ns for a call to gettimeofday() called using C code.

---

<div class="post-metadata">

**Author:** ![jb2170](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jb2170/32/24298_2.png) [@jb2170](https://discuss.python.org/u/jb2170)\
**Post date:** [June 19, 2026, 10:39pm UTC](https://discuss.python.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811/9 "2026-06-19T22:39:15Z")

</div>

Small poi: `$ man 3p gettimeofday` regards it as obsolete

```txt
APPLICATION USAGE
     Applications should use the clock_gettime() function instead
     of the obsolescent gettimeofday() function.

```

though it may be one of those things like `signal(3p)` vs `sigaction(3p)` where the old version never truly dies 😅 Python only exposes `signal` as `signal.signal` and not `sigaction` at a user-facing level.

---

<div class="post-metadata">

**Author:** ![zang-langyan](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zang-langyan/32/35503_2.png) [@zang-langyan](https://discuss.python.org/u/zang-langyan)\
**Post date:** [June 20, 2026, 1:14am UTC](https://discuss.python.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811/10 "2026-06-20T01:14:50Z")

</div>

> [@gdchinacat](#):
>
> \_PY\_EVAL\_TIMEOUT\_BIT seems to simply indicate whether timeout\_block has a non-zero length. Is the expense of checking the length of the list enough to justify caching it in the timeout bit, or is there another reason for the bit?

```c
if (_Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & _PY_EVAL_EVENTS_MASK) {
        return _Py_HandlePending(tstate);
}

```

In the bytecode dispatch loop, we already test a single word `tstate->eval_breaker`(atomic) against a set of bits for pending calls, async exceptions, etc. Adding `_PY_EVAL_TIMEOUT_BIT` means the timeout check is just another bit in the same test—effectively zero overhead on top of what’s already there. Not setting that means not only lose atomic test but also an additional condition after the above `if` statement(though it may be negligible to test if it is a nullptr or not).

---

<div class="post-metadata">

**Author:** ![barry-scott](https://avatars.discourse-cdn.com/v4/letter/b/e9c0ed/32.png) [@barry-scott](https://discuss.python.org/u/barry-scott)\
**Post date:** [June 20, 2026, 8:49am UTC](https://discuss.python.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811/11 "2026-06-20T08:49:47Z")

</div>

> [@jb2170](#):
>
> ```python
> APPLICATION USAGE
> Applications should use the clock_gettime() function instead
> of the obsolescent gettimeofday() function.
> 
> ```

I’m showing my age using the old function!

Anyway using `clock_gettime(CLOCK_REALTIME_COARSE, &t1);` I’m seeing 3.2ns/call.

---

<div class="post-metadata">

**Author:** ![zang-langyan](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zang-langyan/32/35503_2.png) [@zang-langyan](https://discuss.python.org/u/zang-langyan)\
**Post date:** [June 23, 2026, 1:07pm UTC](https://discuss.python.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811/12 "2026-06-23T13:07:37Z")

</div>

I’ve collected some new data that might address the earlier concerns about per-bytecode overhead.

_Raw C loop_ — calling `Py_CheckTimeOut` directly in a tight loop:  
– **Slow path** (clock read every call): _ **10.2ns ± 1.2ns per call** _  
– **Fast path** (skip interval active): _ **1.1~1.3ns per call** _

This shows that the fast path (just a counter decrement and branch) is essentially free, while the more expensive clock read is amortized to negligible levels.

Let me know if you have some other ideas or concerns.

For those interested in the implementation, see:

> <https://github.com/python/cpython/issues/151989>
>
> \# Feature or enhancement
> 
> \### Proposal:
> 
> As a follow up on https://discuss.pytho…n.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811
> 
> \`\`\`python
> \>\>\> with timeout(0.2):
> ... while True:
> ... pass
> ...
> Traceback (most recent call last):
> File "\<python-input-1\>", line 3, in \<module\>
> pass
> TimeoutError: Timeout
> 
> \>\>\> with timeout(0.2):
> ... re.match(r"(a+)+b", "a" \* 30 + "c")
> ...
> Traceback (most recent call last):
> File "\<python-input-2\>", line 2, in \<module\>
> re.match(r"(a+)+b", "a" \* 30 + "c")
> ~~~~~~~~ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
> File ".../cpython/Lib/re/\_\_init\_\_.py", line 166, in prefixmatch
> return \_compile(pattern, flags).prefixmatch(string)
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^^^^^^^^
> TimeoutError: Timeout
> \`\`\`
> 
> \*\*Key Proporties\*\*:
> Tested on my Macbook Pro M4
> 1. \*\*\*No extra\*\*\* overhead outside a timeout block (i.e. \`timeout\_block == NULL\` timeout check is completely bypassed).
> 2. \`Py\_CheckTimeOut\`
> \* fast path (with skip interval ≥ 8) takes \*\*\*1.1~1.3ns per call\*\*\*; 
> \* slow path (with skip interval \< 8) takes \*\*\*2.0~6.3ns per call\*\*\*; 
> \* full calling (without skip interval) takes \*\*\*10.2ns +- 1.2ns per call\*\*\*.
> 3. \`re\` module integration
> By tapping into the existing signal‑check counter inside \_sre, timeout checks add no measurable overhead to normal regexps while allowing catastrophic backtracking to be safely interrupted within the specified deadline.
> 
> 
> 
> \### Has this already been discussed elsewhere?
> 
> I have already discussed this feature proposal on Discourse
> 
> \### Links to previous discussion of this feature:
> 
> https://discuss.python.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811
> 
> 
> \### Linked PRs
> \* gh-151990

> <https://github.com/python/cpython/pull/151990>
>
> Usage Example:
> \`\`\`python
> \>\>\> with timeout(0.2):
> ... while True:
> ... … pass
> ...
> Traceback (most recent call last):
> File "\<python-input-1\>", line 3, in \<module\>
> pass
> TimeoutError: Timeout
> 
> \>\>\> with timeout(0.2):
> ... re.match(r"(a+)+b", "a" \* 30 + "c")
> ...
> Traceback (most recent call last):
> File "\<python-input-2\>", line 2, in \<module\>
> re.match(r"(a+)+b", "a" \* 30 + "c")
> ~~~~~~~~ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
> File ".../cpython/Lib/re/\_\_init\_\_.py", line 166, in prefixmatch
> return \_compile(pattern, flags).prefixmatch(string)
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^^^^^^^^
> TimeoutError: Timeout
> \`\`\`
> 
> \*\*Performance Summary\*\*
> Tested on my Macbook Pro M4
> 
> 1. \*\*\*No extra\*\*\* overhead outside a timeout block (i.e. \`timeout\_block == NULL\` timeout check is completely bypassed).
> 2. \`Py\_CheckTimeOut\`
> \* fast path (with skip interval ≥ 8) takes 1.1~1.3ns per call;
> \* slow path (with skip interval \< 8) takes 2.0~6.3ns per call;
> \* full calling (without skip interval) takes 10.2ns +- 1.2ns per call.
> 3. \`re\` module integration
> By tapping into the existing signal‑check counter inside \`\_sre\`, timeout checks add no measurable overhead to normal regexps while allowing catastrophic backtracking to be safely interrupted within the specified deadline.
> 
> 
> \* Issue: gh-151989

---

<div class="post-metadata">

**Author:** ![vstinner](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/vstinner/32/15130_2.png) [@vstinner](https://discuss.python.org/u/vstinner)\
**Post date:** [June 23, 2026, 2:43pm UTC](https://discuss.python.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811/13 "2026-06-23T14:43:18Z")

</div>

> [@zang-langyan](#):
>
> _Raw C loop_ — calling `Py_CheckTimeOut` directly in a tight loop:  
> – **Slow path** (clock read every call): _ **10.2ns ± 1.2ns per call** _  
> – **Fast path** (skip interval active): _ **1.1~1.3ns per call** _

I don’t understand well what you are measuring. Can you provide the benchmark script?

From what I saw in your PR, you’re setting `_PY_EVAL_EVENTS_BITS` during a `with timeout(...):` block. It means that ceval has to stop at every single instruction to call `_Py_HandlePending()`, rather than only stopping when there is a pending event. It sounds inefficient.

---

<div class="post-metadata">

**Author:** ![zang-langyan](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zang-langyan/32/35503_2.png) [@zang-langyan](https://discuss.python.org/u/zang-langyan)\
**Post date:** [June 23, 2026, 3:36pm UTC](https://discuss.python.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811/14 "2026-06-23T15:36:30Z")

</div>

Thanks for taking a closer look. I should have included the benchmark script upfront. Here is the script,

```python
import timeit
import _timeout
from contextlib import timeout

_timeout._test_timeout_init(3600)

number = 100_000_000
for si in range(0, 513, 2):
    t = timeit.timeit(f"_timeout._test_benchmark_timeout_check_raw_loop({si}, {number})", globals=dict(_timeout=_timeout), number=1)
    print(f"Py_CheckTimeOut raw loop (wt interval {si}): {t / number * 1e9:.1f} ns")
# when si = 0: it gives around 10ns
# as si increases it diminish down to around 1~2ns

_timeout._test_timeout_cleanup()

number = 100_000_000
code = "x = [i * i for i in range(1000)]"
t1 = timeit.timeit(code, number=number, globals=globals())
t2 = timeit.timeit(f"with timeout(3600):\n {code}", number=number, globals=globals())
print(f"overhead: {((t2/t1)-1)*100:.2f}%") # this gives around 20~30% as of current implementation

```

You’re right that my current PR sets a dedicated bit (`_PY_EVAL_TIMEOUT_BIT`) inside `_PY_EVAL_EVENTS_MASK`, which forces `check_periodics` to call `_Py_HandlePending` on every bytecode when a `with timeout` block is active.  
But the _expensive_ part (clock read) is only done once every N invocations. Inside `_Py_HandlePending`, the actual check `Py_CheckTimeOut` uses a per‑block skip counter. For most of the calls it only does an increment and returns immediately with no clock/syscall.

The remaining overhead is the fixed cost of entering `_Py_HandlePending` itself  
This is what shows up as the 20~30% in the list‑comprehension benchmark.

But still “always entering \_Py\_HandlePending” is not ideal, any suggestion on this?

---

<div class="post-metadata">

**Author:** ![barry-scott](https://avatars.discourse-cdn.com/v4/letter/b/e9c0ed/32.png) [@barry-scott](https://discuss.python.org/u/barry-scott)\
**Post date:** [June 23, 2026, 3:39pm UTC](https://discuss.python.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811/15 "2026-06-23T15:39:21Z")

</div>

> [@zang-langyan](#):
>
> _Raw C loop_ — calling `Py_CheckTimeOut` directly in a tight loop:  
> – **Slow path** (clock read every call): _ **10.2ns ± 1.2ns per call** _  
> – **Fast path** (skip interval active): _ **1.1~1.3ns per call** _

What OS?  
What hardware?

---

<div class="post-metadata">

**Author:** ![zang-langyan](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zang-langyan/32/35503_2.png) [@zang-langyan](https://discuss.python.org/u/zang-langyan)\
**Post date:** [June 23, 2026, 3:40pm UTC](https://discuss.python.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811/16 "2026-06-23T15:40:29Z")

</div>

> [@barry-scott](#):
>
> What OS?  
> What hardware?

Macbook Pro M4

---

<div class="post-metadata">

**Author:** ![barry-scott](https://avatars.discourse-cdn.com/v4/letter/b/e9c0ed/32.png) [@barry-scott](https://discuss.python.org/u/barry-scott)\
**Post date:** [June 23, 2026, 3:48pm UTC](https://discuss.python.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811/17 "2026-06-23T15:48:58Z")

</div>

> [@zang-langyan](#):
>
> Macbook Pro M4

On a linux system the clock\_gettime(CLOCK\_REALTIME\_COARSE) is faster as it not a syscall at 3.2ns on my Fedora desktop.

I see 11ns on a macboot pro M2 for clock\_gettime(CLOCK\_REALTIME).

---

<div class="post-metadata">

**Author:** ![zang-langyan](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zang-langyan/32/35503_2.png) [@zang-langyan](https://discuss.python.org/u/zang-langyan)\
**Post date:** [June 23, 2026, 3:58pm UTC](https://discuss.python.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811/18 "2026-06-23T15:58:37Z")

</div>

> [@zang-langyan](#):
>
> `when si = 0: it gives around 10ns`

Thank you, then the number matches up. Do you think it is a good idea to have this feature?

---

<div class="post-metadata">

**Author:** ![tstefan](https://avatars.discourse-cdn.com/v4/letter/t/f475e1/32.png) [@tstefan](https://discuss.python.org/u/tstefan)\
**Post date:** [June 23, 2026, 4:06pm UTC](https://discuss.python.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811/19 "2026-06-23T16:06:56Z")

</div>

Since your post is actually exactly the same that was [proposed](https://discuss.python.org/t/add-an-opt-in-timeout-parameter-to-re-to-mitigate-catastrophic-backtracking/107766/4) in another thread some days ago, I repeat my concerns:

> [@Add an opt-in timeout parameter to re to mitigate catastrophic backtracking](https://discuss.python.org/t/add-an-opt-in-timeout-parameter-to-re-to-mitigate-catastrophic-backtracking/107766/10):
>
> The problem I see with such a generic solution is that it is quite brittle. The context manager `with timeout` could also be used to run any Python code. If the timeout fires, the cancellation […] could happen between any two bytecode instructions. So, if it was used in a generic way, it could leave any python object that is manipulated within the context manager in an invalid state (not keeping the invariants of the class). The Python docs explicitly warns about [this problem](https://docs.python.org/3/library/signal.html#note-on-signal-handlers-and-exceptions). […]

You emphasize that the mechanism would be “cooperative”. But the details of the proposal reveal that it is cooperative only at the C level (like signal handling; so C extensions can be hardened), but it is not cooperative at the Python level (so Python code cannot be not be hardened). So, the following would also be true if a timeout has fired:

> [@](#):
>
> Most Python code, including the standard library, cannot be made robust against this, and so a `KeyboardInterrupt` (or any other exception resulting from a signal handler) may on rare occasions put the program in an unexpected state. – [docs.python.org](https://docs.python.org/3/library/signal.html#note-on-signal-handlers-and-exceptions)

---

<div class="post-metadata">

**Author:** ![GalaxySnail](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/galaxysnail/32/5655_2.png) [@GalaxySnail](https://discuss.python.org/u/GalaxySnail)\
**Post date:** [June 23, 2026, 4:32pm UTC](https://discuss.python.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811/20 "2026-06-23T16:32:47Z")

</div>

Related discussions about cancellations at the Python level:

> [@Cancelling threads](https://discuss.python.org/t/cancelling-threads/105287):
>
> I’ve been writing an application that makes extensive use of threads (I’d love to use virtual threads someday), and I’ve been really wishing that I had a reliable way to gracefully cancel threads. In prior discussions, it has been rightly noted that, ultimately, threads cannot be forcibly aborted and keep the process stable. I want the process to be stable, however I don’t strictly need my threads to be forcibly aborted. I can live with forcible abortions only being possible with isolated proce…

> [@Are context managers safe with ansychronous exceptions or signals](https://discuss.python.org/t/are-context-managers-safe-with-ansychronous-exceptions-or-signals/107517/9):
>
> This is a known and discussed problem that unfortunately is very hard to fix. Nathaniel Smith @njs did some very thorough blog posts on the subject during his work on trio which led to some fixes and I think a solution that allowed ContextManagers in C to be able to behave atomically but for those in python I it was substantially harder since they involve multiple op-codes. Sorry I don’t have time to provide direct references right now but do a search for those as well as related reports before …

> **[Control-C handling in Python and Trio — njs blog](https://vorpus.org/blog/control-c-handling-in-python-and-trio/)**

(By the way, Trio changed its KeyboarInterrupt protection implementation in [v0.28.0](https://github.com/python-trio/trio/releases/tag/v0.28.0), slightly different implementation, but the same idea.)

[Next page](https://discuss.python.org/t/a-lightweight-cooperative-timeout-mechanism-for-synchronous-code-with-timeout-seconds/107811.md?page=2)
