From a18c42df8f27093a407ce428d98698c14b28fe73 Mon Sep 17 00:00:00 2001 From: Mateusz Malicki Date: Mon, 27 Jul 2015 17:28:22 +0200 Subject: [PATCH] InputMonitor connected to epoll dispatcher [Feature] InputMonitor connected to epoll dispatcher [Cause] N/A [Solution] N/A [Verification] Build, run tests Change-Id: If2bdcd8c8493bc22357aa93b43b5c6e530c954d4 --- server/input-monitor.cpp | 139 +++++++++++---------------- server/input-monitor.hpp | 36 +++---- server/zones-manager.cpp | 16 ++- server/zones-manager.hpp | 3 +- tests/unit_tests/server/ut-input-monitor.cpp | 125 ++++++++++++++++++------ 5 files changed, 179 insertions(+), 140 deletions(-) diff --git a/server/input-monitor.cpp b/server/input-monitor.cpp index e99906f..bb65b90 100644 --- a/server/input-monitor.cpp +++ b/server/input-monitor.cpp @@ -26,19 +26,17 @@ #include "input-monitor-config.hpp" #include "input-monitor.hpp" +#include "zones-manager.hpp" #include "exception.hpp" #include "logger/logger.hpp" #include "utils/exception.hpp" #include "utils/fs.hpp" -#include "utils/callback-wrapper.hpp" -#include "utils/scoped-gerror.hpp" +#include "utils/fd-utils.hpp" #include #include #include -#include -#include #include #include #include @@ -64,10 +62,13 @@ const std::string DEVICE_DIR = "/dev/input/"; } // namespace -InputMonitor::InputMonitor(const InputConfig& inputConfig, - const NotifyCallback& notifyCallback) - : mConfig(inputConfig), - mNotifyCallback(notifyCallback) +InputMonitor::InputMonitor(ipc::epoll::EventPoll& eventPoll, + const InputConfig& inputConfig, + ZonesManager* zonesManager) + : mConfig(inputConfig) + , mZonesManager(zonesManager) + , mFd(-1) + , mEventPoll(eventPoll) { if (mConfig.timeWindowMs > MAX_TIME_WINDOW_SEC * 1000L) { LOGE("Time window exceeds maximum: " << MAX_TIME_WINDOW_SEC); @@ -79,37 +80,39 @@ InputMonitor::InputMonitor(const InputConfig& inputConfig, throw InputMonitorException("Number of events exceeds maximum"); } - std::string devicePath = getDevicePath(); + mDevicePath = getDevicePath(); LOGT("Input monitor configuration: \n" << "\tenabled: " << mConfig.enabled << "\n" << "\tdevice: " << mConfig.device << "\n" - << "\tpath: " << devicePath << "\n" + << "\tpath: " << mDevicePath << "\n" << "\ttype: " << EV_KEY << "\n" << "\tcode: " << mConfig.code << "\n" << "\tvalue: " << KEY_PRESSED << "\n" << "\tnumberOfEvents: " << mConfig.numberOfEvents << "\n" << "\ttimeWindowMs: " << mConfig.timeWindowMs); - - createGIOChannel(devicePath); } InputMonitor::~InputMonitor() { + std::unique_lock mMutex; LOGD("Destroying InputMonitor"); - ScopedGError error; - g_io_channel_unref(mChannelPtr); - if (g_io_channel_shutdown(mChannelPtr, FALSE, &error) - != G_IO_STATUS_NORMAL) { - LOGE("Error during shutting down GIOChannel: " << error->message); - } + stop(); +} - if (!g_source_remove(mSourceId)) { - LOGE("Error during removing the source"); - } +void InputMonitor::start() +{ + std::unique_lock mMutex; + setHandler(mDevicePath); +} + +void InputMonitor::stop() +{ + leaveDevice(); } namespace { + bool isDeviceWithName(const boost::regex& deviceNameRegex, const fs::directory_entry& directoryEntry) { @@ -119,7 +122,7 @@ bool isDeviceWithName(const boost::regex& deviceNameRegex, return false; } - int fd = open(path.c_str(), O_RDONLY); + int fd = ::open(path.c_str(), O_RDONLY); if (fd < 0) { LOGD("Failed to open " << path); return false; @@ -128,13 +131,13 @@ bool isDeviceWithName(const boost::regex& deviceNameRegex, char name[DEVICE_NAME_LENGTH]; if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) { LOGD("Failed to get the device name of: " << path); - if (close(fd) < 0) { + if (::close(fd) < 0) { LOGE("Error during closing file " << path); } return false; } - if (close(fd) < 0) { + if (::close(fd) < 0) { LOGE("Error during closing file " << path); } @@ -146,6 +149,7 @@ bool isDeviceWithName(const boost::regex& deviceNameRegex, return false; } + } // namespace std::string InputMonitor::getDevicePath() const @@ -174,80 +178,47 @@ std::string InputMonitor::getDevicePath() const return it->path().string(); } - - -void InputMonitor::createGIOChannel(const std::string& devicePath) +void InputMonitor::setHandler(const std::string& devicePath) { + using namespace std::placeholders; + // We need NONBLOCK for FIFOs in the tests - int fd = open(devicePath.c_str(), O_RDONLY | O_NONBLOCK); - if (fd < 0) { + mFd = ::open(devicePath.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC); + if (mFd < 0) { LOGE("Cannot create input monitor channel. Device file: " << devicePath << " doesn't exist"); throw InputMonitorException("Device does not exist"); } - - mChannelPtr = g_io_channel_unix_new(fd); - - // Read binary data - if (g_io_channel_set_encoding(mChannelPtr, - NULL, - NULL) != G_IO_STATUS_NORMAL) { - LOGE("Cannot set encoding for input monitor channel "); - throw InputMonitorException("Cannot set encoding"); - } - - using namespace std::placeholders; - ReadDeviceCallback callback = std::bind(&InputMonitor::readDevice, this, _1); - // Add the callback - mSourceId = g_io_add_watch_full(mChannelPtr, - G_PRIORITY_DEFAULT, - G_IO_IN, - readDeviceCallback, - utils::createCallbackWrapper(callback, mGuard.spawn()), - &utils::deleteCallbackWrapper); - if (!mSourceId) { - LOGE("Cannot add watch on device input file"); - throw InputMonitorException("Cannot add watch"); - } + mEventPoll.addFD(mFd, EPOLLIN, std::bind(&InputMonitor::handleInternal, this, _1, _2)); } -gboolean InputMonitor::readDeviceCallback(GIOChannel* gio, - GIOCondition /*condition*/, - gpointer data) -{ - const ReadDeviceCallback& callback = utils::getCallbackFromPointer(data); - callback(gio); - return TRUE; -} - -void InputMonitor::readDevice(GIOChannel* gio) +void InputMonitor::handleInternal(int /* fd */, ipc::epoll::Events events) { struct input_event ie; - gsize nBytesReadTotal = 0; - gsize nBytesRequested = sizeof(struct input_event); - ScopedGError error; - - do { - gsize nBytesRead = 0; - GIOStatus readStatus = g_io_channel_read_chars(gio, - &reinterpret_cast(&ie)[nBytesReadTotal], - nBytesRequested, - &nBytesRead, - &error); - - if (readStatus == G_IO_STATUS_ERROR) { - LOGE("Read from input monitor channel failed: " << error->message); + try { + if (events == EPOLLHUP) { + stop(); return; } - - nBytesRequested -= nBytesRead; - nBytesReadTotal += nBytesRead; - } while (nBytesRequested > 0); - - + utils::read(mFd, &ie, sizeof(struct input_event)); + } catch (const std::exception& ex) { + LOGE("Read from input monitor channel failed: " << ex.what()); + return; + } if (isExpectedEventSequence(ie)) { LOGI("Input monitor detected pattern."); - mNotifyCallback(); + if (mZonesManager->isRunning()) { + mZonesManager->switchingSequenceMonitorNotify(); + } + } +} + +void InputMonitor::leaveDevice() +{ + if (mFd != -1) { + mEventPoll.removeFD(mFd); + utils::close(mFd); + mFd = -1; } } diff --git a/server/input-monitor.hpp b/server/input-monitor.hpp index 2b0ed45..7ab5344 100644 --- a/server/input-monitor.hpp +++ b/server/input-monitor.hpp @@ -26,45 +26,45 @@ #define SERVER_INPUT_MONITOR_HPP #include "input-monitor-config.hpp" -#include "utils/callback-guard.hpp" +#include "ipc/epoll/event-poll.hpp" #include #include -#include -#include #include #include +#include namespace vasum { +class ZonesManager; + class InputMonitor { public: - typedef std::function NotifyCallback; - - InputMonitor(const InputConfig& inputConfig, - const NotifyCallback& notifyCallback); + InputMonitor(ipc::epoll::EventPoll& eventPoll, + const InputConfig& inputConfig, + ZonesManager* zonesManager); ~InputMonitor(); + void start(); + void stop(); private: - typedef std::function ReadDeviceCallback; + typedef std::mutex Mutex; InputConfig mConfig; - NotifyCallback mNotifyCallback; - + ZonesManager* mZonesManager; + int mFd; + ipc::epoll::EventPoll& mEventPoll; std::list mEventTimes; - GIOChannel* mChannelPtr; + std::string mDevicePath; + Mutex mMutex; std::string getDevicePath() const; - void createGIOChannel(const std::string& devicePath); - - // Internal callback to be registered at glib g_io_add_watch() - static gboolean readDeviceCallback(GIOChannel*, GIOCondition, gpointer); + void setHandler(const std::string& devicePath); + void handleInternal(int fd, ipc::epoll::Events events); + void leaveDevice(); bool isExpectedEventSequence(const struct input_event&); - void readDevice(GIOChannel*); - utils::CallbackGuard mGuard; - guint mSourceId; }; } // namespace vasum diff --git a/server/zones-manager.cpp b/server/zones-manager.cpp index 484b3e9..0d88d24 100644 --- a/server/zones-manager.cpp +++ b/server/zones-manager.cpp @@ -173,6 +173,11 @@ ZonesManager::ZonesManager(ipc::epoll::EventPoll& eventPoll, const std::string& configPath, mDynamicConfig, getVasumDbPrefix()); + + if (mConfig.inputConfig.enabled) { + LOGI("Registering input monitor [" << mConfig.inputConfig.device.c_str() << "]"); + mSwitchingSequenceMonitor.reset(new InputMonitor(eventPoll, mConfig.inputConfig, this)); + } } ZonesManager::~ZonesManager() @@ -207,11 +212,8 @@ void ZonesManager::start() LOGD("ZonesManager object instantiated"); if (mConfig.inputConfig.enabled) { - LOGI("Registering input monitor [" << mConfig.inputConfig.device.c_str() << "]"); - mSwitchingSequenceMonitor.reset( - new InputMonitor(mConfig.inputConfig, - std::bind(&ZonesManager::switchingSequenceMonitorNotify, - this))); + LOGI("Starting input monitor "); + mSwitchingSequenceMonitor->start(); } // After everything's initialized start to respond to clients' requests @@ -238,6 +240,10 @@ void ZonesManager::stop(bool wait) // wait for all tasks to complete mWorker.reset(); mHostIPCConnection.stop(wait); + if (mConfig.inputConfig.enabled) { + LOGI("Stopping input monitor "); + mSwitchingSequenceMonitor->stop(); + } mIsRunning = false; } diff --git a/server/zones-manager.hpp b/server/zones-manager.hpp index de6663d..c6f6918 100644 --- a/server/zones-manager.hpp +++ b/server/zones-manager.hpp @@ -198,6 +198,8 @@ public: api::MethodResultBuilder::Pointer result); void handleCleanUpZonesRootCall(api::MethodResultBuilder::Pointer result); + void switchingSequenceMonitorNotify(); + private: typedef std::recursive_mutex Mutex; typedef std::unique_lock Lock; @@ -227,7 +229,6 @@ private: void saveDynamicConfig(); void updateDefaultId(); void refocus(); - void switchingSequenceMonitorNotify(); void generateNewConfig(const std::string& id, const std::string& templatePath); std::string getTemplatePathForExistingZone(const std::string& id); diff --git a/tests/unit_tests/server/ut-input-monitor.cpp b/tests/unit_tests/server/ut-input-monitor.cpp index 22fba47..75079a2 100644 --- a/tests/unit_tests/server/ut-input-monitor.cpp +++ b/tests/unit_tests/server/ut-input-monitor.cpp @@ -29,10 +29,12 @@ #include "input-monitor.hpp" #include "input-monitor-config.hpp" #include "exception.hpp" +#include "zones-manager.hpp" #include "utils/glib-loop.hpp" #include "utils/latch.hpp" #include "utils/scoped-dir.hpp" +#include "ipc/epoll/thread-dispatcher.hpp" #include #include @@ -44,7 +46,6 @@ #include #include - using namespace vasum; using namespace utils; @@ -58,16 +59,24 @@ const int EVENT_CODE = 139; const int EVENT_BUTTON_RELEASED = 0; const int EVENT_BUTTON_PRESSED = 1; -const int SINGLE_EVENT_TIMEOUT = 1000; +const int EVENT_TIMEOUT = 1000; + +const std::string CONFIG_DIR = VSM_TEST_CONFIG_INSTALL_DIR; +const std::string TEST_CONFIG_PATH = CONFIG_DIR + "/test-daemon.conf"; +const std::string SIMPLE_TEMPLATE = "console-ipc"; +const std::string ZONES_PATH = "/tmp/ut-zones"; // the same as in daemon.conf struct Fixture { utils::ScopedGlibLoop mLoop; ScopedDir mTestPathGuard; + ScopedDir mZonesPathGuard; InputConfig inputConfig; struct input_event ie; + ipc::epoll::ThreadDispatcher dispatcher; Fixture() : mTestPathGuard(TEST_DIR) + , mZonesPathGuard(ZONES_PATH) { inputConfig.numberOfEvents = 2; inputConfig.device = TEST_INPUT_DEVICE; @@ -84,46 +93,65 @@ struct Fixture { BOOST_CHECK(::mkfifo(TEST_INPUT_DEVICE.c_str(), S_IWUSR | S_IRUSR) >= 0); } + + ipc::epoll::EventPoll& getPoll() { + return dispatcher.getPoll(); + } }; + +template +bool spinWaitFor(int timeoutMs, Predicate pred) +{ + auto until = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeoutMs); + while (!pred()) { + if (std::chrono::steady_clock::now() >= until) { + return false; + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + return true; +} + } // namespace BOOST_FIXTURE_TEST_SUITE(InputMonitorSuite, Fixture) BOOST_AUTO_TEST_CASE(ConfigOK) { - InputMonitor inputMonitor(inputConfig, InputMonitor::NotifyCallback()); + ZonesManager cm(getPoll(), TEST_CONFIG_PATH); + InputMonitor inputMonitor(getPoll(), inputConfig, &cm); } BOOST_AUTO_TEST_CASE(ConfigTimeWindowMsTooHigh) { + ZonesManager cm(getPoll(), TEST_CONFIG_PATH); inputConfig.timeWindowMs = 50000; - BOOST_REQUIRE_EXCEPTION(InputMonitor inputMonitor(inputConfig, InputMonitor::NotifyCallback()), + BOOST_REQUIRE_EXCEPTION(InputMonitor inputMonitor(getPoll(), inputConfig, &cm), InputMonitorException, WhatEquals("Time window exceeds maximum")); } BOOST_AUTO_TEST_CASE(ConfigDeviceFilePathNotExisting) { + ZonesManager cm(getPoll(), TEST_CONFIG_PATH); inputConfig.device = TEST_INPUT_DEVICE + "notExisting"; - BOOST_REQUIRE_EXCEPTION(InputMonitor inputMonitor(inputConfig, InputMonitor::NotifyCallback()), + BOOST_REQUIRE_EXCEPTION(InputMonitor inputMonitor(getPoll(), inputConfig, &cm), InputMonitorException, WhatEquals("Cannot find a device")); } namespace { -void sendNEvents(Fixture& f, unsigned int noOfEventsToSend) +void sendEvent(Fixture& f, ZonesManager& cm) { - Latch eventLatch; - - InputMonitor inputMonitor(f.inputConfig, [&] {eventLatch.set();}); - + InputMonitor inputMonitor(f.getPoll(), f.inputConfig, &cm); + inputMonitor.start(); int fd = ::open(TEST_INPUT_DEVICE.c_str(), O_WRONLY); BOOST_REQUIRE(fd >= 0); - for (unsigned int i = 0; i < noOfEventsToSend * f.inputConfig.numberOfEvents; ++i) { + for (int i = 0; i < f.inputConfig.numberOfEvents; ++i) { // button pressed event f.ie.value = EVENT_BUTTON_PRESSED; f.ie.time.tv_usec += 5; @@ -140,36 +168,52 @@ void sendNEvents(Fixture& f, unsigned int noOfEventsToSend) } BOOST_CHECK(::close(fd) >= 0); - BOOST_CHECK(eventLatch.waitForN(noOfEventsToSend, SINGLE_EVENT_TIMEOUT * noOfEventsToSend)); - - // Check if no more events are waiting - BOOST_CHECK(!eventLatch.wait(10)); } } // namespace -BOOST_AUTO_TEST_CASE(EventOneAtATime) +BOOST_AUTO_TEST_CASE(SingleEvent) { - sendNEvents(*this, 1); + ZonesManager cm(getPoll(), TEST_CONFIG_PATH); + cm.start(); + cm.createZone("zone1", SIMPLE_TEMPLATE); + cm.createZone("zone2", SIMPLE_TEMPLATE); + cm.createZone("zone3", SIMPLE_TEMPLATE); + cm.restoreAll(); + sendEvent(*this, cm); + BOOST_CHECK(spinWaitFor(EVENT_TIMEOUT, [&] { + return cm.getRunningForegroundZoneId() == "zone2"; + })); } -BOOST_AUTO_TEST_CASE(EventTenAtATime) +BOOST_AUTO_TEST_CASE(MultipleEvent) { - sendNEvents(*this, 10); + ZonesManager cm(getPoll(), TEST_CONFIG_PATH); + cm.start(); + cm.createZone("zone1", SIMPLE_TEMPLATE); + cm.createZone("zone2", SIMPLE_TEMPLATE); + cm.createZone("zone3", SIMPLE_TEMPLATE); + cm.restoreAll(); + for (int i = 1; i < 10; ++i) { + sendEvent(*this, cm); + std::string zoneId = "zone" + std::to_string(i % 3 + 1); + BOOST_CHECK(spinWaitFor(EVENT_TIMEOUT, [&] { + return cm.getRunningForegroundZoneId() == zoneId; + })); + } } namespace { -void sendNEventsWithPauses(Fixture& f, unsigned int noOfEventsToSend) +void sendEventWithPauses(Fixture& f, ZonesManager& cm) { - Latch eventLatch; - - InputMonitor inputMonitor(f.inputConfig, [&] {eventLatch.set();}); + InputMonitor inputMonitor(f.getPoll(), f.inputConfig, &cm); + inputMonitor.start(); int fd = ::open(TEST_INPUT_DEVICE.c_str(), O_WRONLY); BOOST_REQUIRE(fd >= 0); - for (unsigned int i = 0; i < noOfEventsToSend * f.inputConfig.numberOfEvents; ++i) { + for (int i = 0; i < f.inputConfig.numberOfEvents; ++i) { // Send first two bytes of the button pressed event f.ie.value = EVENT_BUTTON_PRESSED; f.ie.time.tv_usec += 5; @@ -191,22 +235,39 @@ void sendNEventsWithPauses(Fixture& f, unsigned int noOfEventsToSend) BOOST_CHECK(ret > 0); } BOOST_CHECK(::close(fd) >= 0); - BOOST_CHECK(eventLatch.waitForN(noOfEventsToSend, SINGLE_EVENT_TIMEOUT * noOfEventsToSend)); - - // Check if no more events are waiting - BOOST_CHECK(!eventLatch.wait(10)); } } // namespace -BOOST_AUTO_TEST_CASE(EventOneAtATimeWithPauses) +BOOST_AUTO_TEST_CASE(SingleEventWithPauses) { - sendNEventsWithPauses(*this, 1); + ZonesManager cm(getPoll(), TEST_CONFIG_PATH); + cm.start(); + cm.createZone("zone1", SIMPLE_TEMPLATE); + cm.createZone("zone2", SIMPLE_TEMPLATE); + cm.createZone("zone3", SIMPLE_TEMPLATE); + cm.restoreAll(); + sendEventWithPauses(*this, cm); + BOOST_CHECK(spinWaitFor(EVENT_TIMEOUT, [&] { + return cm.getRunningForegroundZoneId() == "zone2"; + })); } -BOOST_AUTO_TEST_CASE(EventTenAtATimeWithPauses) +BOOST_AUTO_TEST_CASE(MultipleEventWithPauses) { - sendNEventsWithPauses(*this, 10); + ZonesManager cm(getPoll(), TEST_CONFIG_PATH); + cm.start(); + cm.createZone("zone1", SIMPLE_TEMPLATE); + cm.createZone("zone2", SIMPLE_TEMPLATE); + cm.createZone("zone3", SIMPLE_TEMPLATE); + cm.restoreAll(); + for (int i = 1; i < 10; ++i) { + sendEventWithPauses(*this, cm); + std::string zoneId = "zone" + std::to_string(i % 3 + 1); + BOOST_CHECK(spinWaitFor(EVENT_TIMEOUT, [&] { + return cm.getRunningForegroundZoneId() == zoneId; + })); + } } -- 2.7.4