Преглед изворни кода

[8] fix(content): omit malformed item rows instead of truncating the walk

Review: package_item_rows says malformed are skipped, but will fail on
an invalid entry.
Resolution: an unreadable index row is skipped like the definition read
below it, and the walk reports what it omitted.

The item index-table walk stopped at the first entry whose index row did
not read, discarding every entry after it. When that entry was near the
start, the pass published nothing and extraction failed outright, even
though the rest of the table was readable. The comment further down the
same function already stated the intended contract: one malformed row is
omitted on its own so unrelated Collections categories stay usable.

Makes the index-row failure skip its entry like the definition-read
failure below it already did, and reports the walk so an omitted entry
is visible rather than silent. The report also carries whether the walk
stopped early on full row storage, which was previously an unlogged
truncation, and raises the line to warn whenever either happened. Both
counts follow from the loop index and the published total, so nothing
has to be tracked alongside them.

Thomas Shields пре 3 недеља
родитељ
комит
f1828daf70

+ 12 - 0
Sunrise/src/client/content/items/packages/internal.h

@@ -272,6 +272,18 @@ void report_ability_failure(const char* stage,
 /** Reports requested, retained, and skipped detail closure rows. */
 void report_detail_count(std::size_t requested, std::size_t built) noexcept;
 
+/**
+ * Reports the item index-table walk, including rows the pass could not read.
+ * @param walked Table entries the pass visited.
+ * @param rows Readable rows it retained.
+ * @param skipped Entries omitted because their index row or definition was malformed.
+ * @param truncated True when the walk stopped early because the row storage filled.
+ */
+void report_row_count(std::size_t walked,
+                      std::size_t rows,
+                      std::size_t skipped,
+                      bool truncated) noexcept;
+
 /** Reports the exact socket-rule, deduplicated-pool, member, and skipped-lane counts. */
 void report_socket_plug_count(std::size_t rules,
                               std::size_t pools,

+ 22 - 0
Sunrise/src/client/content/items/packages/package_build_report.cpp

@@ -84,6 +84,28 @@ void report_detail_count(std::size_t requested, std::size_t built) noexcept {
     }
 }
 
+/** Reports the item index-table walk and every entry it could not read. */
+void report_row_count(std::size_t walked,
+                      std::size_t rows,
+                      std::size_t skipped,
+                      bool truncated) noexcept {
+    std::array<char, 128> line{};
+    const int written = std::snprintf(line.data(),
+                                      line.size(),
+                                      "ev=pkg stage=rows result=ok walked=%zu rows=%zu "
+                                      "skipped=%zu truncated=%u",
+                                      walked,
+                                      rows,
+                                      skipped,
+                                      static_cast<unsigned>(truncated));
+    if (written > 0) {
+        core::log::write(core::log::Channel::client,
+                         skipped == 0 && !truncated ? core::log::Level::info
+                                                    : core::log::Level::warn,
+                         {line.data(), static_cast<std::size_t>(written)});
+    }
+}
+
 /** Reports the bounded exact ordinary-socket relation extracted from the installed packages. */
 void report_socket_plug_count(std::size_t rules,
                               std::size_t pools,

+ 9 - 3
Sunrise/src/client/content/items/packages/package_item_rows.cpp

@@ -62,11 +62,14 @@ bool build_item_rows(const reader::Source& source,
     storage.detailRequests.reset();
     storage.specialPlugCategories.fill(0);
     std::size_t detailCount = 0;
-    for (std::uint64_t index = 0; needRows && index < table.count && rowCount < storage.rows.size();
-         ++index) {
+    // One malformed entry is omitted on its own so the rest of the table still publishes. An
+    // entry can fail either at its index row or at the definition the row points to, and neither
+    // says anything about the entries that follow it.
+    std::uint64_t index = 0;
+    for (; needRows && index < table.count && rowCount < storage.rows.size(); ++index) {
         tables::IndexRow row{};
         if (!tables::index_row(container, table, index, row)) {
-            break;
+            continue;
         }
         tables::items::Row item{};
         item.definitionHash = row.definitionHash;
@@ -93,6 +96,9 @@ bool build_item_rows(const reader::Source& source,
     }
     bool requestsFit = true;
     if (needRows) {
+        // Every walked entry either published a row or was skipped, so the count of one
+        // follows from the other rather than being tracked alongside them.
+        report_row_count(index, rowCount, index - rowCount, index < table.count);
         requestsFit = publish_buckets(storage)
                       && (!needDetailRows
                           || materialize_requests(