Repository: agronholm/anyio

Issue:
`MemoryObjectSendStream.send(item)` doesn't raise `BrokenResourceError` when the last receive stream is closed if `item` is falsey
### Things to check first

- [X] I have searched the existing issues and didn't find my bug already reported there

- [X] I have checked that my bug is still present in the latest release


### AnyIO version

master (96920b054c4d0c76ad440f36d7173ab5d5c86948)

### Python version

3.12

### What happened?

`MemoryObjectSendStream.send(item)` doesn't raise `BrokenResourceError` when the last receive stream is closed if `item` is falsey

### How can we reproduce the bug?

2c22c6a53f450e22164ef20d078ecaec65847eff

Discussion:

Patch:
diff --git a/docs/versionhistory.rst b/docs/versionhistory.rst
index b6f0b7a..7c020e0 100644
--- a/docs/versionhistory.rst
+++ b/docs/versionhistory.rst
@@ -12,6 +12,8 @@ This library adheres to `Semantic Versioning 2.0 <http://semver.org/>`_.
 - Added the ``BlockingPortalProvider`` class to aid with constructing synchronous
   counterparts to asynchronous interfaces that would otherwise require multiple blocking
   portals
+- Added ``__slots__`` to ``AsyncResource`` so that child classes can use ``__slots__``
+  (`#733 <https://github.com/agronholm/anyio/pull/733>`_; PR by Justin Su)
 - Fixed erroneous ``RuntimeError: called 'started' twice on the same task status``
   when cancelling a task in a TaskGroup created with the ``start()`` method before
   the first checkpoint is reached after calling ``task_status.started()``
@@ -28,8 +30,10 @@ This library adheres to `Semantic Versioning 2.0 <http://semver.org/>`_.
 - Emit a ``ResourceWarning`` for ``MemoryObjectReceiveStream`` and
   ``MemoryObjectSendStream`` that were garbage collected without being closed (PR by
   Andrey Kazantcev)
-- Added ``__slots__`` to ``AsyncResource`` so that child classes can use ``__slots__``
-  (`#733 <https://github.com/agronholm/anyio/pull/733>`_; PR by Justin Su)
+- Fixed ``MemoryObjectSendStream.send()`` not raising ``BrokenResourceError`` when the
+  last corresponding ``MemoryObjectReceiveStream`` is closed while waiting to send a
+  falsey item (`#731 <https://github.com/agronholm/anyio/issues/731>`_; PR by Ganden
+  Schaffner)
 
 **4.3.0**
 
diff --git a/src/anyio/streams/memory.py b/src/anyio/streams/memory.py
index b16a07a..8fd94f4 100644
--- a/src/anyio/streams/memory.py
+++ b/src/anyio/streams/memory.py
@@ -234,7 +234,8 @@ class MemoryObjectSendStream(Generic[T_contra], ObjectSendStream[T_contra]):
                 self._state.waiting_senders.pop(send_event, None)
                 raise
 
-            if self._state.waiting_senders.pop(send_event, None):
+            if send_event in self._state.waiting_senders:
+                del self._state.waiting_senders[send_event]
                 raise BrokenResourceError from None
 
     def clone(self) -> MemoryObjectSendStream[T_contra]:


Test patch:
diff --git a/tests/streams/test_memory.py b/tests/streams/test_memory.py
index ded2beb..53d87c6 100644
--- a/tests/streams/test_memory.py
+++ b/tests/streams/test_memory.py
@@ -224,10 +224,11 @@ async def test_close_send_while_receiving() -> None:
 
 
 async def test_close_receive_while_sending() -> None:
-    send, receive = create_memory_object_stream[str](0)
+    # We send None here as a regression test for #731
+    send, receive = create_memory_object_stream[None](0)
     with pytest.raises(ExceptionGroup) as exc:
         async with create_task_group() as tg:
-            tg.start_soon(send.send, "hello")
+            tg.start_soon(send.send, None)
             await wait_all_tasks_blocked()
             await receive.aclose()