dbus-sensors: apply non-blocking discovery patch

Resolve an infinite discovery loop issue where missing resources
(returning 404 Not Found) would block the daemon from completing
discovery and creating D-Bus sensors.

Implement a "Non-Blocking Discovery with Background Retry" strategy:
1. Skip failed metric report paths during the main discovery pass to
   allow other working resources to be processed.
2. Store failed paths in a set and allow discovery to complete.
3. In the main polling loop, periodically (every 60 ticks) retry one
   failed path in the background.
4. If a retry succeeds after discovery is done, dynamically add the
   new sensors to D-Bus.

Tested: https://paste.googleplex.com/5905898336813056?raw
Platforms-Affected: Global
Fusion-Link: fusion2, n/a
Google-Bug-Id: 527716739
Change-Id: I61250870374afe83fd85d6112d72a89f24c97678
Signed-off-by: Chunk Huang <chunkh@google.com>
diff --git a/recipes-phosphor/sensors/dbus-sensors/0106-RedfishSensor-Implement-non-blocking-discovery-and-b.patch b/recipes-phosphor/sensors/dbus-sensors/0106-RedfishSensor-Implement-non-blocking-discovery-and-b.patch
new file mode 100644
index 0000000..7f1f2f8
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0106-RedfishSensor-Implement-non-blocking-discovery-and-b.patch
@@ -0,0 +1,211 @@
+From 179e5cae7a3536e1f1e43dcae5270b4fd7c0f73e Mon Sep 17 00:00:00 2001
+From: Chunk Huang <chunkh@google.com>
+Date: Tue, 7 Jul 2026 09:21:54 +0000
+Subject: [PATCH] RedfishSensor: Implement non-blocking discovery and
+ background retry
+
+Resolve an infinite discovery loop issue where missing resources
+(returning 404 Not Found) would block the daemon from completing
+discovery and creating D-Bus sensors.
+
+Implement a "Non-Blocking Discovery with Background Retry" strategy:
+1. Skip failed metric report paths during the main discovery pass to
+   allow other working resources to be processed.
+2. Store failed paths in a set and allow discovery to complete.
+3. In the main polling loop, periodically (every 60 ticks) retry one
+   failed path in the background.
+4. If a retry succeeds after discovery is done, dynamically add the
+   new sensors to D-Bus.
+
+Tested: https://paste.googleplex.com/5905898336813056?raw
+Platforms-Affected: Global
+Fusion-Link: fusion2, n/a
+Google-Bug-Id: 527716739
+Signed-off-by: Chunk Huang <chunkh@google.com>
+---
+ meson.options              |  1 +
+ src/RedfishSensor.cpp      | 31 +++++++++++++++-----
+ src/RedfishSensor.hpp      |  7 +++++
+ src/RedfishSensorQuery.cpp | 58 +++++++++++++++++++++++++++++++++++++-
+ src/meson.build            |  1 +
+ 5 files changed, 90 insertions(+), 8 deletions(-)
+
+diff --git a/meson.options b/meson.options
+index 2d96c0a..e6f4511 100644
+--- a/meson.options
++++ b/meson.options
+@@ -11,6 +11,7 @@ option('nvme', type: 'feature', value: 'enabled', description: 'Enable NVMe sens
+ option('psu', type: 'feature', value: 'enabled', description: 'Enable PSU sensor.',)
+ option('external', type: 'feature', value: 'enabled', description: 'Enable External sensor.',)
+ option('redfish', type: 'feature', value: 'enabled', description: 'Enable Redfish sensor.',)
++option('redfish-retry-ticks-threshold', type: 'integer', value: 60, description: 'Number of ticks to wait before retrying failed Redfish queries in background.')
+ option('tests', type: 'feature', value: 'enabled', description: 'Build tests.',)
+ option('validate-unsecure-feature', type : 'feature', value : 'disabled', description : 'Enables unsecure features required by validation. Note: mustbe turned off for production images.',)
+ option('insecure-sensor-override', type : 'feature', value : 'disabled', description : 'Enables Sensor override feature without any check.',)
+diff --git a/src/RedfishSensor.cpp b/src/RedfishSensor.cpp
+index b2f08d4..96addce 100644
+--- a/src/RedfishSensor.cpp
++++ b/src/RedfishSensor.cpp
+@@ -401,6 +401,24 @@ void RedfishServer::nextAction()
+         return;
+     }
+ 
++    // --- Background Retry Logic ---
++    if (!failedMetricReports.empty())
++    {
++        ++retryTicksCounter;
++        if (retryTicksCounter >= retryTicksThreshold)
++        {
++            retryTicksCounter = 0;
++            auto it = failedMetricReports.begin();
++            std::string pathToRetry = *it;
++            failedMetricReports.erase(it);
++
++            std::cerr << "Workflow: Background retry for " << pathToRetry
++                      << "\n";
++            queryMetricReport(pathToRetry);
++            return; // Network is now busy with the retry
++        }
++    }
++
+     ++ticksReading;
+     timeReading = timeAction;
+ 
+@@ -510,6 +528,12 @@ void RedfishServer::advanceDiscovery()
+         // Collect all metric reports, for use during preflight
+         for (const std::string& reportPath : reportPaths)
+         {
++            if (failedMetricReports.find(reportPath) !=
++                failedMetricReports.end())
++            {
++                continue; // Skip failed reports in the current pass
++            }
++
+             auto iter = pathsToMetricReports.find(reportPath);
+             if (iter == pathsToMetricReports.end())
+             {
+@@ -655,13 +679,6 @@ void RedfishServer::acceptSensors()
+         std::cerr << "Ready to finalize what was discovered\n";
+     }
+ 
+-    // FUTURE: Make sure this works after accepting sensorsChanged
+-    for (auto& sensorPair : pathsToSensors)
+-    {
+-        sensorPair.second.reset();
+-    }
+-    pathsToSensors.clear();
+-
+     size_t countDiscovered = 0;
+     size_t countAlready = 0;
+     size_t countNotRelevant = 0;
+diff --git a/src/RedfishSensor.hpp b/src/RedfishSensor.hpp
+index 7a6c996..3a32959 100644
+--- a/src/RedfishSensor.hpp
++++ b/src/RedfishSensor.hpp
+@@ -5,6 +5,7 @@
+ 
+ #include <boost/asio/steady_timer.hpp>
+ #include <boost/container/flat_map.hpp>
++#include <boost/container/flat_set.hpp>
+ #include <nlohmann/json.hpp>
+ #include <sdbusplus/asio/connection.hpp>
+ #include <sdbusplus/asio/object_server.hpp>
+@@ -258,6 +259,12 @@ class RedfishServer : public std::enable_shared_from_this<RedfishServer>
+                                std::shared_ptr<RedfishMetricReport>>
+         pathsToMetricReports;
+ 
++    boost::container::flat_set<std::string> failedMetricReports;
++    size_t retryTicksCounter = 0;
++    boost::container::flat_map<std::string, int> quickRetryCounters;
++    static constexpr int maxQuickRetries = 3;
++    static constexpr size_t retryTicksThreshold = RETRY_TICKS_THRESHOLD;
++
+     // For remembering starting times to measure how long it took
+     std::chrono::steady_clock::time_point timeDiscovery;
+     std::chrono::steady_clock::time_point timeReading;
+diff --git a/src/RedfishSensorQuery.cpp b/src/RedfishSensorQuery.cpp
+index fb70afe..d3f14d8 100644
+--- a/src/RedfishSensorQuery.cpp
++++ b/src/RedfishSensorQuery.cpp
+@@ -819,9 +819,65 @@ void RedfishServer::queryMetricReport(const std::string& path)
+         }
+         if (lockThis->doneTransaction(completion))
+         {
++            // Clear counter on success
++            lockThis->quickRetryCounters.erase(completion.queryRequest);
++
+             if (lockThis->fillFromMetricReport(completion.jsonResponse))
+             {
+-                lockThis->advanceDiscovery();
++                if (lockThis->discoveryDone)
++                {
++                    std::cerr << "Workflow: Background retry success for "
++                              << completion.queryRequest
++                              << ", updating D-Bus sensors.\n";
++                    lockThis->acceptSensors();
++                }
++                else
++                {
++                    lockThis->advanceDiscovery();
++                }
++            }
++        }
++        else
++        {
++            if (lockThis->discoveryDone)
++            {
++                // Background retry failed - return directly to the slow queue
++                std::cerr << "Workflow: Background retry failed for "
++                          << completion.queryRequest
++                          << ", will retry again in next background cycle.\n";
++
++                lockThis->failedMetricReports.insert(completion.queryRequest);
++            }
++            else
++            {
++                // Initial discovery failed - use the Quick Retry mechanism
++                int retries =
++                    ++(lockThis->quickRetryCounters[completion.queryRequest]);
++
++                if (retries <= maxQuickRetries)
++                {
++                    std::cerr << "Discovery: Metric report query failed for "
++                              << completion.queryRequest
++                              << ", retry on next tick (" << retries << "/"
++                              << maxQuickRetries << ")\n";
++                    // Do nothing. The next timer tick will naturally retry this
++                    // URL
++                }
++                else
++                {
++                    std::cerr << "Discovery: Metric report query failed for "
++                              << completion.queryRequest << " after "
++                              << maxQuickRetries
++                              << " attempts. Moving to background retry.\n";
++
++                    lockThis->quickRetryCounters.erase(completion.queryRequest);
++                    lockThis->failedMetricReports.insert(
++                        completion.queryRequest);
++
++                    // Allow discovery to continue immediately for remaining
++                    // resources
++                    lockThis->advanceDiscovery();
++                }
+             }
+         }
+     };
+diff --git a/src/meson.build b/src/meson.build
+index a1d9d7a..48b01d2 100644
+--- a/src/meson.build
++++ b/src/meson.build
+@@ -259,6 +259,7 @@ if get_option('redfish').enabled()
+         'RedfishSensorMain.cpp',
+         'RedfishSensorQuery.cpp',
+         'RedfishSensorResponse.cpp',
++        cpp_args: ['-DRETRY_TICKS_THRESHOLD=@0@'.format(get_option('redfish-retry-ticks-threshold'))],
+         dependencies: [
+             default_deps,
+             thresholds_dep,
+-- 
+2.55.0.rc2.803.g1fd1e6609c-goog
+
diff --git a/recipes-phosphor/sensors/dbus-sensors_%.bbappend b/recipes-phosphor/sensors/dbus-sensors_%.bbappend
index f4d8985..0924780 100644
--- a/recipes-phosphor/sensors/dbus-sensors_%.bbappend
+++ b/recipes-phosphor/sensors/dbus-sensors_%.bbappend
@@ -14,6 +14,7 @@
   file://0103-RedfishSensor-Intentionally-drop-reported-stale.patch \
   file://0104-RedfishSensor-Port-feature-and-other-cleanups.patch \
   file://0105-RedfishSensor-Configurable-polling-intervall.patch \
+  file://0106-RedfishSensor-Implement-non-blocking-discovery-and-b.patch \
   file://0010-dbus-sensors-Creating-association-between-inventory-.patch \
   file://0001-intrusionsensor-Add-Assosication-interfaces-for-cabl.patch \
   file://0001-adcsensor-Add-optional-battery-status-interface.patch \