Skip to content

Auto closing broadcast#503

Open
shsms wants to merge 12 commits intofrequenz-floss:v1.x.xfrom
shsms:auto-closing-broadcast
Open

Auto closing broadcast#503
shsms wants to merge 12 commits intofrequenz-floss:v1.x.xfrom
shsms:auto-closing-broadcast

Conversation

@shsms
Copy link
Copy Markdown
Contributor

@shsms shsms commented Mar 10, 2026

Pull request overview

This PR introduces a new auto-closing broadcast channel API (BroadcastChannel) and deprecates the legacy Broadcast/Anycast channel classes, aiming to make broadcast channels close automatically when one side (senders or receivers) is fully gone/closed.

Changes:

  • Add BroadcastChannel (tuple-style constructor returning an initial BroadcastSender/BroadcastReceiver) and rename broadcast endpoint classes to BroadcastSender/BroadcastReceiver.
  • Add new sender capability interfaces (SubscribableSender, ClonableSender, ClonableSubscribableSender) and export them publicly.
  • Deprecate Broadcast and Anycast, update release notes, and add tests covering broadcast auto-close behavior.

@github-actions github-actions bot added part:docs Affects the documentation part:tests Affects the unit, integration and performance (benchmarks) tests part:channels Affects channels implementation part:core Affects the core types (`Sender`, `Receiver`, exceptions, etc.) part:experimental Affects the experimental package labels Mar 10, 2026
@shsms
Copy link
Copy Markdown
Contributor Author

shsms commented Mar 10, 2026

Based on the one shot PR, which also needs more docs and examples.

cc: @simonvoelcker

@shsms shsms force-pushed the auto-closing-broadcast branch 3 times, most recently from 8ae8eab to 49b5b42 Compare March 10, 2026 12:50
llucax pushed a commit to llucax/frequenz-channels-python that referenced this pull request Mar 23, 2026
Bumps the patch group with 6 updates:

