fix: a clock glitch is bridged and a channel is known by name, so a capture keeps its t0
Two defects behind one report ("a trigger during a capture overrides the first,
and the hold-off does not apply"); the second is also what made a spike in one
channel look like the acquisition resetting.
The trigger engine keyed its invalidation on the trigger channel's *index*, which
every rebuild of the channel list renumbers -- enabling a signal that sorts before
it is enough. Any rebuild therefore abandoned an open capture and set no hold-off,
so the next edge inside the post window became t0. It keys on the channel's name
now, which changes only when the source genuinely does.
A packet whose stamps jump more than 256 packet spans ahead is now rejected as a
fault and *bridged* at the declared cadence, instead of poisoning the monotonic
mark (which pinned every later packet flat at the glitched value) and condemning
the next good packet. Only four consecutive failures abandon the declared time
signal, and that fallback no longer wipes the rings: the arrival clock is anchored
to continue from the monotonic mark, so the history is still on one timeline --
the wipe is what made one glitching packet look like the stream restarting from 0.
The declared unit is resolved before those checks, since they weigh the stamps
against a cadence in seconds.
Guards: UDPSCOPE_PROBE_TRIGRENUM_MS prints t0_held=1 through the real widgets,
and the mock's --time-glitch-at/--time-glitch-by injects the clock fault.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
4003084648
commit
f179fbbb44
13 files changed
+406
-96
No files matched your search
@@ -44,6 +44,13 @@ precedent, not an exception.
|
||||
`UDPSCOPE_TRIG_TRACE_MS` — schedule individual probes. `UDPSCOPE_PROBE_DISARM_MS` only
|
||||
*starts* its sequence: it switches the trigger on and then polls for a capture to open,
|
||||
so the Disarm lands ~300 ms into one rather than wherever a fixed delay happened to fall.
|
||||
- `UDPSCOPE_PROBE_TRIGRENUM_MS` — the trigger's capture must survive a **renumbering of its own
|
||||
channel**. It sets the reported geometry through the real widgets (1 s window, 10 % pre share,
|
||||
hold-off from `--holdoff`) on the mock's *square* alone, polls for a capture to open, and then
|
||||
enables the sine — which sorts before the square and so moves the trigger channel's index under
|
||||
the engine. Its three lines are in [probe-readings.md](probe-readings.md); **`t0_held=1` is the
|
||||
assertion**. Run it with `--holdoff 10`, which makes the stolen-t0 reading unmistakable: a second
|
||||
latch barely a second after the first cannot be a hold-off that applied.
|
||||
- `UDPSCOPE_PROBE_FIELDS_MS` — change the trigger panel's edge and Normal/Single
|
||||
dropdowns. As much a crash test as a state check (see the GTK signal-signature invariant
|
||||
below), so time it inside a capture: `[fields-probe] ... capturing=1` followed by no
|
||||
@@ -170,6 +177,33 @@ precedent, not an exception.
|
||||
`/tmp/udpscope-export`) — collect the export table and write it both ways, skipping
|
||||
only the file chooser.
|
||||
|
||||
The mock also carries one **fault injection** that no probe schedules, because what it exercises is
|
||||
the ingest rather than the UI: `--time-glitch-at <stream seconds> --time-glitch-by <seconds>` makes
|
||||
**one** packet's time signal jump ahead and then carries on from where the true timeline was, which
|
||||
is what a device whose clock glitches puts on the wire. Run it with the trigger armed, e.g.
|
||||
|
||||
```sh
|
||||
xvfb-run -a env UDPSCOPE_ENABLE_ALL_MS=400 UDPSCOPE_ARM_TRIGGER_MS=700 UDPSCOPE_EXIT_AFTER_MS=22000 \
|
||||
./build/udpscope --server 127.0.0.1 --port 8000 --holdoff 10
|
||||
# mock: --rate 10000 --batch 100 --freq 5 --time-glitch-at 6.0 --time-glitch-by 10.0
|
||||
```
|
||||
|
||||
Four readings, all off the once-a-second `[udpscope] trig engine:` line:
|
||||
|
||||
- exactly one `warning: ... jumped implausibly far ahead of the previous packet (16.01..16.0199
|
||||
against a previous 6.0099); bridging that packet ...` — the glitch was **caught**;
|
||||
- **no** `timing samples by arrival instead` — one bad packet must not change the time source;
|
||||
- `now_t` crosses the glitch continuously (`5.040 → 6.050 → 7.050`), and `history` keeps **growing**
|
||||
(`1.01 … 23.15`) instead of collapsing — the ring was not wiped;
|
||||
- the latches stay a hold-off apart: `edge at 1.060 / latched at 2.060 / holdoff_until=12.060`, then
|
||||
`edge at 12.260 / latched at 13.260 / holdoff_until=23.260` — **11.2 s** for a 10 s hold-off plus
|
||||
a 0.9 s post window.
|
||||
|
||||
Before the fix the same run read a fallback warning, `history` collapsing 4.03 → 0.95, `now_t`
|
||||
jumping 5.040 → 16.040, and two latches **1.2 s** apart. Note the window here comes from the saved
|
||||
session for that port; the figures above are the default 2 s window (50 % pre), so move
|
||||
`~/.config/udpscope/<host>_<port>.toml` aside to reproduce them.
|
||||
|
||||
**Run one self-check at a time.** Two concurrent `xvfb-run -a` instances end up on the
|
||||
same display and the second gets no GL context — it dumps `layout over 0 frames` and
|
||||
`valid=-1`, which looks like a pipeline failure and is not one. `tools/mock_udpstreamer.py`
|
||||
|
||||
Reference in new issue
Block a user