From 3cc0e6cc83f12e55a243aedf93c9ce706160b359 Mon Sep 17 00:00:00 2001
From: inoue22 <39761181+inoue22@users.noreply.github.com>
Date: Thu, 4 Jun 2026 15:42:17 +0900
Subject: [PATCH 1/3] Fixed weakref TypeError on the asyncio backend
---
src/anyio/_backends/_asyncio.py | 10 ++++++++-
src/anyio/_core/_tasks.py | 2 ++
tests/test_lowlevel.py | 40 +++++++++++++++++++++++++++++++++
3 files changed, 51 insertions(+), 1 deletion(-)
diff --git a/src/anyio/_backends/_asyncio.py b/src/anyio/_backends/_asyncio.py
index e64a58410..c7b756eab 100644
--- a/src/anyio/_backends/_asyncio.py
+++ b/src/anyio/_backends/_asyncio.py
@@ -416,7 +416,11 @@ def __enter__(self) -> CancelScope:
"Each CancelScope may only be used for a single 'with' block"
)
- self._host_task = host_task = cast(asyncio.Task, current_task())
+ host_task = current_task()
+ if host_task is None:
+ raise RuntimeError("A cancel scope can only be entered from within a task")
+
+ self._host_task = host_task
self._tasks.add(host_task)
try:
task_state = _task_states[host_task]
@@ -2424,6 +2428,10 @@ async def checkpoint_if_cancelled(cls) -> None:
@classmethod
async def cancel_shielded_checkpoint(cls) -> None:
+ if current_task() is None:
+ await sleep(0)
+ return
+
with CancelScope(shield=True):
await sleep(0)
diff --git a/src/anyio/_core/_tasks.py b/src/anyio/_core/_tasks.py
index e95d1c159..0c5dd5a56 100644
--- a/src/anyio/_core/_tasks.py
+++ b/src/anyio/_core/_tasks.py
@@ -46,6 +46,8 @@ class CancelScope:
:param shield: ``True`` to shield the cancel scope from external cancellation
:raises NoEventLoopError: if no supported asynchronous event loop is running in the
current thread
+ :raises RuntimeError: if the scope is entered while there is no current task in the
+ running event loop
"""
def __new__(
diff --git a/tests/test_lowlevel.py b/tests/test_lowlevel.py
index 87dd7fdaa..786adfeac 100644
--- a/tests/test_lowlevel.py
+++ b/tests/test_lowlevel.py
@@ -1,10 +1,13 @@
from __future__ import annotations
+import asyncio
from typing import Any
import pytest
from anyio import create_task_group, run
+from anyio._backends import _asyncio
+from anyio._backends._asyncio import AsyncIOBackend, CancelScope
from anyio.lowlevel import (
RunVar,
cancel_shielded_checkpoint,
@@ -86,6 +89,43 @@ async def second_func() -> None:
assert second_finished
+def test_cancel_scope_without_running_task(
+ asyncio_event_loop: asyncio.AbstractEventLoop,
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ """Entering a cancel scope without a current task raises a clear error (asyncio).
+
+ Regression test: previously this raised an obscure
+ ``TypeError: cannot create weak reference to 'NoneType' object`` because
+ ``current_task()`` returned ``None`` and that ``None`` was then used as a key in
+ a ``WeakKeyDictionary``.
+ """
+ monkeypatch.setattr(_asyncio, "current_task", lambda: None)
+
+ async def main() -> None:
+ with pytest.raises(
+ RuntimeError, match="can only be entered from within a task"
+ ):
+ with CancelScope():
+ pass
+
+ asyncio_event_loop.run_until_complete(main())
+
+
+def test_cancel_shielded_checkpoint_without_task(
+ asyncio_event_loop: asyncio.AbstractEventLoop,
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ """cancel_shielded_checkpoint() must not crash without a current task (asyncio).
+
+ Library primitives such as ``CapacityLimiter`` reach this code path while
+ ``current_task()`` may return ``None``; it must degrade to a plain checkpoint
+ instead of raising ``TypeError``.
+ """
+ monkeypatch.setattr(_asyncio, "current_task", lambda: None)
+ asyncio_event_loop.run_until_complete(AsyncIOBackend.cancel_shielded_checkpoint())
+
+
class TestRunVar:
def test_get_set(
self,
From 284b801ae3d5a6cbb2b97e099ba8ba3df5647586 Mon Sep 17 00:00:00 2001
From: inoue22 <39761181+inoue22@users.noreply.github.com>
Date: Thu, 4 Jun 2026 16:15:18 +0900
Subject: [PATCH 2/3] Update docs/versionhistory.rst
---
docs/versionhistory.rst | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/docs/versionhistory.rst b/docs/versionhistory.rst
index 2e62b485f..5c9650c87 100644
--- a/docs/versionhistory.rst
+++ b/docs/versionhistory.rst
@@ -57,6 +57,10 @@ This library adheres to `Semantic Versioning 2.0 `_.
``anyio.run()``; the options are now passed as keyword arguments to ``trio.run()``
again, as documented (a regression from AnyIO 3)
(`#1161 `_; PR by @Zac-HD)
+- Fixed ``TypeError: cannot create weak reference to 'NoneType' object`` on the asyncio
+ backend when a cancel scope or ``cancel_shielded_checkpoint()`` was used while
+ ``current_task()`` returned ``None``
+ (`#1163 `_; PR by @inoue22)
**4.13.0**
From 14c229e7c38a5a72c9555a45f552c3917765760f Mon Sep 17 00:00:00 2001
From: inoue22 <39761181+inoue22@users.noreply.github.com>
Date: Thu, 4 Jun 2026 16:34:36 +0900
Subject: [PATCH 3/3] Update tests/test_lowlevel.py
---
tests/test_lowlevel.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/test_lowlevel.py b/tests/test_lowlevel.py
index 786adfeac..0ca987a70 100644
--- a/tests/test_lowlevel.py
+++ b/tests/test_lowlevel.py
@@ -104,7 +104,7 @@ def test_cancel_scope_without_running_task(
async def main() -> None:
with pytest.raises(
- RuntimeError, match="can only be entered from within a task"
+ RuntimeError, match="A cancel scope can only be entered from within a task"
):
with CancelScope():
pass