cynara: rewrite class using cynara async API for parallel processing 51/38351/6
authorRafal Krypa <r.krypa@samsung.com>
Fri, 17 Apr 2015 09:17:02 +0000 (11:17 +0200)
committerRafal Krypa <r.krypa@samsung.com>
Fri, 17 Apr 2015 14:17:28 +0000 (16:17 +0200)
Cynara class method check() can now be called in parallel by multiple
threads. Each call blocks until it gets a response.

This is a first step toward making security-manager multi-threaded, for
processing multiple requests in parallel.

Cynara class remains a singleton for now, but eventually there will be
single instance constructed (and destructed) from the main thread and
called for checks from separate threads processing user requests.

Change-Id: Ie1f55b9610caf45dc0df06dbd713070d39ccac07
Signed-off-by: Rafal Krypa <r.krypa@samsung.com>
packaging/security-manager.spec
src/common/CMakeLists.txt
src/common/cynara.cpp
src/common/include/cynara.h

index d6d259b..8baa231 100644 (file)
@@ -20,7 +20,7 @@ BuildRequires: pkgconfig(libtzplatform-config)
 BuildRequires: pkgconfig(sqlite3)
 BuildRequires: pkgconfig(db-util)
 BuildRequires: pkgconfig(cynara-admin)
-BuildRequires: pkgconfig(cynara-client)
+BuildRequires: pkgconfig(cynara-client-async)
 BuildRequires: boost-devel
 %{?systemd_requires}
 
index 6571e64..32b3f77 100644 (file)
@@ -7,7 +7,7 @@ PKG_CHECK_MODULES(COMMON_DEP
     libsmack
     db-util
     cynara-admin
-    cynara-client
+    cynara-client-async
     )
 
 FIND_PACKAGE(Boost REQUIRED)
index 14acb36..30f0777 100644 (file)
@@ -544,14 +544,39 @@ int CynaraAdmin::GetPrivilegeManagerMaxLevel(const std::string &label, const std
 
 Cynara::Cynara()
 {
+    int ret;
+
+    ret = eventfd(0, 0);
+    if (ret == -1) {
+        LogError("Error while creating eventfd: " << strerror(errno));
+        ThrowMsg(CynaraException::UnknownError, "Error while creating eventfd");
+    }
+
+    // Poll the eventfd for reading
+    pollFds[0].fd = ret;
+    pollFds[0].events = POLLIN;
+
+    // Temporary, will be replaced by cynara fd when available
+    pollFds[1].fd = pollFds[0].fd;
+    pollFds[1].events = 0;
+
     checkCynaraError(
-        cynara_initialize(&m_Cynara, nullptr),
+        cynara_async_initialize(&cynara, nullptr, &Cynara::statusCallback, &(pollFds[1])),
         "Cannot connect to Cynara policy interface.");
+
+    thread = std::thread(&Cynara::run, this);
 }
 
 Cynara::~Cynara()
 {
-    cynara_finish(m_Cynara);
+    LogDebug("Sending terminate event to Cynara thread");
+    terminate.store(true);
+    threadNotifyPut();
+    thread.join();
+
+    // Critical section
+    std::lock_guard<std::mutex> guard(mutex);
+    cynara_async_finish(cynara);
 }
 
 Cynara &Cynara::getInstance()
@@ -560,13 +585,150 @@ Cynara &Cynara::getInstance()
     return cynara;
 }
 
