Time out an abandoned admin edit transaction (#11254)
begin_edit_settings sets a bool that only the matching commit ever clears. If the commit never arrives -- the client dropped its link partway through a bulk config import, or went away entirely -- the transaction stays open indefinitely, and every later config write from any client is applied to RAM, acknowledged, and then never saved. The writes look successful and are visible in get_config, but the node reverts all of them at the next boot, and only a reboot clears it. Give the transaction a one minute idle timeout. Each deferred write restarts the clock, so the window bounds the gap between writes rather than the length of the edit; a bulk import sends them milliseconds apart. The next admin message after it lapses retires the transaction, persisting the segments it had deferred and flushing the warnings it was holding. The check runs after the auth gates and before the switch, so every case sees consistent state and the recovery's flash write happens in the main loop rather than a disconnect callback. It saves rather than rolls back because there is nothing to roll back to: each write already took effect in RAM and was acknowledged, so the transaction only ever deferred the save. That also makes an early expiry cheap -- the worst case for a client that really was still going is that its remaining writes are saved individually instead of batched. Closing on disconnect instead was tempting, but the reporter's captures show iOS dropping and reconnecting within half a second several times per session, which would abort imports that currently survive the blip. Also drops the file-scope hasOpenEditTransaction, shadowed by the class member at every use site and referenced nowhere else in the tree. Reported in #11245
This commit is contained in:
@@ -21,9 +21,18 @@ class AdminModuleTestShim : public AdminModule
|
||||
meshtastic_MeshPacket *reply() { return myReply; }
|
||||
|
||||
// With an "open edit transaction" saveChanges() is a pure no-op: no reloadConfig/saveToDisk/reboot.
|
||||
void deferSaves() { hasOpenEditTransaction = true; }
|
||||
// Stamps the clock like begin_edit_settings, so a slow suite can't age the transaction into expiry.
|
||||
void deferSaves()
|
||||
{
|
||||
hasOpenEditTransaction = true;
|
||||
editTransactionActivityMs = millis();
|
||||
}
|
||||
int savedSegments() const { return lastSaveWhatForTest; }
|
||||
|
||||
bool editTransactionOpen() const { return hasOpenEditTransaction; }
|
||||
// Backdate past the idle window so a test sees an abandoned transaction without waiting it out.
|
||||
void ageEditTransaction() { editTransactionActivityMs = millis() - EDIT_TRANSACTION_IDLE_MS - 1; }
|
||||
|
||||
// Setters may allocate an error reply from packetPool; drain it each iteration or the pool leaks.
|
||||
void drainReply()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user