fix: ignore garbled measurement-state reads (phantom STOPPED/STARTED)
A buffer desync on the shared persistent connection (commonly right after
a DRD/DOD test) can make a Measure? read return a stray value. The state
classifier treated anything not in {"Start","Measure"} as "not measuring",
so a garbled read logged a phantom STOPPED, the next clean read logged
STARTED, and that reset measurement_start_time — producing constant
STOPPED/STARTED device-log pairs and a drifting elapsed timer.
Now only recognized states drive transitions: {"Start","Measure"} =
measuring, {"Stop"} = stopped, anything else = no change. Garbled reads
are also not persisted as the cached state, so they can't poison the next
transition check. Builds on the earlier Start<->Measure normalization.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+18
-4
@@ -75,13 +75,24 @@ def persist_snapshot(s: NL43Snapshot, db: Session):
|
|||||||
# but the DRD stream path tags snapshots "Measure" (and the DOD fallback also
|
# but the DRD stream path tags snapshots "Measure" (and the DOD fallback also
|
||||||
# uses "Measure"). Treat ALL of these as "measuring" — otherwise opening and
|
# uses "Measure"). Treat ALL of these as "measuring" — otherwise opening and
|
||||||
# closing the live stream flips state "Start"->"Measure"->"Start", which the
|
# closing the live stream flips state "Start"->"Measure"->"Start", which the
|
||||||
# old equality check misread as stop-then-start and RESET measurement_start_time
|
# old equality check misread as stop-then-start and reset measurement_start_time.
|
||||||
# every single time (the "elapsed time keeps resetting / shows wrong value on
|
#
|
||||||
# another computer" bug — and each extra viewer made it worse).
|
# Also: only act on RECOGNIZED states. A buffer desync on the shared connection
|
||||||
|
# (e.g. right after a DRD/DOD test) can make a Measure? read return a stray,
|
||||||
|
# garbled value; treating that as "not measuring" produced constant phantom
|
||||||
|
# "STOPPED -> STARTED" log pairs and reset the timer. Ignore unknown reads.
|
||||||
MEASURING_STATES = {"Start", "Measure"}
|
MEASURING_STATES = {"Start", "Measure"}
|
||||||
is_measuring = new_state in MEASURING_STATES
|
STOPPED_STATES = {"Stop"}
|
||||||
was_measuring = previous_state in MEASURING_STATES
|
was_measuring = previous_state in MEASURING_STATES
|
||||||
|
|
||||||
|
if new_state in MEASURING_STATES:
|
||||||
|
is_measuring = True
|
||||||
|
elif new_state in STOPPED_STATES:
|
||||||
|
is_measuring = False
|
||||||
|
else:
|
||||||
|
logger.warning(f"Ignoring unrecognized measurement state for {s.unit_id}: {new_state!r}")
|
||||||
|
is_measuring = was_measuring # garbled/unknown read — no transition
|
||||||
|
|
||||||
if not was_measuring and is_measuring:
|
if not was_measuring and is_measuring:
|
||||||
# Measurement just started - record the start time
|
# Measurement just started - record the start time
|
||||||
row.measurement_start_time = datetime.utcnow()
|
row.measurement_start_time = datetime.utcnow()
|
||||||
@@ -103,6 +114,9 @@ def persist_snapshot(s: NL43Snapshot, db: Session):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Only persist a recognized state so one garbled read can't poison the next
|
||||||
|
# transition check (which would manufacture the phantom STOPPED/STARTED pair).
|
||||||
|
if new_state in MEASURING_STATES or new_state in STOPPED_STATES:
|
||||||
row.measurement_state = new_state
|
row.measurement_state = new_state
|
||||||
row.counter = s.counter
|
row.counter = s.counter
|
||||||
row.lp = s.lp
|
row.lp = s.lp
|
||||||
|
|||||||
Reference in New Issue
Block a user