+- `Property` now serializes "compute the payload, publish it, record what was published" under a per-property reentrant lock, so the publish-on-change memo can never disagree with the last write that actually reached the wire. Two threads reach that sequence: the application thread via `set_value()`, and the MQTT loop thread via `on_connect` → `refresh_tree(force=True)`. Interleaved, the loop thread could publish the old payload, the application thread could then publish and memoize the new one, and the loop thread could finally overwrite the memo with the older payload it had sent first. The property then believed the broker held a value it did not, and the 0.20.0 gate suppressed the very publish that would have corrected it, so the wrong retained value persisted until the next genuine change or reconnect. The window is narrow (it needs a reconnect refresh concurrent with a value update) and the class has never had a lock, so `_value` and `_ever_published` were already exposed to it in kind; 0.20.0 made the consequence durable rather than transient, which is what moves this from a latent wart to a fix. The lock is reentrant because `set_value()` calls `publish_value()` calls `clear_value()`, each taking it; a plain `Lock` self-deadlocks on the commonest call in the SDK. It is per-property, so it never serializes a tree walk, and no path holds two, so there is no ordering hazard. It is deliberately held across the transport's `publish()`: releasing earlier reopens the window it exists to close. No API change. ([#50](https://github.com/electrification-bus/python-sdk/issues/50))
0 commit comments