Add exponential backoff to device monitor

This commit is contained in:
pixl 2023-05-01 21:51:08 -04:00
parent ffd5e054a1
commit 47e4ba2b44
No known key found for this signature in database
GPG Key ID: 1866C148CD593B6E
23 changed files with 211 additions and 100 deletions

View File

@ -18,9 +18,7 @@
#include <DeviceManager.h>
#include <backend/Error.h>
#include <backend/hidpp10/Error.h>
#include <util/log.h>
#include <ipcgull/function.h>
#include <thread>
#include <sstream>
#include <utility>
@ -100,24 +98,29 @@ void DeviceManager::addDevice(std::string path) {
hidpp::Device device(path, hidpp::DefaultDevice, _self.lock(),
config()->io_timeout.value_or(defaults::io_timeout));
isReceiver = device.version() == std::make_tuple(1, 0);
} catch (hidpp20::Error& e) {
if (e.code() != hidpp20::Error::UnknownDevice)
throw DeviceNotReady();
defaultExists = false;
} catch (hidpp10::Error& e) {
if (e.code() != hidpp10::Error::UnknownDevice)
throw;
throw DeviceNotReady();
defaultExists = false;
} catch (hidpp::Device::InvalidDevice& e) {
if (e.code() == hidpp::Device::InvalidDevice::VirtualNode) {
logPrintf(DEBUG, "Ignoring virtual node on %s",
path.c_str());
return;
logPrintf(DEBUG, "Ignoring virtual node on %s", path.c_str());
} else if (e.code() == hidpp::Device::InvalidDevice::Asleep) {
/* May be a valid device, wait */
throw DeviceNotReady();
}
defaultExists = false;
return;
} catch (std::system_error& e) {
logPrintf(WARN, "I/O error on %s: %s, skipping device.",
path.c_str(), e.what());
logPrintf(WARN, "I/O error on %s: %s, skipping device.", path.c_str(), e.what());
return;
} catch (TimeoutError& e) {
logPrintf(WARN, "Device %s timed out.", path.c_str());
defaultExists = false;
/* Ready and valid non-default devices should throw an UnknownDevice error */
throw DeviceNotReady();
}
if (isReceiver) {
@ -130,28 +133,30 @@ void DeviceManager::addDevice(std::string path) {
/* TODO: Can non-receivers only contain 1 device?
* If the device exists, it is guaranteed to be an HID++ 2.0 device */
if (defaultExists) {
auto device = Device::make(path, hidpp::DefaultDevice,
_self.lock());
auto device = Device::make(path, hidpp::DefaultDevice, _self.lock());
std::lock_guard<std::mutex> lock(_map_lock);
_devices.emplace(path, device);
_ipc_devices->deviceAdded(device);
} else {
try {
auto device = Device::make(path,
hidpp::CordedDevice, _self.lock());
auto device = Device::make(path, hidpp::CordedDevice, _self.lock());
std::lock_guard<std::mutex> lock(_map_lock);
_devices.emplace(path, device);
_ipc_devices->deviceAdded(device);
} catch (hidpp10::Error& e) {
if (e.code() != hidpp10::Error::UnknownDevice)
throw;
else
logPrintf(WARN, "HID++ 1.0 error while trying to initialize %s: %s",
path.c_str(), e.what());
} catch (hidpp::Device::InvalidDevice& e) { // Ignore
throw DeviceNotReady();
} catch (hidpp20::Error& e) {
if (e.code() != hidpp20::Error::UnknownDevice)
throw DeviceNotReady();
} catch (hidpp::Device::InvalidDevice& e) {
if (e.code() == hidpp::Device::InvalidDevice::Asleep)
throw DeviceNotReady();
} catch (std::system_error& e) {
// This error should have been thrown previously
logPrintf(WARN, "I/O error on %s: %s", path.c_str(), e.what());
} catch (TimeoutError& e) {
throw DeviceNotReady();
}
}
}

View File

@ -18,6 +18,12 @@
#include <backend/Error.h>
const char* logid::backend::TimeoutError::what() const noexcept {
using namespace logid::backend;
const char* DeviceNotReady::what() const noexcept {
return "device not ready";
}
const char* TimeoutError::what() const noexcept {
return "Device timed out";
}

View File

@ -22,6 +22,11 @@
#include <stdexcept>
namespace logid::backend {
class DeviceNotReady : std::exception {
public:
[[nodiscard]] const char* what() const noexcept override;
};
class TimeoutError : public std::exception {
public:
TimeoutError() = default;

View File

@ -55,7 +55,7 @@ Device::Device(const std::string& path, DeviceIndex index,
duration<double, std::milli>(timeout))),
_raw_device(std::make_shared<raw::RawDevice>(path, monitor)),
_receiver(nullptr), _path(path), _index(index) {
_init();
_setupReportsAndInit();
}
Device::Device(std::shared_ptr<raw::RawDevice> raw_device, DeviceIndex index,
@ -64,7 +64,7 @@ Device::Device(std::shared_ptr<raw::RawDevice> raw_device, DeviceIndex index,
duration<double, std::milli>(timeout))),
_raw_device(std::move(raw_device)), _receiver(nullptr),
_path(_raw_device->rawPath()), _index(index) {
_init();
_setupReportsAndInit();
}
Device::Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
@ -81,7 +81,7 @@ Device::Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
_pid = event.pid;
else
_pid = receiver->getPairingInfo(_index).pid;
_init();
_setupReportsAndInit();
}
Device::Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
@ -92,7 +92,7 @@ Device::Device(const std::shared_ptr<hidpp10::Receiver>& receiver,
_receiver(receiver), _path(receiver->rawDevice()->rawPath()),
_index(index) {
_pid = receiver->getPairingInfo(_index).pid;
_init();
_setupReportsAndInit();
}
const std::string& Device::devicePath() const {
@ -107,7 +107,8 @@ const std::tuple<uint8_t, uint8_t>& Device::version() const {
return _version;
}
void Device::_init() {
void Device::_setupReportsAndInit() {
_event_handlers = std::make_shared<EventHandlerList<Device>>();
supported_reports = getSupportedReports(_raw_device->reportDescriptor());
@ -132,11 +133,17 @@ void Device::_init() {
auto rsp = sendReport({ReportType::Short, _index,
hidpp20::FeatureID::ROOT, hidpp20::Root::Ping,
hidpp::softwareID});
if (rsp.deviceIndex() != _index) {
if (rsp.deviceIndex() != _index)
throw InvalidDevice(InvalidDevice::VirtualNode);
}
} catch (hidpp10::Error& e) {
// Ignore
if (e.deviceIndex() != _index)
throw InvalidDevice(InvalidDevice::VirtualNode);
} catch (hidpp20::Error& e) {
/* This shouldn't happen, the device must not be ready */
if (e.deviceIndex() != _index)
throw InvalidDevice(InvalidDevice::VirtualNode);
else
throw DeviceNotReady();
}
}
@ -152,6 +159,10 @@ void Device::_init() {
this->handleEvent(_report);
}});
_init();
}
void Device::_init() {
try {
hidpp20::Root root(this);
_version = root.getVersion();
@ -162,6 +173,20 @@ void Device::_init() {
// HID++ 2.0 is not supported, assume HID++ 1.0
_version = std::make_tuple(1, 0);
} catch (hidpp20::Error& e) {
/* Should never happen, device not ready? */
throw DeviceNotReady();
}
/* Do a stability test before going further */
if (std::get<0>(_version) >= 2) {
if (!isStable20()) {
throw DeviceNotReady();
}
} else {
if (!isStable10()) {
throw DeviceNotReady();
}
}
if (!_receiver) {
@ -230,9 +255,11 @@ Report Device::sendReport(const Report& report) {
if (std::holds_alternative<Report>(response)) {
return std::get<Report>(response);
} else if (std::holds_alternative<Report::Hidpp10Error>(response)) {
throw hidpp10::Error(std::get<Report::Hidpp10Error>(response).error_code);
auto error = std::get<Report::Hidpp10Error>(response);
throw hidpp10::Error(error.error_code, error.device_index);
} else if (std::holds_alternative<Report::Hidpp20Error>(response)) {
throw hidpp20::Error(std::get<Report::Hidpp20Error>(response).error_code);
auto error = std::get<Report::Hidpp20Error>(response);
throw hidpp20::Error(error.error_code, error.device_index);
}
// Should not be reached
@ -246,10 +273,10 @@ bool Device::responseReport(const Report& report) {
Report::Hidpp10Error hidpp10_error{};
Report::Hidpp20Error hidpp20_error{};
if (report.isError10(&hidpp10_error)) {
if (report.isError10(hidpp10_error)) {
sub_id = hidpp10_error.sub_id;
response = hidpp10_error;
} else if (report.isError20(&hidpp20_error)) {
} else if (report.isError20(hidpp20_error)) {
sub_id = hidpp20_error.feature_index;
response = hidpp20_error;
} else {
@ -280,6 +307,27 @@ void Device::sendReportNoACK(const Report& report) {
_sendReport(report);
}
bool Device::isStable10() {
return true;
}
bool Device::isStable20() {
static constexpr std::string ping_seq = "hello";
hidpp20::Root root(this);
try {
for (auto c: ping_seq) {
if (root.ping(c) != c)
return false;
}
} catch (std::exception& e) {
return false;
}
return true;
}
void Device::reportFixup(Report& report) const {
switch (report.type()) {
case Report::Type::Short:

View File

@ -19,16 +19,16 @@
#ifndef LOGID_BACKEND_HIDPP_DEVICE_H
#define LOGID_BACKEND_HIDPP_DEVICE_H
#include <backend/raw/RawDevice.h>
#include <backend/hidpp/Report.h>
#include <backend/hidpp/defs.h>
#include <backend/EventHandlerList.h>
#include <optional>
#include <variant>
#include <string>
#include <memory>
#include <functional>
#include <map>
#include <backend/raw/RawDevice.h>
#include <backend/hidpp/Report.h>
#include <backend/hidpp/defs.h>
#include <backend/EventHandlerList.h>
namespace logid::backend::hidpp10 {
// Need to define here for a constructor
@ -100,6 +100,10 @@ namespace logid::backend::hidpp {
// Returns whether the report is a response
virtual bool responseReport(const Report& report);
bool isStable20();
bool isStable10();
void _sendReport(Report report);
void reportFixup(Report& report) const;
@ -110,6 +114,8 @@ namespace logid::backend::hidpp {
std::mutex _response_mutex;
std::condition_variable _response_cv;
private:
void _setupReportsAndInit();
void _init();
std::shared_ptr<raw::RawDevice> _raw_device;

View File

@ -265,31 +265,29 @@ void Report::setParams(const std::vector<uint8_t>& _params) {
_data[Offset::Parameters + i] = _params[i];
}
bool Report::isError10(Report::Hidpp10Error* error) const {
assert(error != nullptr);
bool Report::isError10(Report::Hidpp10Error& error) const {
if (_data[Offset::Type] != Type::Short ||
_data[Offset::SubID] != hidpp10::ErrorID)
return false;
error->sub_id = _data[3];
error->address = _data[4];
error->error_code = _data[5];
error.device_index = deviceIndex();
error.sub_id = _data[3];
error.address = _data[4];
error.error_code = _data[5];
return true;
}
bool Report::isError20(Report::Hidpp20Error* error) const {
assert(error != nullptr);
bool Report::isError20(Report::Hidpp20Error& error) const {
if (_data[Offset::Type] != Type::Long ||
_data[Offset::Feature] != hidpp20::ErrorID)
return false;
error->feature_index = _data[3];
error->function = (_data[4] >> 4) & 0x0f;
error->software_id = _data[4] & 0x0f;
error->error_code = _data[5];
error.device_index = deviceIndex();
error.feature_index = _data[3];
error.function = (_data[4] >> 4) & 0x0f;
error.software_id = _data[4] & 0x0f;
error.error_code = _data[5];
return true;
}

View File

@ -111,16 +111,18 @@ namespace logid::backend::hidpp {
void setParams(const std::vector<uint8_t>& _params);
struct Hidpp10Error {
hidpp::DeviceIndex device_index;
uint8_t sub_id, address, error_code;
};
bool isError10(Hidpp10Error* error) const;
bool isError10(Hidpp10Error& error) const;
struct Hidpp20Error {
hidpp::DeviceIndex device_index;
uint8_t feature_index, function, software_id, error_code;
};
bool isError20(Hidpp20Error* error) const;
bool isError20(Hidpp20Error& error) const;
[[nodiscard]] const std::vector<uint8_t>& rawReport() const;

View File

@ -58,9 +58,9 @@ hidpp::Report Device::sendReport(const hidpp::Report& report) {
_sendReport(report);
bool valid = _response_cv.wait_for(lock, io_timeout,
[&response_slot]() {
return response_slot.response.has_value();
});
[&response_slot]() {
return response_slot.response.has_value();
});
if (!valid) {
response_slot.reset();
@ -69,10 +69,13 @@ hidpp::Report Device::sendReport(const hidpp::Report& report) {
auto response = response_slot.response.value();
response_slot.reset();
if (std::holds_alternative<hidpp::Report>(response))
if (std::holds_alternative<hidpp::Report>(response)) {
return std::get<hidpp::Report>(response);
else // if(std::holds_alternative<Error::ErrorCode>(response))
throw Error(std::get<Error::ErrorCode>(response));
} else { // if(std::holds_alternative<hidpp::Report::Hidpp10Error>(response))
auto error = std::get<hidpp::Report::Hidpp10Error>(response);
throw Error(error.error_code, error.device_index);
}
}
bool Device::responseReport(const hidpp::Report& report) {
@ -81,7 +84,7 @@ bool Device::responseReport(const hidpp::Report& report) {
bool is_error = false;
hidpp::Report::Hidpp10Error hidpp10_error{};
if (report.isError10(&hidpp10_error)) {
if (report.isError10(hidpp10_error)) {
sub_id = hidpp10_error.sub_id;
is_error = true;
} else {
@ -94,7 +97,7 @@ bool Device::responseReport(const hidpp::Report& report) {
return false;
if (is_error) {
response_slot.response = static_cast<Error::ErrorCode>(hidpp10_error.error_code);
response_slot.response = hidpp10_error;
} else {
response_slot.response = report;
}

View File

@ -51,7 +51,7 @@ namespace logid::backend::hidpp10 {
bool responseReport(const hidpp::Report& report) final;
private:
typedef std::variant<hidpp::Report, Error::ErrorCode> Response;
typedef std::variant<hidpp::Report, hidpp::Report::Hidpp10Error> Response;
struct ResponseSlot {
std::optional<Response> response;
std::optional<uint8_t> sub_id;

View File

@ -19,9 +19,10 @@
#include <backend/hidpp10/Error.h>
#include <cassert>
using namespace logid::backend;
using namespace logid::backend::hidpp10;
Error::Error(uint8_t code) : _code(code) {
Error::Error(uint8_t code, hidpp::DeviceIndex index) : _code(code), _index(index) {
assert(code != Success);
}
@ -60,4 +61,8 @@ const char* Error::what() const noexcept {
uint8_t Error::code() const noexcept {
return _code;
}
}
hidpp::DeviceIndex Error::deviceIndex() const noexcept {
return _index;
}

View File

@ -19,6 +19,7 @@
#ifndef LOGID_BACKEND_HIDPP10_ERROR_H
#define LOGID_BACKEND_HIDPP10_ERROR_H
#include <backend/hidpp/defs.h>
#include <cstdint>
#include <exception>
@ -43,14 +44,17 @@ namespace logid::backend::hidpp10 {
WrongPINCode = 0x0C
};
explicit Error(uint8_t code);
Error(uint8_t code, hidpp::DeviceIndex index);
[[nodiscard]] const char* what() const noexcept override;
[[nodiscard]] uint8_t code() const noexcept;
[[nodiscard]] hidpp::DeviceIndex deviceIndex() const noexcept;
private:
uint8_t _code;
hidpp::DeviceIndex _index;
};
}

View File

@ -118,10 +118,12 @@ hidpp::Report Device::sendReport(const hidpp::Report& report) {
auto response = response_slot.response.value();
response_slot.reset();
if (std::holds_alternative<hidpp::Report>(response))
if (std::holds_alternative<hidpp::Report>(response)) {
return std::get<hidpp::Report>(response);
else // if(std::holds_alternative<Error::ErrorCode>(response))
throw Error(std::get<Error::ErrorCode>(response));
} else { // if(std::holds_alternative<Error::ErrorCode>(response))
auto error = std::get<hidpp::Report::Hidpp20Error>(response);
throw Error(error.error_code, error.device_index);
}
}
void Device::sendReportNoACK(const hidpp::Report& report) {
@ -137,7 +139,7 @@ bool Device::responseReport(const hidpp::Report& report) {
bool is_error = false;
hidpp::Report::Hidpp20Error hidpp20_error{};
if (report.isError20(&hidpp20_error)) {
if (report.isError20(hidpp20_error)) {
is_error = true;
sw_id = hidpp20_error.software_id;
feature = hidpp20_error.feature_index;
@ -154,7 +156,7 @@ bool Device::responseReport(const hidpp::Report& report) {
}
if (is_error) {
response_slot.response = static_cast<Error::ErrorCode>(hidpp20_error.error_code);
response_slot.response = hidpp20_error;
} else {
response_slot.response = report;
}

View File

@ -56,7 +56,7 @@ namespace logid::backend::hidpp20 {
bool responseReport(const hidpp::Report& report) final;
private:
typedef std::variant<hidpp::Report, Error::ErrorCode> Response;
typedef std::variant<hidpp::Report, hidpp::Report::Hidpp20Error> Response;
struct ResponseSlot {
std::optional<Response> response;
std::optional<uint8_t> feature;

View File

@ -19,9 +19,10 @@
#include <backend/hidpp20/Error.h>
#include <cassert>
using namespace logid::backend;
using namespace logid::backend::hidpp20;
Error::Error(uint8_t code) : _code(code) {
Error::Error(uint8_t code, hidpp::DeviceIndex index) : _code(code), _index (index) {
assert(_code != NoError);
}
@ -56,4 +57,8 @@ const char* Error::what() const noexcept {
uint8_t Error::code() const noexcept {
return _code;
}
hidpp::DeviceIndex Error::deviceIndex() const noexcept {
return _index;
}

View File

@ -19,6 +19,7 @@
#ifndef LOGID_BACKEND_HIDPP20_ERROR_H
#define LOGID_BACKEND_HIDPP20_ERROR_H
#include <backend/hidpp/defs.h>
#include <stdexcept>
#include <cstdint>
@ -41,14 +42,17 @@ namespace logid::backend::hidpp20 {
UnknownDevice = 10
};
explicit Error(uint8_t code);
Error(uint8_t code, hidpp::DeviceIndex index);
[[nodiscard]] const char* what() const noexcept override;
[[nodiscard]] uint8_t code() const noexcept;
[[nodiscard]] hidpp::DeviceIndex deviceIndex() const noexcept;
private:
uint8_t _code;
hidpp::DeviceIndex _index;
};
}

View File

@ -40,8 +40,7 @@ namespace logid::backend::hidpp20 {
std::vector<uint8_t> callFunction(uint8_t function_id,
std::vector<uint8_t>& params);
private:
hidpp::Device* _device;
hidpp::Device* const _device;
uint8_t _index;
};
}

View File

@ -55,8 +55,7 @@ namespace logid::backend::hidpp20 {
void callFunctionNoResponse(uint8_t function_id, std::vector<uint8_t>& params);
private:
Device* _device;
Device* const _device;
uint8_t _index;
};
}

View File

@ -16,6 +16,7 @@
*
*/
#include <backend/hidpp20/features/ChangeHost.h>
#include <backend/hidpp20/Device.h>
#include <backend/hidpp20/Error.h>
using namespace logid::backend::hidpp20;
@ -48,7 +49,7 @@ void ChangeHost::setHost(uint8_t host) {
getHostInfo();
if (host >= _host_count)
throw hidpp20::Error(hidpp20::Error::InvalidArgument);
throw Error(hidpp20::Error::InvalidArgument, _device->deviceIndex());
std::vector<uint8_t> params = {host};

View File

@ -17,6 +17,7 @@
*/
#include <backend/hidpp20/features/ReprogControls.h>
#include <backend/hidpp20/Error.h>
#include <backend/hidpp20/Device.h>
#include <cassert>
using namespace logid::backend::hidpp20;
@ -106,7 +107,7 @@ ReprogControls::ControlInfo ReprogControls::getControlIdInfo(uint16_t cid) {
auto it = _cids.find(cid);
if (it == _cids.end())
throw Error(Error::InvalidArgument);
throw Error(Error::InvalidArgument, _device->deviceIndex());
else
return it->second;
}

View File

@ -61,6 +61,15 @@ feature_info Root::getFeature(uint16_t feature_id) {
}
}
uint8_t Root::ping(uint8_t byte) {
std::vector<uint8_t> params(3);
params[2] = byte;
auto response = this->callFunction(Root::Function::Ping, params);
return response[2];
}
std::tuple<uint8_t, uint8_t> Root::getVersion() {
std::vector<uint8_t> params(0);
auto response = this->callFunction(Root::Function::Ping, params);

View File

@ -39,6 +39,8 @@ namespace logid::backend::hidpp20 {
feature_info getFeature(uint16_t feature_id);
uint8_t ping(uint8_t byte);
std::tuple<uint8_t, uint8_t> getVersion();
enum FeatureFlag : uint8_t {

View File

@ -20,6 +20,7 @@
#include <backend/raw/IOMonitor.h>
#include <backend/raw/RawDevice.h>
#include <backend/hidpp/Device.h>
#include <backend/Error.h>
#include <util/task.h>
#include <util/log.h>
#include <system_error>
@ -95,15 +96,7 @@ void DeviceMonitor::ready() {
std::string dev_node = udev_device_get_devnode(device);
if (action == "add")
spawn_task([this, dev_node]() {
/* Device was just connected, may not be ready yet.
* Sleep for 2 seconds to ensure device is ready before attempting to add.
* This is a bit of a hack and this time was determined through a bit of
* experimentation.
*/
std::this_thread::sleep_for(std::chrono::milliseconds(ready_wait));
_addHandler(dev_node);
});
spawn_task([this, dev_node]() { _addHandler(dev_node); });
else if (action == "remove")
spawn_task([this, dev_node]() { _removeHandler(dev_node); });
@ -144,24 +137,37 @@ void DeviceMonitor::enumerate() {
std::string dev_node = udev_device_get_devnode(device);
udev_device_unref(device);
_addHandler(dev_node);
spawn_task([this, dev_node]() { _addHandler(dev_node); });
}
udev_enumerate_unref(udev_enum);
}
void DeviceMonitor::_addHandler(const std::string& device) {
try {
auto supported_reports = backend::hidpp::getSupportedReports(
RawDevice::getReportDescriptor(device));
if (supported_reports)
this->addDevice(device);
else
logPrintf(DEBUG, "Unsupported device %s ignored",
device.c_str());
} catch (std::exception& e) {
logPrintf(WARN, "Error adding device %s: %s",
device.c_str(), e.what());
int tries;
for (tries = 0; tries < max_tries; ++tries) {
try {
auto supported_reports = backend::hidpp::getSupportedReports(
RawDevice::getReportDescriptor(device));
if (supported_reports)
this->addDevice(device);
else
logPrintf(DEBUG, "Unsupported device %s ignored", device.c_str());
break;
} catch (backend::DeviceNotReady& e) {
/* Do exponential backoff for 2^tries * backoff ms. */
std::chrono::milliseconds timeout((1 << tries) * ready_backoff);
logPrintf(DEBUG, "Failed to add device %s on try %d, backing off for %dms",
device.c_str(), tries + 1, timeout.count());
std::this_thread::sleep_for(timeout);
} catch (std::exception& e) {
logPrintf(WARN, "Error adding device %s: %s", device.c_str(), e.what());
break;
}
}
if (tries == max_tries) {
logPrintf(WARN, "Failed to add device %s after %d tries. Treating as failure.",
device.c_str(), max_tries);
}
}

View File

@ -33,7 +33,8 @@ struct udev_monitor;
namespace logid::backend::raw {
class IOMonitor;
static constexpr int ready_wait = 2000;
static constexpr int max_tries = 5;
static constexpr int ready_backoff = 500;
class DeviceMonitor {
public: