Loading debian/changelog +17 −0 Original line number Diff line number Diff line authdb (20260712+3) unstable; urgency=high * Cluster::scrub(): drop the remove()+store() "cleanup" pass in the repair loop — a third, independent copy of the same bug already fixed in mediadb's Cluster::scrub() and libparitypp's client::rebalance(). paritypp::client::remove() is best-effort across all nodes and never throws on partial failure, so it reliably deleted a group's just-repaired auth data blocks before the second store() re-wrote them; if that second store() then hit a transient node failure, the group was left with zero valid blocks anywhere. The first store() alone already places every block on its canonical node, which is sufficient to repair the group — the AuthHeader/magic validation added here stays in place unchanged, only the destructive re-store pass is removed. -- Jan Koester <jan.koester@tuxist.de> Sun, 12 Jul 2026 12:35:00 +0200 authdb (20260618+2) unstable; urgency=high * file backend: File::read()/write() looped on rd/wr==0 as if more data Loading src/cluster.cpp +16 −16 Original line number Diff line number Diff line Loading @@ -1224,23 +1224,23 @@ namespace authdb { continue; } // Data is valid — safe to re-store for correct placement. // SAFETY: store FIRST so correct blocks exist on correct nodes. // If this throws, original data remains on old nodes. // Data is valid — re-store onto the canonical nodes // (block_index % n == node_index). store() alone fixes // both under-replication and misplacement — it writes // every block to its correct node. Do NOT remove() the // group first: paritypp::client::remove() is best-effort // across all nodes and never throws on partial failure, // so a remove-then-restore here could wipe the only good // copy of this auth data if the following store() then // hit a transient node failure (>m nodes unreachable) — // turning a merely-incomplete group into a permanently // empty one. Any stale blocks left on nodes that no // longer canonically own this group are a space concern // only, reclaimed by vacuum_all_nodes() below. (Same bug // and same fix as mediadb's Cluster::scrub() and // libparitypp's client::rebalance() — this was a third, // independent copy of the same logic.) scrub_client_->store(gid, data.data(), data.size()); // Data is now safely placed. Remove all copies and re-store // for clean distribution without leftover misplaced blocks. try { scrub_client_->remove(gid); scrub_client_->store(gid, data.data(), data.size()); } catch (const std::exception &ce) { // Cleanup pass failed — data is still safe from the // first store(). Misplaced blocks may remain. std::cerr << "[SCRUB] group " << gid << " — cleanup pass failed: " << ce.what() << " (data is safe)" << std::endl; } result.groups_repaired++; std::cerr << "[SCRUB] group " << gid << " — repaired" << std::endl; Loading Loading
debian/changelog +17 −0 Original line number Diff line number Diff line authdb (20260712+3) unstable; urgency=high * Cluster::scrub(): drop the remove()+store() "cleanup" pass in the repair loop — a third, independent copy of the same bug already fixed in mediadb's Cluster::scrub() and libparitypp's client::rebalance(). paritypp::client::remove() is best-effort across all nodes and never throws on partial failure, so it reliably deleted a group's just-repaired auth data blocks before the second store() re-wrote them; if that second store() then hit a transient node failure, the group was left with zero valid blocks anywhere. The first store() alone already places every block on its canonical node, which is sufficient to repair the group — the AuthHeader/magic validation added here stays in place unchanged, only the destructive re-store pass is removed. -- Jan Koester <jan.koester@tuxist.de> Sun, 12 Jul 2026 12:35:00 +0200 authdb (20260618+2) unstable; urgency=high * file backend: File::read()/write() looped on rd/wr==0 as if more data Loading
src/cluster.cpp +16 −16 Original line number Diff line number Diff line Loading @@ -1224,23 +1224,23 @@ namespace authdb { continue; } // Data is valid — safe to re-store for correct placement. // SAFETY: store FIRST so correct blocks exist on correct nodes. // If this throws, original data remains on old nodes. // Data is valid — re-store onto the canonical nodes // (block_index % n == node_index). store() alone fixes // both under-replication and misplacement — it writes // every block to its correct node. Do NOT remove() the // group first: paritypp::client::remove() is best-effort // across all nodes and never throws on partial failure, // so a remove-then-restore here could wipe the only good // copy of this auth data if the following store() then // hit a transient node failure (>m nodes unreachable) — // turning a merely-incomplete group into a permanently // empty one. Any stale blocks left on nodes that no // longer canonically own this group are a space concern // only, reclaimed by vacuum_all_nodes() below. (Same bug // and same fix as mediadb's Cluster::scrub() and // libparitypp's client::rebalance() — this was a third, // independent copy of the same logic.) scrub_client_->store(gid, data.data(), data.size()); // Data is now safely placed. Remove all copies and re-store // for clean distribution without leftover misplaced blocks. try { scrub_client_->remove(gid); scrub_client_->store(gid, data.data(), data.size()); } catch (const std::exception &ce) { // Cleanup pass failed — data is still safe from the // first store(). Misplaced blocks may remain. std::cerr << "[SCRUB] group " << gid << " — cleanup pass failed: " << ce.what() << " (data is safe)" << std::endl; } result.groups_repaired++; std::cerr << "[SCRUB] group " << gid << " — repaired" << std::endl; Loading