Retire expired tracker awake leases before foreground deferrals

This commit is contained in:
mikecarper committed 2026-10-16 18:37:24 -07:00
1 parent eda70c832b
commit 8e13fccdd1
6 files changed
+228 -7

No files matched your search

+113 -3
View File
@@ -29,11 +29,12 @@ static void configure(SensorMesh& mesh) {
assert(mesh.alert1.text[0] == 0 && mesh.alert2.text[0] == 0);
assert(!mesh.next_local_advert && !mesh.next_flood_advert);
}
static Packet* queueCheck(SensorMesh& mesh, bool gps = true) {
mesh.serviceTracker();
static Packet* queueCheck(SensorMesh& mesh, bool gps = true, bool foreground = false) {
const auto service = [&]() { if (foreground) mesh.loop(); else mesh.serviceTracker(); };
service();
assert(mesh.tracker_acquiring && !mesh.tracker_pending && sensors.pending);
if (gps) sensors.fix(); else { sensors.pending = false; sensors.fresh = false; }
mesh.serviceTracker();
service();
assert(!mesh.tracker_acquiring && mesh.tracker_pending && !mesh.tracker_sent);
assert(mesh.pool.queued.size() == 1);
assert(mesh.last_payload.size() == 4 + RequestLength);
@@ -164,6 +165,112 @@ static void cadence() {
++scenarios;
}
static void awakeExpiry() {
for (uint32_t started : {100U, UINT32_MAX - 60000U}) {
for (unsigned path = 0; path < 4; ++path) {
SensorMesh mesh; mesh.now_ms = started; configure(mesh);
assert(command(mesh, "set tracker.lost.interval 86400") == "OK");
queueCheck(mesh, true, true); transmitted(mesh); assert(response(mesh, Lost, 120));
const auto before = mesh.tracker;
const auto writes = mesh.fs.write_opens;
const uint32_t deadline = uint32_t(started + 120000U);
mesh.now_ms = deadline; mesh.rtc.now += 120;
mesh.loop();
assert(mesh.tracker_awake_until == deadline && mesh.trackerNeedsRadio());
assert(!mesh.tracker_radio_asleep); // Production expiry is strictly > 0.
if (path == 1) {
mesh.tracker_radio_asleep = mesh.radio.sleeping = true; mesh.available = false;
} else if (path == 2) mesh.radio.morse_tx = true;
else if (path == 3) mesh.radio.morse_rx = true;
const auto wakes = mesh.radio.wakes, loops = mesh._clock_sync.loops;
const auto cli_loops = mesh._cli.loops, base_loops = mesh.base_loops;
++mesh.now_ms; mesh.loop();
assert(mesh._cli.loops == cli_loops + 1 && mesh.base_loops == base_loops + 1);
assert(!mesh.tracker_awake_until && "observed expiry must retire the tracker awake lease");
assert(mesh.trackerNeedsRadio() == (path >= 2) && mesh.radio.wakes == wakes);
assert(mesh.tracker.last_lost == Lost && mesh.tracker.generation == before.generation);
assert(mesh.tracker_next_check == mesh.rtc.now - 120 + 86400);
assert(mesh.fs.write_opens == writes && !mesh.tracker_pending && !mesh.tracker_acquiring);
if (path < 2) assert(mesh.tracker_radio_asleep && !mesh.available);
else assert(mesh._clock_sync.loops == loops); // Still defers optional Morse work.
// Observation of expiry, not a half-cycle without servicing, is the
// supported guarantee. A retained deadline used to become future again.
mesh.now_ms += 0x80000000U; mesh.loop();
assert(mesh.trackerNeedsRadio() == (path >= 2) && !mesh.tracker_awake_until);
assert(mesh.radio.wakes == wakes && mesh.tracker.last_lost == Lost);
mesh.radio.morse_tx = mesh.radio.morse_rx = false; mesh.loop();
assert(!mesh.trackerNeedsRadio() && mesh.tracker_radio_asleep);
++scenarios;
}
}
}
static void awakeOwners() {
SensorMesh mesh; configure(mesh);
assert(command(mesh, "set tracker.lost.interval 86400") == "OK");
queueCheck(mesh, true, true); transmitted(mesh); assert(response(mesh, Lost, 120));
mesh.now_ms += 120001; mesh.rtc.now += 120; mesh.loop();
assert(!mesh.tracker_awake_until && mesh.tracker_radio_asleep);
const auto next = mesh.tracker_next_check;
// The actual local-command lease runs before the following foreground pass.
mesh.localConsoleLease(1); assert(!mesh.tracker_awake_until && mesh.tracker_radio_asleep);
mesh.localConsoleLease(0); mesh.loop();
assert(mesh.tracker_awake_until == uint32_t(mesh.now_ms + 120000));
assert(mesh.trackerNeedsRadio() && !mesh.tracker_radio_asleep && mesh.tracker_next_check == next);
// Renewal during CLI service must survive retirement of the preceding lease.
mesh.now_ms += 120001; mesh.rtc.now += 120; mesh._cli.local_command = true; mesh.loop();
assert(mesh.tracker_awake_until == uint32_t(mesh.now_ms + 120000) && mesh.trackerNeedsRadio());
bool* owners[] = {&mesh.board.usb, &mesh.board.ota, &mesh.board.test, &mesh.temporary,
&mesh.pending_ota, &mesh.dual, &mesh.saved_radio_apply_pending, &mesh._cli.active,
&mesh.queue_wake, &mesh.retry_wake, &mesh::test_logging_watchdog};
for (bool* owner : owners) {
*owner = true; mesh.tracker_awake_until = mesh.now_ms - 1; mesh.loop();
assert(!mesh.tracker_awake_until && mesh.trackerNeedsRadio() && !mesh.tracker_radio_asleep);
assert(mesh.tracker.last_lost == Lost && mesh.tracker_next_check == next);
*owner = false;
}
for (bool pending : {false, true}) {
mesh.tracker_next_check = mesh.rtc.now; mesh.loop();
assert(mesh.tracker_acquiring && sensors.pending);
if (pending) { sensors.fix(); mesh.loop(); transmitted(mesh); }
const auto tag = mesh.tracker_pending_tag;
mesh.tracker_awake_until = mesh.now_ms - 1; mesh.loop();
assert(!mesh.tracker_awake_until && mesh.trackerNeedsRadio() && !mesh.tracker_radio_asleep);
assert(mesh.tracker_pending == pending && mesh.tracker_acquiring == !pending);
assert(mesh.tracker_pending_tag == tag && mesh.tracker.last_lost == Lost);
if (pending) assert(response(mesh, Lost, 300));
else { mesh.tracker_acquiring = false; sensors.pending = false; }
}
mesh.loop(); assert(mesh.tracker_awake_until == uint32_t(mesh.now_ms + 300000));
++scenarios;
}
static void awakeLongCadence() {
for (unsigned stayawake : {0U, 120U}) for (bool unknown : {false, true}) {
SensorMesh mesh; configure(mesh);
assert(command(mesh, "set tracker.lost.interval 86400") == "OK");
const uint32_t origin = mesh.rtc.now; uint64_t elapsed_ms = 0;
const auto advance = [&](uint64_t delta) {
elapsed_ms += delta; mesh.now_ms = uint32_t(100 + elapsed_ms);
mesh.rtc.now = origin + uint32_t(elapsed_ms / 1000);
};
queueCheck(mesh, true, true); transmitted(mesh); assert(response(mesh, Lost, stayawake));
advance(120001); mesh.loop();
assert(!mesh.trackerNeedsRadio() && mesh.tracker_radio_asleep);
for (unsigned day = 1; day <= 26; ++day) {
const uint64_t due_ms = uint64_t(mesh.tracker_next_check - origin) * 1000;
advance(due_ms - elapsed_ms); queueCheck(mesh, true, true); transmitted(mesh);
if (unknown && day == 1) assert(response(mesh, Unknown));
else { advance(20001); mesh.loop(); }
assert(!mesh.tracker_pending && !mesh.tracker_acquiring && mesh.tracker.last_lost == Lost);
mesh.loop();
assert(!mesh.trackerNeedsRadio() && mesh.tracker_radio_asleep
&& "expired tracker awake lease must not revive after signed half-wrap");
}
++scenarios;
}
}
static void timing() {
{ SensorMesh mesh; configure(mesh); queueCheck(mesh);
assert(!response(mesh, Lost, 120)); // A reply cannot start before the send callback.
@@ -551,6 +658,9 @@ int main(int argc, char** argv) {
assert(argc == 2); const std::string name = argv[1];
if (name == "commands") commands();
else if (name == "cadence") cadence();
else if (name == "awake-expiry") awakeExpiry();
else if (name == "awake-owners") awakeOwners();
else if (name == "awake-long") awakeLongCadence();
else if (name == "timing") timing();
else if (name == "identity") identityAndNonce();
else if (name == "paths") paths();