From 8eafa1b0513dd2f5898182487b4524a485bf1e21 Mon Sep 17 00:00:00 2001 From: Eddielu Date: Mon, 27 Jul 2020 20:54:28 +0800 Subject: [PATCH] Update add sensors slow readings patch. --- mainloop.cpp | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---- mainloop.hpp | 3 +++ meson.build | 1 + sensor.cpp | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++------- sensor.hpp | 20 ++++++++++++++-- 5 files changed, 158 insertions(+), 14 deletions(-) diff --git a/mainloop.cpp b/mainloop.cpp index 4789a80..98d0658 100644 --- a/mainloop.cpp +++ b/mainloop.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -299,7 +300,7 @@ std::optional { // Add status interface based on _fault file being present sensorObj->addStatus(info); - valueInterface = sensorObj->addValue(retryIO, info); + valueInterface = sensorObj->addValue(retryIO, info, _timedoutMap); } catch (const std::system_error& e) { @@ -542,10 +543,74 @@ void MainLoop::read() // RAII object for GPIO unlock / lock auto locker = sensor::gpioUnlock(sensor->getGpio()); - // Retry for up to a second if device is busy - // or has a transient error. - value = _ioAccess->read(sensorSysfsType, sensorSysfsNum, input, + // For sensors with attribute ASYNC_READ_TIMEOUT, + // spawn a thread with timeout + auto asyncRead = + env::getEnv("ASYNC_READ_TIMEOUT", sensorSetKey); + if (!asyncRead.empty()) + { + // Default async read timeout + std::chrono::milliseconds asyncReadTimeout{ + std::stoi(asyncRead)}; + bool valueIsValid = false; + std::future asyncThread; + + auto asyncIter = _timedoutMap.find(sensorSetKey); + if (asyncIter == _timedoutMap.end()) + { + // If sensor not found in timedoutMap, spawn an async + // thread + asyncThread = std::async( + std::launch::async, + &hwmonio::HwmonIOInterface::read, _ioAccess, + sensorSysfsType, sensorSysfsNum, input, + hwmonio::retries, hwmonio::delay); + valueIsValid = true; + } + else + { + // If we already have the async thread in the + // timedoutMap, it means this sensor has already timed + // out in the previous reads. No need to wait on + // subsequent reads + asyncReadTimeout = std::chrono::seconds(0); + asyncThread = std::move(asyncIter->second); + } + + std::future_status status = + asyncThread.wait_for(asyncReadTimeout); + switch (status) + { + // Read has finished + case std::future_status::ready: + // Read has finished + if (valueIsValid) + { + value = asyncThread.get(); + break; + // Good sensor reads should skip the code below + } + // Async read thread has completed, erase from + // timedoutMap to allow retry then throw + _timedoutMap.erase(sensorSetKey); + throw sensor::AsyncSensorReadTimeOut(); + default: + // Read timed out so add the thread to the + // timedoutMap (if the entry already exists, + // operator[] updates it) + _timedoutMap[sensorSetKey] = std::move(asyncThread); + throw sensor::AsyncSensorReadTimeOut(); + } + } + else + { + // Retry for up to a second if device is busy + // or has a transient error. + value = + _ioAccess->read(sensorSysfsType, sensorSysfsNum, input, hwmonio::retries, hwmonio::delay); + } + // Set functional property to true if we could read sensor statusIface->functional(true); diff --git a/mainloop.hpp b/mainloop.hpp index b3de022..6803c4b 100644 --- a/mainloop.hpp +++ b/mainloop.hpp @@ -9,6 +9,7 @@ #include "types.hpp" #include +#include #include #include #include @@ -116,6 +117,8 @@ class MainLoop /** @brief Store the specifications of sensor objects */ std::map> _sensorObjects; + /** @brief Store the async futures of timed out sensor objects */ + std::map> _timedoutMap; /** * @brief Map of removed sensors diff --git a/meson.build b/meson.build index 66e6801..d6a92f8 100644 --- a/meson.build +++ b/meson.build @@ -84,6 +84,7 @@ libhwmon_all = static_library( gpioplus, phosphor_dbus_interfaces, phosphor_logging, + threads, ], link_with: [ libaverage, diff --git a/sensor.cpp b/sensor.cpp index b1cb470..72b45f8 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -125,8 +126,9 @@ SensorValueType Sensor::adjustValue(SensorValueType value) return value; } -std::shared_ptr Sensor::addValue(const RetryIO& retryIO, - ObjectInfo& info) +std::shared_ptr Sensor::addValue( + const RetryIO& retryIO, ObjectInfo& info, + std::map>& timedoutMap) { static constexpr bool deferSignals = true; @@ -153,12 +155,69 @@ std::shared_ptr Sensor::addValue(const RetryIO& retryIO, // RAII object for GPIO unlock / lock auto locker = gpioUnlock(getGpio()); - // Retry for up to a second if device is busy - // or has a transient error. - val = - _ioAccess->read(_sensor.first, _sensor.second, - hwmon::entry::cinput, std::get(retryIO), - std::get(retryIO)); + // For sensors with attribute ASYNC_READ_TIMEOUT, + // spawn a thread with timeout + auto asyncRead = env::getEnv("ASYNC_READ_TIMEOUT", _sensor); + if (!asyncRead.empty()) + { + // Default async read timeout + std::chrono::milliseconds asyncReadTimeout{ + std::stoi(asyncRead)}; + bool valueIsValid = false; + std::future asyncThread; + + auto asyncIter = timedoutMap.find(_sensor); + if (asyncIter == timedoutMap.end()) + { + // If sensor not found in timedoutMap, spawn an async thread + asyncThread = std::async( + std::launch::async, &hwmonio::HwmonIOInterface::read, + _ioAccess, _sensor.first, _sensor.second, + hwmon::entry::cinput, std::get(retryIO), + std::get(retryIO)); + valueIsValid = true; + } + else + { + // If we already have the async thread in the timedoutMap, + // it means this sensor has already timed out in the + // previous reads. No need to wait on subsequent reads + asyncReadTimeout = std::chrono::seconds(0); + asyncThread = std::move(asyncIter->second); + } + + std::future_status status = + asyncThread.wait_for(asyncReadTimeout); + switch (status) + { + case std::future_status::ready: + // Read has finished + if (valueIsValid) + { + val = asyncThread.get(); + break; + // Good sensor reads should skip the code below + } + // Async read thread has completed, erase from + // timedoutMap to allow retry then throw + timedoutMap.erase(_sensor); + throw AsyncSensorReadTimeOut(); + default: + // Read timed out so add the thread to the timedoutMap + // (if the entry already exists, operator[] updates it) + timedoutMap[_sensor] = std::move(asyncThread); + throw AsyncSensorReadTimeOut(); + } + } + else + { + // Retry for up to a second if device is busy + // or has a transient error. + val = _ioAccess->read( + _sensor.first, _sensor.second, hwmon::entry::cinput, + std::get(retryIO), + std::get(retryIO)); + } } #ifdef UPDATE_FUNCTIONAL_ON_FAIL catch (const std::system_error& e) diff --git a/sensor.hpp b/sensor.hpp index 369a252..41c0fe7 100644 --- a/sensor.hpp +++ b/sensor.hpp @@ -4,6 +4,8 @@ #include "sensorset.hpp" #include "types.hpp" +#include +#include #include #include #include @@ -20,6 +22,17 @@ struct valueAdjust std::unordered_set rmRCs; }; +/** @brief Custom exception for async sensor reading timeout + */ +struct AsyncSensorReadTimeOut : public std::system_error +{ + AsyncSensorReadTimeOut() : + system_error(std::error_code(ETIMEDOUT, std::system_category()), + "Async sensor read timed out") + { + } +}; + /** @class Sensor * @brief Sensor object based on a SensorSet container's key type * @details Sensor object to create and modify an associated device's sensor @@ -87,10 +100,13 @@ class Sensor * (number of and delay between) * @param[in] info - Sensor object information * + * @param[in] timedoutMap - Map to track timed out threads + * * @return - Shared pointer to the value object */ - std::shared_ptr addValue(const RetryIO& retryIO, - ObjectInfo& info); + std::shared_ptr addValue( + const RetryIO& retryIO, ObjectInfo& info, + std::map>& timedoutMap); /** * @brief Add status interface and functional property for sensor -- 2.7.4