| Package | From | To |
| --- | --- | --- |
|
[frequenz-repo-config[lib]](https://github.com/frequenz-floss/frequenz-repo-config-python)
| `0.13.3` | `0.13.4` |
| [mkdocs-material](https://github.com/squidfunk/mkdocs-material) |
`9.6.12` | `9.6.14` |
| [mkdocstrings-python](https://github.com/mkdocstrings/python) |
`1.16.10` | `1.16.11` |
| [pylint](https://github.com/pylint-dev/pylint) | `3.3.6` | `3.3.7` |
|
[frequenz-repo-config[extra-lint-examples]](https://github.com/frequenz-floss/frequenz-repo-config-python)
| `0.13.3` | `0.13.4` |
| [pytest-mock](https://github.com/pytest-dev/pytest-mock) | `3.14.0` |
`3.14.1` |

Updates `frequenz-repo-config[lib]` from 0.13.3 to 0.13.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/releases">frequenz-repo-config[lib]'s
releases</a>.</em></p>
<blockquote>
<h2>v0.13.4</h2>
<h1>Frequenz Repository Configuration Release Notes</h1>
<h2>Summary</h2>
<p>This release just widens the <code>setuptools</code> dependency to
allow for version 80.x.</p>
<h2>What's Changed</h2>
<ul>
<li>Clear release notes by <a
href="https://github.com/llucax"><code>@​llucax</code></a> in <a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/pull/393">frequenz-floss/frequenz-repo-config-python#393</a></li>
<li>Widen setuptools upper bound to &lt; 81 by <a
href="https://github.com/llucax"><code>@​llucax</code></a> in <a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/pull/401">frequenz-floss/frequenz-repo-config-python#401</a></li>
<li>Bump setuptools from 79.0.0 to 80.1.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/pull/400">frequenz-floss/frequenz-repo-config-python#400</a></li>
<li>Bump pydoclint from 0.6.4 to 0.6.6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/pull/397">frequenz-floss/frequenz-repo-config-python#397</a></li>
<li>Bump types-pyyaml from 6.0.12.20250326 to 6.0.12.20250402 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/pull/398">frequenz-floss/frequenz-repo-config-python#398</a></li>
<li>Update types-setuptools requirement from <!-- raw HTML omitted
-->=67.6.0,&lt;81 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/pull/399">frequenz-floss/frequenz-repo-config-python#399</a></li>
<li>Bump the patch group across 1 directory with 4 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/pull/402">frequenz-floss/frequenz-repo-config-python#402</a></li>
<li>Bump the minor group with 4 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/pull/404">frequenz-floss/frequenz-repo-config-python#404</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/compare/v0.13.3...v0.13.4">https://github.com/frequenz-floss/frequenz-repo-config-python/compare/v0.13.3...v0.13.4</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/commit/78ec7874752cf2bacf0db33fcdca52680cc9243d"><code>78ec787</code></a>
Bump the minor group with 4 updates (<a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/issues/404">#404</a>)</li>
<li><a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/commit/b68ebb91edd489049a332a61dea426678452c39b"><code>b68ebb9</code></a>
Bump the minor group with 4 updates</li>
<li><a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/commit/d788cbcb575a12ee89ab893a09c2add68ab913f7"><code>d788cbc</code></a>
Bump the patch group across 1 directory with 4 updates (<a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/issues/402">#402</a>)</li>
<li><a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/commit/1763b3e0bffa87b716ef82ec8fa37dd1a1fb7463"><code>1763b3e</code></a>
Bump the patch group across 1 directory with 4 updates</li>
<li><a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/commit/556d9f98bb6080e1194a837ddebb188a6fdd20a9"><code>556d9f9</code></a>
Bump pydoclint from 0.6.4 to 0.6.6 (<a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/issues/397">#397</a>)</li>
<li><a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/commit/ec5b00f09f88cb5641be5f3da1df1d7f831a0a86"><code>ec5b00f</code></a>
Bump types-pyyaml from 6.0.12.20250326 to 6.0.12.20250402 (<a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/issues/398">#398</a>)</li>
<li><a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/commit/30e8c0a90b480ac424c31d94e50e2d27892f0cf4"><code>30e8c0a</code></a>
Update types-setuptools requirement from &lt;80,&gt;=67.6.0 to
&gt;=67.6.0,&lt;81 (<a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/issues/399">#399</a>)</li>
<li><a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/commit/961db043eea74c5f24c5939e80c2b24de61fce5a"><code>961db04</code></a>
Bump setuptools from 79.0.0 to 80.1.0 (<a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/issues/400">#400</a>)</li>
<li><a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/commit/338a87feb9886ec5c06915cbc7d1816229883b8e"><code>338a87f</code></a>
Widen setuptools upper bound to &lt; 81 (<a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/issues/401">#401</a>)</li>
<li><a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/commit/991c35e636368e1c1608ab1e9276b6a81487d3a1"><code>991c35e</code></a>
Widen setuptools upper bound to &lt; 81</li>
<li>Additional commits viewable in <a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/compare/v0.13.3...v0.13.4">compare
view</a></li>
</ul>
</details>
<br />

Updates `mkdocs-material` from 9.6.12 to 9.6.14
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/squidfunk/mkdocs-material/releases">mkdocs-material's
releases</a>.</em></p>
<blockquote>
<h2>mkdocs-material-9.6.14</h2>
<ul>
<li>Fixed <a
href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8215">#8215</a>:
Social plugin crashes when CairoSVG is updated to 2.8</li>
</ul>
<h2>mkdocs-material-9.6.13</h2>
<ul>
<li>Fixed <a
href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8204">#8204</a>:
Annotations showing list markers in print view</li>
<li>Fixed <a
href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8153">#8153</a>:
Improve style of cardinality symbols in Mermaid.js ER diagrams</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG">mkdocs-material's
changelog</a>.</em></p>
<blockquote>
<p>mkdocs-material-9.6.14 (2025-05-13)</p>
<ul>
<li>Fixed <a
href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8215">#8215</a>:
Social plugin crashes when CairoSVG is updated to 2.8</li>
</ul>
<p>mkdocs-material-9.6.13 (2025-05-10)</p>
<ul>
<li>Fixed <a
href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8204">#8204</a>:
Annotations showing list markers in print view</li>
<li>Fixed <a
href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8153">#8153</a>:
Improve style of cardinality symbols in Mermaid.js ER diagrams</li>
</ul>
<p>mkdocs-material-9.6.12 (2025-04-17)</p>
<ul>
<li>Fixed <a
href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8158">#8158</a>:
Flip footnote back reference icon for right-to-left languages</li>
</ul>
<p>mkdocs-material-9.6.11 (2025-04-01)</p>
<ul>
<li>Updated Docker image to latest Alpine Linux</li>
<li>Bump required Jinja version to 3.1</li>
<li>Fixed <a
href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8133">#8133</a>:
Jinja filter <code>items</code> not available (9.6.10 regression)</li>
<li>Fixed <a
href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8128">#8128</a>:
Search plugin not entirely disabled via enabled setting</li>
</ul>
<p>mkdocs-material-9.6.10 (2025-03-30)</p>
<p>This version is a pure refactoring release, and does not contain new
features
or bug fixes. It strives to improve the compatibility of our templates
with
alternative Jinja-like template engines that we're currently exploring,
including minijinja.</p>
<p>Additionally, it replaces several instances of Python function
invocations
with idiomatic use of template filters. All instances where variables
have
been mutated inside templates have been replaced. Most changes have been
made
in partials, and only a few in blocks, and all of them are fully
backward
compatible, so no changes to overrides are necessary.</p>
<p>Note that this release does not replace the Jinja template engine
with
minijinja. However, our templates are now 99% compatible with minijinja,
which means we can explore alternative Jinja-compatible implementations.
Additionally, immutability and removal of almost all Python function
invocations means much more idiomatic templating.</p>
<p>mkdocs-material-9.6.9 (2025-03-17)</p>
<ul>
<li>Updated Serbo-Croatian translations</li>
<li>Fixed <a
href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8086">#8086</a>:
Custom SVG icons containing hashes break rendering</li>
<li>Fixed <a
href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8067">#8067</a>:
Drawer has gap on right side in Firefox on some OSs</li>
</ul>
<p>mkdocs-material-9.6.8+insiders-4.53.16 (2025-03-13)</p>
<ul>
<li>Fixed <a
href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8019">#8019</a>:
Tooltips have precedence over instant previews</li>
</ul>
<p>mkdocs-material-9.6.8 (2025-03-13)</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/squidfunk/mkdocs-material/commit/2e9bd81018958357a3f96457a7251e3d42866c81"><code>2e9bd81</code></a>
Prepare 9.6.14 release</li>
<li><a
href="https://github.com/squidfunk/mkdocs-material/commit/e45972a8cd183ab8fcede2ac630eff84d9137acb"><code>e45972a</code></a>
Fixed social plugin crashing for CairoSVG &gt;= 2.8</li>
<li><a
href="https://github.com/squidfunk/mkdocs-material/commit/bce053afc3a7199e7b10de710ef1506700953061"><code>bce053a</code></a>
Fixed social plugin crashing for CairoSVG &gt;= 2.8</li>
<li><a
href="https://github.com/squidfunk/mkdocs-material/commit/ca656243deeaf588c6d591a2c24cb57bbe9347d0"><code>ca65624</code></a>
Updated changelog</li>
<li><a
href="https://github.com/squidfunk/mkdocs-material/commit/8670a01be3c43213e05d07ac1ec50f046b290f7f"><code>8670a01</code></a>
Prepare 9.6.13 release</li>
<li><a
href="https://github.com/squidfunk/mkdocs-material/commit/84858814111aee30f1d2b0e7b4f56e419f7f59d0"><code>8485881</code></a>
Fixed entity-relationship diagram styling after Mermaid upgrade (<a
href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8211">#8211</a>)</li>
<li><a
href="https://github.com/squidfunk/mkdocs-material/commit/5a0adb8905fac5d9054cacbafe985023e2dec0d2"><code>5a0adb8</code></a>
Documentation (<a
href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8205">#8205</a>)</li>
<li><a
href="https://github.com/squidfunk/mkdocs-material/commit/572da4f57c18836bbeafa201223e1857499bae6c"><code>572da4f</code></a>
Fixed annotations showing list markers in print view</li>
<li><a
href="https://github.com/squidfunk/mkdocs-material/commit/6d04f2cd2c27f8d82676a4ccdc2ab6e67a2edeca"><code>6d04f2c</code></a>
Updated Change Request guide (<a
href="https://redirect.github.com/squidfunk/mkdocs-material/issues/8201">#8201</a>)</li>
<li><a
href="https://github.com/squidfunk/mkdocs-material/commit/e56efb23987a71344dd8fd984c52830149c0df16"><code>e56efb2</code></a>
Updated dependencies</li>
<li>See full diff in <a
href="https://github.com/squidfunk/mkdocs-material/compare/9.6.12...9.6.14">compare
view</a></li>
</ul>
</details>
<br />

Updates `mkdocstrings-python` from 1.16.10 to 1.16.11
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/mkdocstrings/python/releases">mkdocstrings-python's
releases</a>.</em></p>
<blockquote>
<h2>1.16.11</h2>
<h2><a
href="https://github.com/mkdocstrings/python/releases/tag/1.16.11">1.16.11</a>
- 2025-05-24</h2>
<p><!-- raw HTML omitted --><a
href="https://github.com/mkdocstrings/python/compare/1.16.10...1.16.11">Compare
with 1.16.10</a><!-- raw HTML omitted --></p>
<h3>Bug Fixes</h3>
<ul>
<li>Fix highlighting for signature with known special names like
<code>__init__</code> (<a
href="https://github.com/mkdocstrings/python/commit/7f956868f93a766346455fedb296c26787894d5c">7f95686</a>
by Timothée Mazzucotelli). <a
href="https://redirect.github.com/mkdocstrings/mkdocstrings/issues/757">Issue-mkdocstrings-757</a></li>
<li>Use default font-size for parameter headings (<a
href="https://github.com/mkdocstrings/python/commit/0a35b20a6050a28ba8492d93e5f9940a69462ce3">0a35b20</a>
by Timothée Mazzucotelli). <a
href="https://redirect.github.com/mkdocstrings/mkdocstrings/issues/697">Issue-mkdocstrings-697</a></li>
<li>Prevent uppercasing H5 titles (by Material for MkDocs) (<a
href="https://github.com/mkdocstrings/python/commit/ba669697daad5067ea5db3fdf8a2d5ba2f966b25">ba66969</a>
by Timothée Mazzucotelli). <a
href="https://redirect.github.com/mkdocstrings/mkdocstrings/issues/697">Issue-mkdocstrings-697</a>,
<a
href="https://redirect.github.com/mkdocstrings/python/issues/276">Issue-276</a></li>
<li>Use configured heading even when signature is not separated (<a
href="https://github.com/mkdocstrings/python/commit/096960abd79831d6fd45e2a7962dfd2bd49e4edd">096960a</a>
by Timothée Mazzucotelli). <a
href="https://redirect.github.com/mkdocstrings/mkdocstrings/issues/767">Issue-mkdocstrings-767</a>,
<a
href="https://redirect.github.com/mkdocstrings/python/pull/278">PR-278</a></li>
<li>Render attribute names without full path in ToC (<a
href="https://github.com/mkdocstrings/python/commit/d4e618ab794747b84dced848b1be824639fea2b8">d4e618a</a>
by David Lee). <a
href="https://redirect.github.com/mkdocstrings/python/issues/271">Issue-271</a>,
<a
href="https://redirect.github.com/mkdocstrings/python/pull/272">PR-272</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/mkdocstrings/python/blob/main/CHANGELOG.md">mkdocstrings-python's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/mkdocstrings/python/releases/tag/1.16.11">1.16.11</a>
- 2025-05-24</h2>
<p><!-- raw HTML omitted --><a
href="https://github.com/mkdocstrings/python/compare/1.16.10...1.16.11">Compare
with 1.16.10</a><!-- raw HTML omitted --></p>
<h3>Bug Fixes</h3>
<ul>
<li>Fix highlighting for signature with known special names like
<code>__init__</code> (<a
href="https://github.com/mkdocstrings/python/commit/7f956868f93a766346455fedb296c26787894d5c">7f95686</a>
by Timothée Mazzucotelli). <a
href="https://redirect.github.com/mkdocstrings/mkdocstrings/issues/757">Issue-mkdocstrings-757</a></li>
<li>Use default font-size for parameter headings (<a
href="https://github.com/mkdocstrings/python/commit/0a35b20a6050a28ba8492d93e5f9940a69462ce3">0a35b20</a>
by Timothée Mazzucotelli). <a
href="https://redirect.github.com/mkdocstrings/mkdocstrings/issues/697">Issue-mkdocstrings-697</a></li>
<li>Prevent uppercasing H5 titles (by Material for MkDocs) (<a
href="https://github.com/mkdocstrings/python/commit/ba669697daad5067ea5db3fdf8a2d5ba2f966b25">ba66969</a>
by Timothée Mazzucotelli). <a
href="https://redirect.github.com/mkdocstrings/mkdocstrings/issues/697">Issue-mkdocstrings-697</a>,
<a
href="https://redirect.github.com/mkdocstrings/python/issues/276">Issue-276</a></li>
<li>Use configured heading even when signature is not separated (<a
href="https://github.com/mkdocstrings/python/commit/096960abd79831d6fd45e2a7962dfd2bd49e4edd">096960a</a>
by Timothée Mazzucotelli). <a
href="https://redirect.github.com/mkdocstrings/mkdocstrings/issues/767">Issue-mkdocstrings-767</a>,
<a
href="https://redirect.github.com/mkdocstrings/python/pull/278">PR-278</a></li>
<li>Render attribute names without full path in ToC (<a
href="https://github.com/mkdocstrings/python/commit/d4e618ab794747b84dced848b1be824639fea2b8">d4e618a</a>
by David Lee). <a
href="https://redirect.github.com/mkdocstrings/python/issues/271">Issue-271</a>,
<a
href="https://redirect.github.com/mkdocstrings/python/pull/272">PR-272</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/mkdocstrings/python/commit/5d2ba0aa557f683c3f7338d61810034c9af4ab11"><code>5d2ba0a</code></a>
chore: Prepare release 1.16.11</li>
<li><a
href="https://github.com/mkdocstrings/python/commit/7f956868f93a766346455fedb296c26787894d5c"><code>7f95686</code></a>
fix: Fix highlighting for signature with known special names like
<code>__init__</code></li>
<li><a
href="https://github.com/mkdocstrings/python/commit/0a35b20a6050a28ba8492d93e5f9940a69462ce3"><code>0a35b20</code></a>
fix: Use default font-size for parameter headings</li>
<li><a
href="https://github.com/mkdocstrings/python/commit/ba669697daad5067ea5db3fdf8a2d5ba2f966b25"><code>ba66969</code></a>
fix: Prevent uppercasing H5 titles (by Material for MkDocs)</li>
<li><a
href="https://github.com/mkdocstrings/python/commit/096960abd79831d6fd45e2a7962dfd2bd49e4edd"><code>096960a</code></a>
fix: Use configured heading even when signature is not separated</li>
<li><a
href="https://github.com/mkdocstrings/python/commit/d4e618ab794747b84dced848b1be824639fea2b8"><code>d4e618a</code></a>
fix: Render attribute names without full path in ToC</li>
<li><a
href="https://github.com/mkdocstrings/python/commit/bb36fa1fdc9d0fba1211fbfea2d50768b126a282"><code>bb36fa1</code></a>
chore: Template upgrade</li>
<li>See full diff in <a
href="https://github.com/mkdocstrings/python/compare/1.16.10...1.16.11">compare
view</a></li>
</ul>
</details>
<br />

Updates `pylint` from 3.3.6 to 3.3.7
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pylint-dev/pylint/commit/f798a4a3508bcbb8ad0773ae14bf32d28dcfdcbe"><code>f798a4a</code></a>
Bump pylint to 3.3.7, update changelog (<a
href="https://redirect.github.com/pylint-dev/pylint/issues/10367">#10367</a>)</li>
<li><a
href="https://github.com/pylint-dev/pylint/commit/f9773de127fe2dc3ec1254b8b6e280c19e4b1079"><code>f9773de</code></a>
Consistency between <code>is</code>/<code>is not</code> and
<code>==</code>/<code>!=</code> when comparing types...</li>
<li><a
href="https://github.com/pylint-dev/pylint/commit/05331112c04c4322656333075cdc077e199e7f7d"><code>0533111</code></a>
[fix] Fix a crash for class decorators mistaken for class attributes (<a
href="https://redirect.github.com/pylint-dev/pylint/issues/10362">#10362</a>)...</li>
<li><a
href="https://github.com/pylint-dev/pylint/commit/887b1b649b9deea0bf91023b23e11ed8cdaf8d72"><code>887b1b6</code></a>
[fix] AttributeError crash when a slice is used as a class decorator (<a
href="https://redirect.github.com/pylint-dev/pylint/issues/10350">#10350</a>)...</li>
<li><a
href="https://github.com/pylint-dev/pylint/commit/6a0239583f11ec2dfe46632f53e68fdc2f971d72"><code>6a02395</code></a>
[maintenance/3.3.x] Fix doc build (<a
href="https://redirect.github.com/pylint-dev/pylint/issues/10336">#10336</a>)</li>
<li><a
href="https://github.com/pylint-dev/pylint/commit/e87d10ca558ef032a64b4263ed876088ff5ccdd2"><code>e87d10c</code></a>
added black's github link in tutorial (<a
href="https://redirect.github.com/pylint-dev/pylint/issues/10303">#10303</a>)
(<a
href="https://redirect.github.com/pylint-dev/pylint/issues/10335">#10335</a>)</li>
<li><a
href="https://github.com/pylint-dev/pylint/commit/59d4c26a4ba8c8157da3cdf196d54bbc003be467"><code>59d4c26</code></a>
Fix typo in missing-member-hint-distance documentation (<a
href="https://redirect.github.com/pylint-dev/pylint/issues/10314">#10314</a>)
(<a
href="https://redirect.github.com/pylint-dev/pylint/issues/10315">#10315</a>)</li>
<li><a
href="https://github.com/pylint-dev/pylint/commit/46460d8b5f9257abd9b8342b187ac1dc96c1d419"><code>46460d8</code></a>
[setuptools] Force the upgrade of setuptools &gt; 77 (<a
href="https://redirect.github.com/pylint-dev/pylint/issues/10313">#10313</a>)</li>
<li><a
href="https://github.com/pylint-dev/pylint/commit/5b2aa6c7a471f43c3bfe7eceb4db074bb5d94802"><code>5b2aa6c</code></a>
Speed up the generation of no-member suggestions (<a
href="https://redirect.github.com/pylint-dev/pylint/issues/10277">#10277</a>)
(<a
href="https://redirect.github.com/pylint-dev/pylint/issues/10311">#10311</a>)</li>
<li><a
href="https://github.com/pylint-dev/pylint/commit/744ba539b81e620234e2d73fc7820d9611a99a86"><code>744ba53</code></a>
Fix crash caused by invalid format strings in <code>.format</code>
context (<a
href="https://redirect.github.com/pylint-dev/pylint/issues/10300">#10300</a>)
(<a
href="https://redirect.github.com/pylint-dev/pylint/issues/10">#10</a>...</li>
<li>See full diff in <a
href="https://github.com/pylint-dev/pylint/compare/v3.3.6...v3.3.7">compare
view</a></li>
</ul>
</details>
<br />

Updates `frequenz-repo-config[extra-lint-examples]` from 0.13.3 to
0.13.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/releases">frequenz-repo-config[extra-lint-examples]'s
releases</a>.</em></p>
<blockquote>
<h2>v0.13.4</h2>
<h1>Frequenz Repository Configuration Release Notes</h1>
<h2>Summary</h2>
<p>This release just widens the <code>setuptools</code> dependency to
allow for version 80.x.</p>
<h2>What's Changed</h2>
<ul>
<li>Clear release notes by <a
href="https://github.com/llucax"><code>@​llucax</code></a> in <a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/pull/393">frequenz-floss/frequenz-repo-config-python#393</a></li>
<li>Widen setuptools upper bound to &lt; 81 by <a
href="https://github.com/llucax"><code>@​llucax</code></a> in <a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/pull/401">frequenz-floss/frequenz-repo-config-python#401</a></li>
<li>Bump setuptools from 79.0.0 to 80.1.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/pull/400">frequenz-floss/frequenz-repo-config-python#400</a></li>
<li>Bump pydoclint from 0.6.4 to 0.6.6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/pull/397">frequenz-floss/frequenz-repo-config-python#397</a></li>
<li>Bump types-pyyaml from 6.0.12.20250326 to 6.0.12.20250402 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/pull/398">frequenz-floss/frequenz-repo-config-python#398</a></li>
<li>Update types-setuptools requirement from <!-- raw HTML omitted
-->=67.6.0,&lt;81 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/pull/399">frequenz-floss/frequenz-repo-config-python#399</a></li>
<li>Bump the patch group across 1 directory with 4 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/pull/402">frequenz-floss/frequenz-repo-config-python#402</a></li>
<li>Bump the minor group with 4 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/pull/404">frequenz-floss/frequenz-repo-config-python#404</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/compare/v0.13.3...v0.13.4">https://github.com/frequenz-floss/frequenz-repo-config-python/compare/v0.13.3...v0.13.4</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/commit/78ec7874752cf2bacf0db33fcdca52680cc9243d"><code>78ec787</code></a>
Bump the minor group with 4 updates (<a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/issues/404">#404</a>)</li>
<li><a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/commit/b68ebb91edd489049a332a61dea426678452c39b"><code>b68ebb9</code></a>
Bump the minor group with 4 updates</li>
<li><a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/commit/d788cbcb575a12ee89ab893a09c2add68ab913f7"><code>d788cbc</code></a>
Bump the patch group across 1 directory with 4 updates (<a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/issues/402">#402</a>)</li>
<li><a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/commit/1763b3e0bffa87b716ef82ec8fa37dd1a1fb7463"><code>1763b3e</code></a>
Bump the patch group across 1 directory with 4 updates</li>
<li><a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/commit/556d9f98bb6080e1194a837ddebb188a6fdd20a9"><code>556d9f9</code></a>
Bump pydoclint from 0.6.4 to 0.6.6 (<a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/issues/397">#397</a>)</li>
<li><a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/commit/ec5b00f09f88cb5641be5f3da1df1d7f831a0a86"><code>ec5b00f</code></a>
Bump types-pyyaml from 6.0.12.20250326 to 6.0.12.20250402 (<a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/issues/398">#398</a>)</li>
<li><a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/commit/30e8c0a90b480ac424c31d94e50e2d27892f0cf4"><code>30e8c0a</code></a>
Update types-setuptools requirement from &lt;80,&gt;=67.6.0 to
&gt;=67.6.0,&lt;81 (<a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/issues/399">#399</a>)</li>
<li><a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/commit/961db043eea74c5f24c5939e80c2b24de61fce5a"><code>961db04</code></a>
Bump setuptools from 79.0.0 to 80.1.0 (<a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/issues/400">#400</a>)</li>
<li><a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/commit/338a87feb9886ec5c06915cbc7d1816229883b8e"><code>338a87f</code></a>
Widen setuptools upper bound to &lt; 81 (<a
href="https://redirect.github.com/frequenz-floss/frequenz-repo-config-python/issues/401">#401</a>)</li>
<li><a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/commit/991c35e636368e1c1608ab1e9276b6a81487d3a1"><code>991c35e</code></a>
Widen setuptools upper bound to &lt; 81</li>
<li>Additional commits viewable in <a
href="https://github.com/frequenz-floss/frequenz-repo-config-python/compare/v0.13.3...v0.13.4">compare
view</a></li>
</ul>
</details>
<br />

Updates `pytest-mock` from 3.14.0 to 3.14.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pytest-dev/pytest-mock/releases">pytest-mock's
releases</a>.</em></p>
<blockquote>
<h2>v3.14.1</h2>
<ul>
<li><a
href="https://redirect.github.com/pytest-dev/pytest-mock/pull/503">#503</a>:
Python 3.14 is now officially supported.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/pytest-dev/pytest-mock/blob/main/CHANGELOG.rst">pytest-mock's
changelog</a>.</em></p>
<blockquote>
<h2>3.14.1 (2025-08-26)</h2>
<ul>
<li><code>[frequenz-floss#503](pytest-dev/pytest-mock#503)
&lt;https://github.com/pytest-dev/pytest-mock/pull/503&gt;</code>_:
Python 3.14 is now officially supported.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pytest-dev/pytest-mock/commit/34dd61aa459520e096c70eb8a573700fc17c5de8"><code>34dd61a</code></a>
Release 3.14.1</li>
<li><a
href="https://github.com/pytest-dev/pytest-mock/commit/299adb96640a51a48b5af9a69064b9edd7a9fe90"><code>299adb9</code></a>
Add support for Python 3.14 (<a
href="https://redirect.github.com/pytest-dev/pytest-mock/issues/503">#503</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest-mock/commit/f5fcef726a8ba4a54cd138321ae9771648a0bc8a"><code>f5fcef7</code></a>
[pre-commit.ci] pre-commit autoupdate (<a
href="https://redirect.github.com/pytest-dev/pytest-mock/issues/504">#504</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest-mock/commit/bae64d8c8ef44a7075d63f1d7f6ac36b76b61ce4"><code>bae64d8</code></a>
[pre-commit.ci] pre-commit autoupdate (<a
href="https://redirect.github.com/pytest-dev/pytest-mock/issues/502">#502</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest-mock/commit/824f334cc4b39eb05c0093fc43411ada3fdc8300"><code>824f334</code></a>
[pre-commit.ci] pre-commit autoupdate (<a
href="https://redirect.github.com/pytest-dev/pytest-mock/issues/501">#501</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest-mock/commit/db1add63034430d66835c78992c0ed9b1e331cfd"><code>db1add6</code></a>
[pre-commit.ci] pre-commit autoupdate (<a
href="https://redirect.github.com/pytest-dev/pytest-mock/issues/500">#500</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest-mock/commit/48ac8746b6587457becf31d1272947de6d65e0d0"><code>48ac874</code></a>
[pre-commit.ci] pre-commit autoupdate (<a
href="https://redirect.github.com/pytest-dev/pytest-mock/issues/499">#499</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest-mock/commit/fe7ad9aab6a8e15e5762d5bdc85402249f2ca7ef"><code>fe7ad9a</code></a>
[pre-commit.ci] pre-commit autoupdate (<a
href="https://redirect.github.com/pytest-dev/pytest-mock/issues/498">#498</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest-mock/commit/7857e608242aeb3d4b771296ee41d258b1a13838"><code>7857e60</code></a>
[pre-commit.ci] pre-commit autoupdate (<a
href="https://redirect.github.com/pytest-dev/pytest-mock/issues/497">#497</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest-mock/commit/a8b97ea2ca86e9cfa553e395cf20352a881d8eaf"><code>a8b97ea</code></a>
[pre-commit.ci] pre-commit autoupdate (<a
href="https://redirect.github.com/pytest-dev/pytest-mock/issues/496">#496</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/pytest-dev/pytest-mock/compare/v3.14.0...v3.14.1">compare
view</a></li>
</ul>
</details>
<br />

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions

</details>
@shsms shsms force-pushed the auto-closing-broadcast branch 2 times, most recently from 9b6460b to 7ad1cc9 Compare March 30, 2026 09:56
@shsms shsms marked this pull request as ready for review March 30, 2026 10:03
@shsms shsms requested a review from a team as a code owner March 30, 2026 10:03
@shsms shsms requested review from Copilot and florian-wagner-frequenz and removed request for a team March 30, 2026 10:03
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new auto-closing broadcast channel API (BroadcastChannel) and deprecates the legacy Broadcast/Anycast channel classes, aiming to make broadcast channels close automatically when one side (senders or receivers) is fully gone/closed.

Changes:

  • Add BroadcastChannel (tuple-style constructor returning an initial BroadcastSender/BroadcastReceiver) and rename broadcast endpoint classes to BroadcastSender/BroadcastReceiver.
  • Add new sender capability interfaces (SubscribableSender, ClonableSender, ClonableSubscribableSender) and export them publicly.
  • Deprecate Broadcast and Anycast, update release notes, and add tests covering broadcast auto-close behavior.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
tests/test_broadcast.py Updates overflow test type assertions and adds auto-close tests for BroadcastChannel.
src/frequenz/channels/_sender.py Adds new sender capability ABCs (subscribe/clone) for use by broadcast senders.
src/frequenz/channels/_broadcast.py Implements auto-close mechanics, introduces BroadcastChannel, and deprecates Broadcast.
src/frequenz/channels/_anycast.py Deprecates Anycast.
src/frequenz/channels/init.py Exports new broadcast types and sender capability interfaces.
RELEASE_NOTES.md Documents the new channel APIs and deprecations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@shsms shsms force-pushed the auto-closing-broadcast branch 3 times, most recently from aa429d1 to ec05bfb Compare March 30, 2026 13:11
Copy link
Copy Markdown
Contributor

@llucax llucax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there might be a few bugs left. If those are real bugs, it would be good to add test cases for them too.

"""Clean up this sender."""
try:
if not self._closed:
self._channel._sender_count -= 1
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can probably fail because when this instance is deleted by the GC because there is no guarantee in order, self._channel might be already destroyed when this runs. I think we should probably leave the __del__ out and required users to properly close receivers explicitly/using them as context managers. Also not calling the channel's aclose() could still leave things in a bad state.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, that's why copilot was suggesting getattr instead I guess. It also recommended wrapping the whole thing under try:except Exception:pass, which I did. So it might work or might not, but won't make things worse, right?

aclose() on channel wakes up all receivers, so they can stop waiting. If all senders are gone, we call channel.aclose():

if (
self._channel._sender_count == 0 # pylint: disable=protected-access
and self._channel._auto_close_enabled # pylint: disable=protected-access
):
await self._channel.aclose()

There's no other place where we need to call channel.aclose(). Not when all the receivers have gone.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be a real problem. The __del__() can still end up in blocked receivers:

This times out after a second:

import gc

async def test_broadcast_receive_unblocks_when_last_sender_is_garbage_collected() -> None:
    """A blocked receiver should stop promptly when the last sender disappears."""
    sender, receiver = BroadcastChannel[int](name="auto-close-test")
    sender_2 = sender.clone()

    await sender.aclose()

    receive_task = asyncio.create_task(receiver.receive())
    await asyncio.sleep(0)

    # Adding await sender_2.aclose() here fixes it, of course
    del sender_2
    gc.collect()

    with pytest.raises(ReceiverStoppedError):
        async with asyncio.timeout(1):
            await receive_task

I would remove the __del__, it doesn't really help much and it is wrong. You should never access other mutable objects in the __del__ anyway, it is intended only to free resources like from external C libraries or stuff like that, they are not proper destructors like in other languages).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, removed.

Comment on lines 596 to 607
@override
def close(self) -> None:
"""Close the receiver.

After calling this method, new messages will not be received. Once the
receiver's buffer is drained, trying to receive a message will raise a
[`ReceiverStoppedError`][frequenz.channels.ReceiverStoppedError].
"""
self._closed = True
self._channel._receivers.pop( # pylint: disable=protected-access
hash(self), None
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this also trigger a closing of the channel if this is the last receiver? I guess to make this happen we need to transition to aclose() for receivers, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the closing of the channel is really a closing of the receivers. But if none are left, there's nothing for the channel close to do.

We might still consider aclose for receivers, but separate problem I guess.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, the "issue" here is, if we don't close the channel (don't mark it as closed), a new subscribe() can "resurrect" the channel. I'm not sure if this is intended behavior, it might be, but if it is we should document it more clearly. I'm not sure if it is a good idea though, because if we do this it means once you got SenderClosedError for a sender, a new send() might succeed, which sounds weird to me.

Suggested new test (that doesn't allow for resurrection, could be adapted if we want to allow it)

async def test_broadcast_auto_close_cannot_be_resurrected_by_new_subscription() -> None:
    """Closing the last receiver should keep the channel closed."""
    sender, receiver = BroadcastChannel[int](name="auto-close-test")

    receiver.close()

    with pytest.raises(SenderError) as excinfo:
        await sender.send(1)
    assert isinstance(excinfo.value.__cause__, ChannelClosedError)

    late_receiver = sender.subscribe()  # <--- This should probably raise SenderError/ChannelClosedError

    with pytest.raises(SenderError) as excinfo:
        await sender.send(2)  # <--- This doesn't raise now
    assert isinstance(excinfo.value.__cause__, ChannelClosedError)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Also interesting that we allowed new_receiver calls from closed channels. Won't be relevant soon though.

Comment on lines 384 to 399
self._channel
)
async with self._channel._recv_cv:
self._channel._recv_cv.notify_all()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't self._channel._latest = message and recv._enqueue(message)be done after all the checks are done (right before we notify all receivers)? Otherwise it might be we report a send failed, but still receivers will receive the message.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If self.channel.receivers is already empty, there won't be a enqueue call. There would just be a sender error.

If self.channels.receivers has only stale receivers, they would get cleaned up first, enqueue will not be called. Then there would be a sender error.

If there are active receivers, enqueue will be called on them. They are not stale, so they will stay. No sender error will be raised.

The sendererror happens below, just to make sure we've dropped stale receivers first.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so it is only about _latest. It seems updating _latest doesn't have a visible effect. This works as expected:

async def test_broadcast_failed_send_does_not_update_latest_message() -> None:
    """A send that fails because the channel is closed should not mutate _latest."""
    sender, receiver = BroadcastChannel[int](
        name="auto-close-test",
        resend_latest=True,
    )
    await sender.send(1)
    await sender.aclose()
    with pytest.raises(SenderError):
        await sender.send(2)
    assert await receiver.receive() == 1

That said, it is weird to me that _latest is updated even if the send failed. Unless there is a very good reason to not move it to after it is really sent, I don't see why we shouldn't do it:

async def test_broadcast_failed_send_does_not_update_latest_message() -> None:
    """A send that fails because the channel is closed should not mutate _latest."""
    sender, receiver = BroadcastChannel[int](
        name="auto-close-test",
        resend_latest=True,
    )
    await sender.send(1)
    assert await receiver.receive() == 1
    receiver.close()

    with pytest.raises(SenderError):
        await sender.send(2)

    assert sender._channel._latest == 1  # <--- fails because it was updated to 2 anyways

Copy link
Copy Markdown
Contributor

@llucax llucax Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to this, but I just noticed sometimes we raise SenderClosedError (sender._closed is True) and sometimes SenderError from ChannelClosedError (sender._channel._closed is True). I wonder if we should use SenderClosedError from ChannelClosedError instead, it sounds like if a channel is closed the sender should be closed too. I guess it relates to the question if senders/channels can be resurrected by re-subscribing or not.

That said, I guess this comes from before, and we are doing it like this in other places, so I'm OK to leave the broader question for another time.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why we shouldn't do it

I wasn't objecting, I just wasn't aware of the issue. I've moved the line updating _latest to after the raise.

@shsms shsms requested a review from llucax March 31, 2026 09:42
@shsms shsms force-pushed the auto-closing-broadcast branch from ec05bfb to b19d4a3 Compare April 7, 2026 09:41
Signed-off-by: Sahas Subramanian <sahas.subramanian@proton.me>
shsms added 11 commits April 7, 2026 11:41
Signed-off-by: Sahas Subramanian <sahas.subramanian@proton.me>
Signed-off-by: Sahas Subramanian <sahas.subramanian@proton.me>
Signed-off-by: Sahas Subramanian <sahas.subramanian@proton.me>
The `BroadcastChannel` would return a sender and a receiver from an
auto-closing channel.

Signed-off-by: Sahas Subramanian <sahas.subramanian@proton.me>
Signed-off-by: Sahas Subramanian <sahas.subramanian@proton.me>
Signed-off-by: Sahas Subramanian <sahas.subramanian@proton.me>
Now that `BroadcastReceiver` is a public type, its internal methods
need to become protected.

Signed-off-by: Sahas Subramanian <sahas.subramanian@proton.me>
Signed-off-by: Sahas Subramanian <sahas.subramanian@proton.me>
Signed-off-by: Sahas Subramanian <sahas.subramanian@proton.me>
Signed-off-by: Sahas Subramanian <sahas.subramanian@proton.me>
Signed-off-by: Sahas Subramanian <sahas.subramanian@proton.me>
@shsms shsms force-pushed the auto-closing-broadcast branch from b19d4a3 to 3843e3f Compare April 7, 2026 09:50
@shsms
Copy link
Copy Markdown
Contributor Author

shsms commented Apr 7, 2026

Ready again @llucax

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

part:channels Affects channels implementation part:core Affects the core types (`Sender`, `Receiver`, exceptions, etc.) part:docs Affects the documentation part:experimental Affects the experimental package part:tests Affects the unit, integration and performance (benchmarks) tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants