Improve error handling with separate ICanErrorListener
Error handling highlights:
- moved onError from ICanMessageListener to ICanErrorListener
- added isFatal callback argument to request client disconnect
- don't down interface that's already down
Also:
- don't crash if it's not possible to unregister ICanBus
- don't crash while trying to down interface that failed
- make hidl-utils available to vendor libraries
Bug: 143779011
Test: implemented a VHAL service prototype that communicates with this HAL
Change-Id: I98d054da9da0ead5ef952aebc086e052ac996212
diff --git a/automotive/can/1.0/default/CanBus.cpp b/automotive/can/1.0/default/CanBus.cpp
index 65da291..38a9974 100644
--- a/automotive/can/1.0/default/CanBus.cpp
+++ b/automotive/can/1.0/default/CanBus.cpp
@@ -68,14 +68,14 @@
return {};
}
- std::lock_guard<std::mutex> lckListeners(mListenersGuard);
+ std::lock_guard<std::mutex> lckListeners(mMsgListenersGuard);
sp<CloseHandle> closeHandle = new CloseHandle([this, listenerCb]() {
- std::lock_guard<std::mutex> lck(mListenersGuard);
- std::erase_if(mListeners, [&](const auto& e) { return e.callback == listenerCb; });
+ std::lock_guard<std::mutex> lck(mMsgListenersGuard);
+ std::erase_if(mMsgListeners, [&](const auto& e) { return e.callback == listenerCb; });
});
- mListeners.emplace_back(CanMessageListener{listenerCb, filter, closeHandle});
- auto& listener = mListeners.back();
+ mMsgListeners.emplace_back(CanMessageListener{listenerCb, filter, closeHandle});
+ auto& listener = mMsgListeners.back();
// fix message IDs to have all zeros on bits not covered by mask
std::for_each(listener.filter.begin(), listener.filter.end(),
@@ -91,8 +91,15 @@
std::lock_guard<std::mutex> lck(mIsUpGuard);
CHECK(!mIsUp) << "Interface is still up while being destroyed";
- std::lock_guard<std::mutex> lckListeners(mListenersGuard);
- CHECK(mListeners.empty()) << "Listeners list is not empty while interface is being destroyed";
+ std::lock_guard<std::mutex> lckListeners(mMsgListenersGuard);
+ CHECK(mMsgListeners.empty()) << "Listener list is not empty while interface is being destroyed";
+}
+
+void CanBus::setErrorCallback(ErrorCallback errcb) {
+ CHECK(!mIsUp) << "Can't set error callback while interface is up";
+ CHECK(mErrCb == nullptr) << "Error callback is already set";
+ mErrCb = errcb;
+ CHECK(!mIsUp) << "Can't set error callback while interface is up";
}
ICanController::Result CanBus::preUp() {
@@ -120,19 +127,19 @@
LOG(ERROR) << "Interface " << mIfname << " didn't get prepared";
return ICanController::Result::BAD_ADDRESS;
}
- mWasUpInitially = *isUp;
- if (!mWasUpInitially && !netdevice::up(mIfname)) {
+ if (!*isUp && !netdevice::up(mIfname)) {
LOG(ERROR) << "Can't bring " << mIfname << " up";
return ICanController::Result::UNKNOWN_ERROR;
}
+ mDownAfterUse = !*isUp;
using namespace std::placeholders;
CanSocket::ReadCallback rdcb = std::bind(&CanBus::onRead, this, _1, _2);
- CanSocket::ErrorCallback errcb = std::bind(&CanBus::onError, this);
+ CanSocket::ErrorCallback errcb = std::bind(&CanBus::onError, this, _1);
mSocket = CanSocket::open(mIfname, rdcb, errcb);
if (!mSocket) {
- if (!mWasUpInitially) netdevice::down(mIfname);
+ if (mDownAfterUse) netdevice::down(mIfname);
return ICanController::Result::UNKNOWN_ERROR;
}
@@ -140,24 +147,50 @@
return ICanController::Result::OK;
}
-void CanBus::clearListeners() {
+void CanBus::clearMsgListeners() {
std::vector<wp<ICloseHandle>> listenersToClose;
{
- std::lock_guard<std::mutex> lck(mListenersGuard);
- std::transform(mListeners.begin(), mListeners.end(), std::back_inserter(listenersToClose),
+ std::lock_guard<std::mutex> lck(mMsgListenersGuard);
+ std::transform(mMsgListeners.begin(), mMsgListeners.end(),
+ std::back_inserter(listenersToClose),
[](const auto& e) { return e.closeHandle; });
}
for (auto& weakListener : listenersToClose) {
/* Between populating listenersToClose and calling close method here, some listeners might
- * have been already removed from the original mListeners list (resulting in a dangling weak
- * pointer here). It's fine - we just want to clean them up. */
+ * have been already removed from the original mMsgListeners list (resulting in a dangling
+ * weak pointer here). It's fine - we just want to clean them up. */
auto listener = weakListener.promote();
if (listener != nullptr) listener->close();
}
- std::lock_guard<std::mutex> lck(mListenersGuard);
- CHECK(mListeners.empty()) << "Listeners list wasn't emptied";
+ std::lock_guard<std::mutex> lck(mMsgListenersGuard);
+ CHECK(mMsgListeners.empty()) << "Listeners list wasn't emptied";
+}
+
+void CanBus::clearErrListeners() {
+ std::lock_guard<std::mutex> lck(mErrListenersGuard);
+ mErrListeners.clear();
+}
+
+Return<sp<ICloseHandle>> CanBus::listenForErrors(const sp<ICanErrorListener>& listener) {
+ if (listener == nullptr) {
+ return new CloseHandle();
+ }
+
+ std::lock_guard<std::mutex> upLck(mIsUpGuard);
+ if (!mIsUp) {
+ listener->onError(ErrorEvent::INTERFACE_DOWN, true);
+ return new CloseHandle();
+ }
+
+ std::lock_guard<std::mutex> errLck(mErrListenersGuard);
+ mErrListeners.emplace_back(listener);
+
+ return new CloseHandle([this, listener]() {
+ std::lock_guard<std::mutex> lck(mErrListenersGuard);
+ std::erase(mErrListeners, listener);
+ });
}
bool CanBus::down() {
@@ -169,12 +202,13 @@
}
mIsUp = false;
- clearListeners();
+ clearMsgListeners();
+ clearErrListeners();
mSocket.reset();
bool success = true;
- if (!mWasUpInitially && !netdevice::down(mIfname)) {
+ if (mDownAfterUse && !netdevice::down(mIfname)) {
LOG(ERROR) << "Can't bring " << mIfname << " down";
// don't return yet, let's try to do best-effort cleanup
success = false;
@@ -223,22 +257,35 @@
LOG(VERBOSE) << "Got message " << toString(message);
}
- std::lock_guard<std::mutex> lck(mListenersGuard);
- for (auto& listener : mListeners) {
+ std::lock_guard<std::mutex> lck(mMsgListenersGuard);
+ for (auto& listener : mMsgListeners) {
if (!match(listener.filter, message.id)) continue;
- if (!listener.callback->onReceive(message).isOk()) {
+ if (!listener.callback->onReceive(message).isOk() && !listener.failedOnce) {
+ listener.failedOnce = true;
LOG(WARNING) << "Failed to notify listener about message";
}
}
}
-void CanBus::onError() {
- std::lock_guard<std::mutex> lck(mListenersGuard);
- for (auto& listener : mListeners) {
- if (!listener.callback->onError(ErrorEvent::HARDWARE_ERROR).isOk()) {
- LOG(WARNING) << "Failed to notify listener about error";
+void CanBus::onError(int errnoVal) {
+ auto eventType = ErrorEvent::HARDWARE_ERROR;
+
+ if (errnoVal == ENODEV || errnoVal == ENETDOWN) {
+ mDownAfterUse = false;
+ eventType = ErrorEvent::INTERFACE_DOWN;
+ }
+
+ {
+ std::lock_guard<std::mutex> lck(mErrListenersGuard);
+ for (auto& listener : mErrListeners) {
+ if (!listener->onError(eventType, true).isOk()) {
+ LOG(WARNING) << "Failed to notify listener about error";
+ }
}
}
+
+ const auto errcb = mErrCb;
+ if (errcb != nullptr) errcb();
}
} // namespace implementation
diff --git a/automotive/can/1.0/default/CanBus.h b/automotive/can/1.0/default/CanBus.h
index 81a83c8..30a2924 100644
--- a/automotive/can/1.0/default/CanBus.h
+++ b/automotive/can/1.0/default/CanBus.h
@@ -34,12 +34,16 @@
namespace implementation {
struct CanBus : public ICanBus {
+ using ErrorCallback = std::function<void()>;
+
virtual ~CanBus();
Return<Result> send(const CanMessage& message) override;
Return<void> listen(const hidl_vec<CanMessageFilter>& filter,
const sp<ICanMessageListener>& listener, listen_cb _hidl_cb) override;
+ Return<sp<ICloseHandle>> listenForErrors(const sp<ICanErrorListener>& listener) override;
+ void setErrorCallback(ErrorCallback errcb);
ICanController::Result up();
bool down();
@@ -68,17 +72,22 @@
sp<ICanMessageListener> callback;
hidl_vec<CanMessageFilter> filter;
wp<ICloseHandle> closeHandle;
+ bool failedOnce = false;
};
- void clearListeners();
+ void clearMsgListeners();
+ void clearErrListeners();
void onRead(const struct canfd_frame& frame, std::chrono::nanoseconds timestamp);
- void onError();
+ void onError(int errnoVal);
- std::mutex mListenersGuard;
- std::vector<CanMessageListener> mListeners GUARDED_BY(mListenersGuard);
+ std::mutex mMsgListenersGuard;
+ std::vector<CanMessageListener> mMsgListeners GUARDED_BY(mMsgListenersGuard);
+
+ std::mutex mErrListenersGuard;
+ std::vector<sp<ICanErrorListener>> mErrListeners GUARDED_BY(mErrListenersGuard);
std::unique_ptr<CanSocket> mSocket;
- bool mWasUpInitially;
+ bool mDownAfterUse;
/**
* Guard for up flag is required to be held for entire time when the interface is being used
@@ -87,6 +96,8 @@
*/
std::mutex mIsUpGuard;
bool mIsUp GUARDED_BY(mIsUpGuard) = false;
+
+ ErrorCallback mErrCb;
};
} // namespace implementation
diff --git a/automotive/can/1.0/default/CanController.cpp b/automotive/can/1.0/default/CanController.cpp
index 20adbe1..3b63fe4 100644
--- a/automotive/can/1.0/default/CanController.cpp
+++ b/automotive/can/1.0/default/CanController.cpp
@@ -81,6 +81,8 @@
return ICanController::Result::NOT_SUPPORTED;
}
+ busService->setErrorCallback([this, name = config.name]() { downInterface(name); });
+
const auto result = busService->up();
if (result != ICanController::Result::OK) return result;
@@ -97,6 +99,14 @@
return ICanController::Result::OK;
}
+static bool unregisterCanBusService(const hidl_string& name, sp<CanBus> busService) {
+ auto manager = hidl::manager::V1_2::IServiceManager::getService();
+ if (!manager) return false;
+ const auto res = manager->tryUnregister(ICanBus::descriptor, name, busService);
+ if (!res.isOk()) return false;
+ return res;
+}
+
Return<bool> CanController::downInterface(const hidl_string& name) {
LOG(VERBOSE) << "Attempting to bring interface down: " << name;
@@ -110,8 +120,7 @@
auto success = true;
- auto manager = hidl::manager::V1_2::IServiceManager::getService();
- if (!manager || !manager->tryUnregister(ICanBus::descriptor, name, busEntry.mapped())) {
+ if (!unregisterCanBusService(name, busEntry.mapped())) {
LOG(ERROR) << "Couldn't unregister " << name;
// don't return yet, let's try to do best-effort cleanup
success = false;
diff --git a/automotive/can/1.0/default/CanSocket.cpp b/automotive/can/1.0/default/CanSocket.cpp
index 4d86de6..ecf4044 100644
--- a/automotive/can/1.0/default/CanSocket.cpp
+++ b/automotive/can/1.0/default/CanSocket.cpp
@@ -61,7 +61,14 @@
CanSocket::~CanSocket() {
mStopReaderThread = true;
- mReaderThread.join();
+
+ /* CanSocket can be brought down as a result of read failure, from the same thread,
+ * so let's just detach and let it finish on its own. */
+ if (mReaderThreadFinished) {
+ mReaderThread.detach();
+ } else {
+ mReaderThread.join();
+ }
}
bool CanSocket::send(const struct canfd_frame& frame) {
@@ -94,6 +101,7 @@
void CanSocket::readerThread() {
LOG(VERBOSE) << "Reader thread started";
+ int errnoCopy = 0;
while (!mStopReaderThread) {
/* The ideal would be to have a blocking read(3) call and interrupt it with shutdown(3).
@@ -130,14 +138,20 @@
}
if (errno == EAGAIN) continue;
- LOG(ERROR) << "Failed to read CAN packet: " << errno;
+ errnoCopy = errno;
+ LOG(ERROR) << "Failed to read CAN packet: " << strerror(errno) << " (" << errno << ")";
break;
}
mReadCallback(frame, ts);
}
- if (!mStopReaderThread) mErrorCallback();
+ bool failed = !mStopReaderThread;
+ auto errCb = mErrorCallback;
+ mReaderThreadFinished = true;
+
+ // Don't access any fields from here, see CanSocket::~CanSocket comment about detached thread
+ if (failed) errCb(errnoCopy);
LOG(VERBOSE) << "Reader thread stopped";
}
diff --git a/automotive/can/1.0/default/CanSocket.h b/automotive/can/1.0/default/CanSocket.h
index cd7162d..284e1ea 100644
--- a/automotive/can/1.0/default/CanSocket.h
+++ b/automotive/can/1.0/default/CanSocket.h
@@ -36,7 +36,7 @@
*/
struct CanSocket {
using ReadCallback = std::function<void(const struct canfd_frame&, std::chrono::nanoseconds)>;
- using ErrorCallback = std::function<void()>;
+ using ErrorCallback = std::function<void(int errnoVal)>;
/**
* Open and bind SocketCAN socket.
@@ -68,6 +68,7 @@
const base::unique_fd mSocket;
std::thread mReaderThread;
std::atomic<bool> mStopReaderThread = false;
+ std::atomic<bool> mReaderThreadFinished = false;
DISALLOW_COPY_AND_ASSIGN(CanSocket);
};
diff --git a/automotive/can/1.0/default/CloseHandle.cpp b/automotive/can/1.0/default/CloseHandle.cpp
index 13693d2..aba2c49 100644
--- a/automotive/can/1.0/default/CloseHandle.cpp
+++ b/automotive/can/1.0/default/CloseHandle.cpp
@@ -33,7 +33,7 @@
const auto wasClosed = mIsClosed.exchange(true);
if (wasClosed) return {};
- mCallback();
+ if (mCallback != nullptr) mCallback();
return {};
}
diff --git a/automotive/can/1.0/default/CloseHandle.h b/automotive/can/1.0/default/CloseHandle.h
index 94972e0..5191739 100644
--- a/automotive/can/1.0/default/CloseHandle.h
+++ b/automotive/can/1.0/default/CloseHandle.h
@@ -39,13 +39,13 @@
*
* \param callback Called on the first close() call, or on destruction of the handle
*/
- CloseHandle(Callback callback);
+ CloseHandle(Callback callback = nullptr);
virtual ~CloseHandle();
Return<void> close() override;
private:
- Callback mCallback;
+ const Callback mCallback;
std::atomic<bool> mIsClosed = false;
DISALLOW_COPY_AND_ASSIGN(CloseHandle);