+void Cynara::threadNotifyPut()
+{
+    int ret = eventfd_write(pollFds[0].fd, 1);
+    if (ret == -1)
+        LogError("Unexpected error while writing to eventfd: " << strerror(errno));
+}
+
+void Cynara::threadNotifyGet()
+{
+    eventfd_t value;
+    int ret = eventfd_read(pollFds[0].fd, &value);
+    if (ret == -1)
+        LogError("Unexpected error while reading from eventfd: " << strerror(errno));
+}
+
+void Cynara::statusCallback(int oldFd, int newFd, cynara_async_status status,
+    void *ptr)
+{
+    auto cynaraFd = static_cast<struct pollfd *>(ptr);
+
+    LogDebug("Cynara status callback. " <<
+        "Status = " << status << ", oldFd = " << oldFd << ", newFd = " << newFd);
+
+    if (newFd == -1) {
+        cynaraFd->events = 0;
+    } else {
+        cynaraFd->fd = newFd;
+
+        switch (status) {
+        case CYNARA_STATUS_FOR_READ:
+            cynaraFd->events = POLLIN;
+            break;
+
+        case CYNARA_STATUS_FOR_RW:
+            cynaraFd->events = POLLIN | POLLOUT;
+            break;
+        }
+    }
+}
+
+void Cynara::responseCallback(cynara_check_id checkId,
+    cynara_async_call_cause cause, int response, void *ptr)
+{
+    LogDebug("Response for received for Cynara check id: " << checkId);
+
+    auto promise = static_cast<std::promise<bool>*>(ptr);
+
+    switch (cause) {
+    case CYNARA_CALL_CAUSE_ANSWER:
+        LogDebug("Cynara cause: ANSWER: " << response);
+        promise->set_value(response);
+        break;
+
+    case CYNARA_CALL_CAUSE_CANCEL:
+        LogDebug("Cynara cause: CANCEL");
+        promise->set_value(CYNARA_API_ACCESS_DENIED);
+        break;
+
+    case CYNARA_CALL_CAUSE_FINISH:
+        LogDebug("Cynara cause: FINISH");
+        promise->set_value(CYNARA_API_ACCESS_DENIED);
+        break;
+
+    case CYNARA_CALL_CAUSE_SERVICE_NOT_AVAILABLE:
+        LogError("Cynara cause: SERVICE_NOT_AVAILABLE");
+
+        try {
+            ThrowMsg(CynaraException::ServiceNotAvailable,
+                "Cynara service not available");
+        } catch (...) {
+            promise->set_exception(std::current_exception());
+        }
+        break;
+    }
+}
+
+void Cynara::run()
+{
+    LogInfo("Cynara thread started");
+    while (true) {
+        int ret = poll(pollFds, 2, -1);
+        if (ret == -1) {
+            if (errno != EINTR)
+                LogError("Unexpected error returned by poll: " << strerror(errno));
+            continue;
+        }
+
+        // Check eventfd for termination signal
+        if (pollFds[0].revents) {
+            threadNotifyGet();
+            if (terminate.load()) {
+                LogInfo("Cynara thread terminated");
+                return;
+            }
+        }
+
+        // Check if Cynara fd is ready for processing
+        try {
+            if (pollFds[1].revents) {
+                // Critical section
+                std::lock_guard<std::mutex> guard(mutex);
+
+                checkCynaraError(cynara_async_process(cynara),
+                    "Unexpected error returned by cynara_async_process");
+            }
+        } catch (const CynaraException::Base &e) {
+            LogError("Error while processing Cynara events: " << e.DumpToString());
+        }
+    }
+}
+
 bool Cynara::check(const std::string &label, const std::string &privilege,
         const std::string &user, const std::string &session)
 {
-    return checkCynaraError(
-        cynara_check(m_Cynara,
-            label.c_str(), session.c_str(), user.c_str(), privilege.c_str()),
-        "Cannot check permission with Cynara.");
+    LogDebug("check: client = " << label << ", user = " << user <<
+        ", privilege = " << privilege << ", session = " << session);
+
+    std::promise<bool> promise;
+    auto future = promise.get_future();
+
+    // Critical section
+    {
+        std::lock_guard<std::mutex> guard(mutex);
+
+        int ret = cynara_async_check_cache(cynara,
+            label.c_str(), session.c_str(), user.c_str(), privilege.c_str());
+
+        if (ret != CYNARA_API_CACHE_MISS)
+            return checkCynaraError(ret, "Error while checking Cynara cache");
+
+        LogDebug("Cynara cache miss");
+
+        cynara_check_id check_id;
+        checkCynaraError(
+            cynara_async_create_request(cynara,
+                label.c_str(), session.c_str(), user.c_str(), privilege.c_str(),
+                &check_id, &Cynara::responseCallback, &promise),
+            "Cannot check permission with Cynara.");
+
+        threadNotifyPut();
+        LogDebug("Waiting for response to Cynara query id " << check_id);
+    }
+
+    return future.get();
 }
 
 } // namespace SecurityManager
index 3e9a818..aa0ed60 100644 (file)
 #ifndef _SECURITY_MANAGER_CYNARA_
 #define _SECURITY_MANAGER_CYNARA_
 
-#include <cynara-client.h>
+#include <cynara-client-async.h>
 #include <cynara-admin.h>
 #include <dpl/exception.h>
 #include <string>
 #include <vector>
 #include <map>
+#include <mutex>
+#include <thread>
+#include <future>
+
+#include <poll.h>
+#include <sys/eventfd.h>
 
 #include "security-manager.h"
 
@@ -293,7 +299,7 @@ private:
 class Cynara
 {
 public:
-    virtual ~Cynara();
+    ~Cynara();
 
     static Cynara &getInstance();
 
@@ -311,9 +317,24 @@ public:
 
 private:
     Cynara();
-    struct cynara *m_Cynara;
-};
 
+    static void statusCallback(int oldFd, int newFd,
+        cynara_async_status status, void *ptr);
+
+    static void responseCallback(cynara_check_id checkId,
+        cynara_async_call_cause cause, int response, void *ptr);
+
+    void run();
+
+    void threadNotifyPut();
+    void threadNotifyGet();
+
+    cynara_async *cynara;
+    struct pollfd pollFds[2];
+    std::mutex mutex;
+    std::thread thread;
+    std::atomic<bool> terminate{false};
+};
 
 } // namespace SecurityManager