Repository navigation
Conversation
Adding a timedelta to a timezone-aware datetime is wall-clock arithmetic
and resets `fold`. Inside the autumn DST fold `candidate + timedelta(
seconds=1)` can therefore land a full hour BEFORE `candidate` in absolute
time, so the `while search_pos < ...` loops in TransitSensor and
AntitransitSensor keep re-finding the same event and never terminate.
Because the search runs synchronously in `async_added_to_hass`, the whole
Home Assistant event loop is blocked: the instance accepts TCP connections
on 8123 but never answers them.
Reproduced with Europe/Warsaw on 2026-10-25, when the Uranus transit fell
at 02:00:24 fold=1:
candidate -> UTC 2026-10-25 01:00:24
candidate + 1s -> UTC 2026-10-25 00:00:25 (one hour earlier)
Since `_RISE_SET_PRIOR_WINDOW_DAYS` is 14, every restart in the two weeks
after a DST change hits this.
Fix: `_advance_one_second()` performs the step in UTC, keeping it
monotonic, and three `if candidate <= last_found: break` guards make the
loops terminate even if another non-monotonic step is ever introduced.
Verified offline against real ephemerides with ZoneInfo('Europe/Warsaw'):
uranus went from >300 iterations without terminating to 4 iterations in
0.1 s; mercury, saturn and neptune returned identical results before and
after.
Refs: okkine#3, okkine#2
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Keeps the fork installable from HACS as a custom repository until the DST fix lands upstream (okkine#4). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rise, Set, Transit and Antitransit did all their ephemeris work inline in async_update, which async_added_to_hass awaits, so every entity's first update ran its searches on the event loop. With ten bodies that is enough to trip Home Assistant's own warning, "Setup of sensor platform sol is taking over 10 seconds" (issue okkine#2), and to stall everything else running in the instance. Each of those four classes now has a plain _compute() holding exactly the work it did before; async_update hands that to async_add_executor_job and only writes the state. Azimuth and Elevation keep their structure (they await the ephemeris cache mid-update) but their find_step_aligned_targets calls, the expensive part, now go through the executor as well. Measured on a bare Home Assistant 2026.2.3 with one location, all ten bodies and every sensor enabled (64 entities), timing how late a 20 ms heartbeat fires while the platform sets up, two cold runs each: event loop blocked longest stall stalls > 500 ms before 23.3 s / 14.7 s 2681 / 1577 ms 11 / 10 after 5.8 s / 2.7 s 795 / 222 ms 3 / 0 Setup wall time is unchanged, as expected: the same work is done, just off the loop. All 64 entities come out with the same states and attributes before and after (compared allowing 0.5 deg and 120 s of drift between runs, since the runs are minutes apart). Co-Authored-By: Claude Opus 5 <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 #2.
RiseSensor,SetSensor,TransitSensorandAntitransitSensordo all their ephemeris work inline inasync_update, andasync_added_to_hassawaits it, so every entity's first update runs its searches on the event loop. With ten bodies that is enough to trip Home Assistant's own warning:and to stall everything else in the instance while it happens.
The change
Each of those four classes gets a plain
_compute()holding exactly the work it did before;async_updatehands that toasync_add_executor_joband only writes the state afterwards.AzimuthSensorandElevationSensorkeep their structure, because they await the ephemeris cache mid-update, but theirfind_step_aligned_targetscalls, the expensive part, go through the executor too.No search was added, removed or reordered. I verified that mechanically: for all ten sensor classes, the set and the source order of observer calls (with the executor and
partialwrappers unwrapped) is identical before and after.Measurements
Bare Home Assistant 2026.2.3, one location, all ten bodies, every sensor enabled (64 entities). A 20 ms heartbeat runs alongside the platform setup; how late it fires is how long the loop was blocked. Two cold runs each (storage wiped between runs):
Setup wall time is unchanged (25.3 / 16.5 s before, 22.9 / 14.8 s after): the same work happens, just not on the loop.
All 64 entities come out with the same states and attributes before and after, allowing 0.5 deg and 120 s of drift since the two runs are minutes apart. Zero differences.
🤖 Generated with Claude Code