From 4f735a651cb71b44b5421189742c3a61782a4402 Mon Sep 17 00:00:00 2001 From: EricLiu2000 Date: Fri, 2 Aug 2019 14:54:14 -0400 Subject: [PATCH] Added event callback functionality. EventManager now uses multiple mutexes to lock events, errors, callbacks, and downgradedThreads separately. Wrote single-threaded test for event callbacks. --- api/icsneocpp/eventmanager.cpp | 26 +++++++++-- docs/Usage.rst | 1 + include/icsneo/api/eventcallback.h | 39 +++++++++++++++++ include/icsneo/api/eventmanager.h | 66 ++++++++++++++++++---------- test/eventmanagertest.cpp | 70 ++++++++++++++++++++++++++++++ 5 files changed, 177 insertions(+), 25 deletions(-) create mode 100644 include/icsneo/api/eventcallback.h diff --git a/api/icsneocpp/eventmanager.cpp b/api/icsneocpp/eventmanager.cpp index 757e1d5..cf94c02 100644 --- a/api/icsneocpp/eventmanager.cpp +++ b/api/icsneocpp/eventmanager.cpp @@ -18,6 +18,7 @@ void EventManager::ResetInstance() { // If this thread is not in the map, add it to be ignored // If it is, set it to be ignored void EventManager::downgradeErrorsOnCurrentThread() { + std::lock_guard lk(downgradedThreadsMutex); auto i = downgradedThreads.find(std::this_thread::get_id()); if(i != downgradedThreads.end()) { i->second = true; @@ -28,14 +29,33 @@ void EventManager::downgradeErrorsOnCurrentThread() { // If this thread exists in the map, turn off downgrading void EventManager::cancelErrorDowngradingOnCurrentThread() { + std::lock_guard lk(downgradedThreadsMutex); auto i = downgradedThreads.find(std::this_thread::get_id()); if(i != downgradedThreads.end()) { i->second = false; } } +int EventManager::addEventCallback(const EventCallback &cb) { + std::lock_guard lk(callbacksMutex); + callbacks.insert({callbackID, cb}); + return callbackID++; +} + +bool EventManager::removeEventCallback(int id) { + std::lock_guard lk(callbacksMutex); + + auto iter = callbacks.find(id); + + if(iter != callbacks.end()) { + callbacks.erase(iter); + return true; + } else + return false; +} + void EventManager::get(std::vector& eventOutput, size_t max, EventFilter filter) { - std::lock_guard lk(mutex); + std::lock_guard lk(eventsMutex); if(max == 0) // A limit of 0 indicates no limit max = (size_t)-1; @@ -60,7 +80,7 @@ void EventManager::get(std::vector& eventOutput, size_t max, EventFilt * If no error was found, return a NoErrorFound Info event */ APIEvent EventManager::getLastError() { - std::lock_guard lk(mutex); + std::lock_guard lk(errorsMutex); auto it = lastUserErrors.find(std::this_thread::get_id()); if(it == lastUserErrors.end()) { @@ -73,7 +93,7 @@ APIEvent EventManager::getLastError() { } void EventManager::discard(EventFilter filter) { - std::lock_guard lk(mutex); + std::lock_guard lk(eventsMutex); events.remove_if([&filter](const APIEvent& event) { return filter.match(event); }); diff --git a/docs/Usage.rst b/docs/Usage.rst index e2df898..bcd8e90 100644 --- a/docs/Usage.rst +++ b/docs/Usage.rst @@ -79,6 +79,7 @@ It is recommended to either enable message polling or manually register callback Write Blocking Status ~~~~~~~~~~~~~~~~~~~~~~~ + The write blocking status of the device determines the behavior of attempting to transmit to the device (likely via sending messages) with a large backlog of messages. If write blocking is enabled, then the transmitting thread will wait for the entire buffer to be transmitted. If write blocking is disabled, then the attempt to transmit will simply fail and an error will be logged on the calling thread. \ No newline at end of file diff --git a/include/icsneo/api/eventcallback.h b/include/icsneo/api/eventcallback.h new file mode 100644 index 0000000..73af690 --- /dev/null +++ b/include/icsneo/api/eventcallback.h @@ -0,0 +1,39 @@ +#ifndef __EVENTCALLBACK_H_ +#define __EVENTCALLBACK_H_ + +#include +#include +#include "event.h" + +namespace icsneo { + +class EventCallback { +public: + typedef std::function< void( std::shared_ptr ) > fn_eventCallback; + + EventCallback(fn_eventCallback cb, std::shared_ptr f) : callback(cb), filter(f) {} + EventCallback(fn_eventCallback cb, EventFilter f = EventFilter()) : callback(cb), filter(std::make_shared(f)) {} + + // Allow the filter to be placed first if the user wants (maybe in the case of a lambda) + EventCallback(std::shared_ptr f, fn_eventCallback cb) : callback(cb), filter(f) {} + EventCallback(EventFilter f, fn_eventCallback cb) : callback(cb), filter(std::make_shared(f)) {} + + virtual bool callIfMatch(const std::shared_ptr& event) const { + bool ret = filter->match(*event); + if(ret) + callback(event); + return ret; + } + + const EventFilter& getFilter() const { return *filter; } + const fn_eventCallback& getCallback() const { return callback; } + +private: + fn_eventCallback callback; + std::shared_ptr filter; + +}; + +} + +#endif \ No newline at end of file diff --git a/include/icsneo/api/eventmanager.h b/include/icsneo/api/eventmanager.h index b1fec3b..b035a44 100644 --- a/include/icsneo/api/eventmanager.h +++ b/include/icsneo/api/eventmanager.h @@ -9,6 +9,7 @@ #include #include #include "icsneo/api/event.h" +#include "icsneo/api/eventcallback.h" namespace icsneo { @@ -24,8 +25,12 @@ public: void cancelErrorDowngradingOnCurrentThread(); + int addEventCallback(const EventCallback &cb); + + bool removeEventCallback(int id); + size_t eventCount(EventFilter filter = EventFilter()) const { - std::lock_guard lk(mutex); + std::lock_guard lk(eventsMutex); return count_internal(filter); }; @@ -41,17 +46,34 @@ public: APIEvent getLastError(); void add(APIEvent event) { - std::lock_guard lk(mutex); - add_internal(event); + if(event.getSeverity() == APIEvent::Severity::Error) { + // if the error was added on a thread that downgrades errors (non-user thread) + std::lock_guard lk(downgradedThreadsMutex); + auto i = downgradedThreads.find(std::this_thread::get_id()); + if(i != downgradedThreads.end() && i->second) { + event.downgradeFromError(); + runCallbacks(event); + std::lock_guard lk(eventsMutex); + add_internal_event(event); + } else { + std::lock_guard lk(errorsMutex); + add_internal_error(event); + } + } else { + runCallbacks(event); + std::lock_guard lk(eventsMutex); + add_internal_event(event); + } } void add(APIEvent::Type type, APIEvent::Severity severity, const Device* forDevice = nullptr) { - std::lock_guard lk(mutex); - add_internal(APIEvent(type, severity, forDevice)); + add(APIEvent(type, severity, forDevice)); } void discard(EventFilter filter = EventFilter()); void setEventLimit(size_t newLimit) { + std::lock_guard lk(eventsMutex); + if(newLimit == eventLimit) return; @@ -59,27 +81,33 @@ public: add(APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error); return; } - - std::lock_guard lk(mutex); + eventLimit = newLimit; if(enforceLimit()) - add_internal(APIEvent(APIEvent::Type::TooManyEvents, APIEvent::Severity::EventWarning)); + add_internal_event(APIEvent(APIEvent::Type::TooManyEvents, APIEvent::Severity::EventWarning)); } size_t getEventLimit() const { - std::lock_guard lk(mutex); + std::lock_guard lk(eventsMutex); return eventLimit; } private: - EventManager() : mutex(), downgradedThreads(), events(), lastUserErrors(), eventLimit(10000) {} + EventManager() : eventsMutex(), errorsMutex(), downgradedThreadsMutex(), callbacksMutex(), downgradedThreads(), callbacks(), events(), lastUserErrors(), eventLimit(10000) {} EventManager(const EventManager &other); EventManager& operator=(const EventManager &other); // Used by functions for threadsafety - mutable std::mutex mutex; + mutable std::mutex eventsMutex; + mutable std::mutex errorsMutex; + mutable std::mutex downgradedThreadsMutex; + mutable std::mutex callbacksMutex; std::map downgradedThreads; + + std::map callbacks; + + int callbackID = 0; // Stores all events std::list events; @@ -88,17 +116,11 @@ private: size_t count_internal(EventFilter filter = EventFilter()) const; - void add_internal(APIEvent event) { - if(event.getSeverity() == APIEvent::Severity::Error) { - // if the error was added on a thread that downgrades errors (non-user thread) - auto i = downgradedThreads.find(std::this_thread::get_id()); - if(i != downgradedThreads.end() && i->second) { - event.downgradeFromError(); - add_internal_event(event); - } else - add_internal_error(event); - } else - add_internal_event(event); + void runCallbacks(APIEvent event) { + std::lock_guard lk(callbacksMutex); + for(auto &i : callbacks) { + i.second.callIfMatch(std::make_shared(event)); + } } /** diff --git a/test/eventmanagertest.cpp b/test/eventmanagertest.cpp index 76e4543..ce3068d 100644 --- a/test/eventmanagertest.cpp +++ b/test/eventmanagertest.cpp @@ -1,4 +1,5 @@ #include +#include #include "icsneo/icsneocpp.h" #include "gtest/gtest.h" @@ -13,6 +14,75 @@ protected: } }; + +TEST_F(EventManagerTest, SingleThreadCallbacksTest) { + int callCounter = 0; + + // increments counter when baudrate events show up + int id1 = EventManager::GetInstance().addEventCallback(EventCallback([&callCounter](std::shared_ptr){ + callCounter++; + }, EventFilter(APIEvent::Type::BaudrateNotFound))); + + // increments counter when infos show up + int id2 = EventManager::GetInstance().addEventCallback(EventCallback([&callCounter](std::shared_ptr) { + callCounter++; + }, EventFilter(APIEvent::Severity::EventInfo))); + + EXPECT_EQ(callCounter, 0); + + EventManager::GetInstance().add(APIEvent(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::EventWarning)); + + EXPECT_EQ(callCounter, 0); + + EventManager::GetInstance().add(APIEvent(APIEvent::Type::BaudrateNotFound, APIEvent::Severity::EventWarning)); + + EXPECT_EQ(callCounter, 1); + + EventManager::GetInstance().add(APIEvent(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::EventInfo)); + + EXPECT_EQ(callCounter, 2); + + EventManager::GetInstance().add(APIEvent(APIEvent::Type::BaudrateNotFound, APIEvent::Severity::EventInfo)); + + EXPECT_EQ(callCounter, 4); + + EXPECT_EQ(EventManager::GetInstance().removeEventCallback(id2), true); + + EventManager::GetInstance().add(APIEvent(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::EventInfo)); + + EXPECT_EQ(callCounter, 4); + + EventManager::GetInstance().add(APIEvent(APIEvent::Type::BaudrateNotFound, APIEvent::Severity::EventInfo)); + + EXPECT_EQ(callCounter, 5); + + // increments counter when device currently open shows up + int id3 = EventManager::GetInstance().addEventCallback(EventCallback([&callCounter](std::shared_ptr) { + callCounter++; + }, EventFilter(APIEvent::Type::DeviceCurrentlyOpen))); + + EventManager::GetInstance().add(APIEvent(APIEvent::Type::DeviceCurrentlyOpen, APIEvent::Severity::EventInfo)); + + EXPECT_EQ(callCounter, 6); + + EXPECT_EQ(EventManager::GetInstance().removeEventCallback(id2), false); + EXPECT_EQ(EventManager::GetInstance().removeEventCallback(id1), true); + + EventManager::GetInstance().add(APIEvent(APIEvent::Type::BaudrateNotFound, APIEvent::Severity::EventInfo)); + + EXPECT_EQ(callCounter, 6); + + EventManager::GetInstance().add(APIEvent(APIEvent::Type::DeviceCurrentlyOpen, APIEvent::Severity::EventInfo)); + + EXPECT_EQ(callCounter, 7); + + EXPECT_EQ(EventManager::GetInstance().removeEventCallback(id3), true); + + EventManager::GetInstance().add(APIEvent(APIEvent::Type::DeviceCurrentlyOpen, APIEvent::Severity::EventInfo)); + + EXPECT_EQ(callCounter, 7); +} + TEST_F(EventManagerTest, ErrorDowngradingTest) { // Check that main thread has no errors EXPECT_EQ(GetLastError().getType(), APIEvent::Type::NoErrorFound);