homie: inbound /set async dispatch, thread-safe and promoted to the root - #23
Merged
Conversation
…oot (#15) - Dispatch: schedule an async /set callback onto the consumer's loop with asyncio.run_coroutine_threadsafe (ensure_future is not safe from paho's network thread, where /set arrives). Branch on the callback's actual return (iscoroutine), not async_loop's presence, so a SYNC callback runs inline even when a device-level loop is set. Surface exceptions the discarded scheduling Future would otherwise swallow, matching the sync path's logging. - Promotion: async_loop is now Device(async_loop=), propagated to every property via add_node()/Node.add_property() (mirroring _qos), children inheriting the root's loop. The per-Property async_loop still works and is kept when the device sets none. - Fix the Optional[...] = False default to = None (bool vs loop annotation); widen to AbstractEventLoop. - Fix Node's mutable default properties: dict = {} (shared-dict footgun the new propagation reads/writes through). docs: CHANGELOG [Unreleased] (Added Device(async_loop=), Fixed dispatch/defaults); README BYO note on async /set. Reviewed adversarially; 4 findings fixed. +10 tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #15. The inbound half of the async-host story.
What it does
/setarrives on the transport's network-loop thread. An async (coroutine)set_callbackis now scheduled onto the consumer's event loop withasyncio.run_coroutine_threadsafeinstead ofasyncio.ensure_future(which is not safe to call from a thread other than the loop's own). The branch is on the callback's actual return (asyncio.iscoroutine), not onasync_loop's presence, so a synchronous callback keeps running inline even when a tree-wide loop is set. An exception raised inside an async callback is logged (the discarded scheduling Future would otherwise swallow it).async_loopis now aDevice(async_loop=...)param that propagates to every property viaadd_node()/Node.add_property()(mirroring_qos) and is inherited by child devices. A tree with N settable properties sets the loop once, not N times. The per-Propertyasync_loopstill works and is preserved when the device sets none.Optional[...] = False->Optional[asyncio.AbstractEventLoop] = None(a bool against the loop annotation, surfaced bypy.typed).properties: dict = {}->Optional[dict] = None(a shared-dict footgun the new propagation reads/writes through).Review
Ran a 2-lens adversarial review (correctness/thread-safety + compat/edge-cases) with a per-finding refutation pass; 4 confirmed, all fixed. The load-bearing one: a device-level loop was forcing sync callbacks onto the async path (
run_coroutine_threadsafe(None, loop)->TypeErroron every/set), which theiscoroutinebranch fixes.Tests / docs
+10 tests (dispatch, propagation, child inheritance, override precedence, backward-compat, sync-inline-under-device-loop, async-exception logging). 543 total; the 3.10-3.13 matrix runs on this PR; ruff clean. CHANGELOG
[Unreleased]and a one-line README note added.🤖 Generated with Claude Code