Implement waiting 62/273562/8
authorMateusz Majewski <m.majewski2@samsung.com>
Thu, 7 Apr 2022 14:51:45 +0000 (16:51 +0200)
committerMateusz Majewski <m.majewski2@samsung.com>
Mon, 11 Apr 2022 08:30:51 +0000 (10:30 +0200)
Change-Id: I166d08afff725ac0c5d22bd92f05b47f76f10e9c

sessiond/src/main.cpp

index 8d448784cba7633540814c5d923e7126fbefbc50..1cf4f80f55e29682ec09ac65c5235d938fc43bc1 100644 (file)
 #include <algorithm>
 #include <iostream>
 #include <memory>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
 
 #include <gio/gio.h>
 
 #include "tuple_g_variant_helpers.hpp"
 
+constexpr static std::string_view bus_name   = "org.tizen.sessiond";
+constexpr static std::string_view bus_object = "/org/tizen/sessiond";
+constexpr static std::string_view bus_iface  = "org.tizen.sessiond.subsession.Manager";
+
+constexpr static int timeout = 10;
+
 [[noreturn]] void g_error_throw(GError *err, std::string message) {
        message += err->message;
        g_error_free(err);
@@ -94,6 +103,171 @@ struct main_loop {
        GMainLoop *inner;
 };
 
+template<typename V>
+struct wait_manager {
+       wait_manager(int session_uid, GDBusConnection *connection, std::string_view completion_signal)
+                       : session_uid(session_uid), connection(connection), completion_signal(completion_signal) {}
+       ~wait_manager()
+       {
+               for (auto &registration : registered)
+                       g_bus_unwatch_name(registration.second);
+
+               // We ought to swallow exceptions in the destructor.
+               // (std::uncaught_exception exists, but there's no need for it IMHO.)
+               try {
+                       for (auto &waiting_for : waiting) {
+                               waiting_for.second.clear();
+                               finalize_if_empty(waiting_for.first);
+                       }
+               } catch (const std::exception &ex) {
+                       std::cerr << "Exception " << ex.what() << "\n" <<
+                               "while finalizing the wait manager\n";
+               }
+
+               for (auto &timeout : timeouts)
+                       g_source_remove(timeout->timeout_id);
+       }
+
+       // Because of the rule of three, we also have to delete these.
+       wait_manager(const wait_manager &) = delete;
+       wait_manager &operator=(const wait_manager &) = delete;
+
+       void on_client_register(const std::string &name)
+       {
+               if (registered.contains(name))
+                       return; // TODO: any error here?
+               auto match = g_bus_watch_name(G_BUS_TYPE_SYSTEM, name.data(), G_BUS_NAME_WATCHER_FLAGS_NONE,
+                               nullptr, glib_client_disappeared, this, nullptr);
+               registered.emplace(name, std::move(match));
+       }
+
+       void on_start(V v)
+       {
+               // See the comment in on_timeout.
+               timeouts.erase(std::remove_if(timeouts.begin(), timeouts.end(),
+                                             [](const auto &td) { return !td->active; }),
+                              timeouts.end());
+
+               // See the comment in finalize.
+               std::erase_if(waiting, [](const auto &waits) { return waits.second.empty(); });
+
+               if (waiting.contains(v))
+                       return; // TODO: any error here?
+
+               std::unordered_set<std::string> waits = {};
+               for (const auto &going_to_wait : registered)
+                       waits.emplace(going_to_wait.first);
+               if (waits.empty())
+                       signal(v);
+               else {
+                       auto td = std::make_unique<timeout_data>();
+                       td->self = this;
+                       td->v = v;
+                       td->active = true;
+                       td->timeout_id = g_timeout_add_seconds(timeout, glib_timeout, td.get());
+
+                       waiting.emplace(v, std::move(waits));
+                       timeouts.emplace_back(std::move(td));
+               }
+       }
+
+       void on_client_done(V v, const std::string &name)
+       {
+               if (!waiting.contains(v))
+                       return; // TODO: any error here?
+               drop_name(v, waiting[v], name);
+       }
+
+       void on_client_disappeared(const std::string &name)
+       {
+               registered.erase(name);
+               for (auto &waiting_for : waiting) {
+                       drop_name(waiting_for.first, waiting_for.second, name);
+               }
+       }
+
+       void drop_name(V v, std::unordered_set<std::string> &waits, const std::string &name)
+       {
+               waits.erase(name);
+               finalize_if_empty(v);
+       }
+
+       void on_timeout(V v)
+       {
+               if (!waiting.contains(v))
+                       return;
+               waiting[v].clear();
+
+               finalize_if_empty(v);
+
+               // One thing that we'd like to do here is removing the timeout from the array.
+               // However doing so is a bit sus, since glib_timeout still has pointer.
+               // What we do instead, is clearing the array up a bit when adding a new timeout in on_start.
+       }
+
+       void finalize_if_empty(V v)
+       {
+               // TODO: I don't like this, somehow.
+               // Refactor suggestions are more than welcome.
+               if (!waiting.contains(v) && !waiting[v].empty())
+                       return;
+
+               signal(v);
+
+               // Again, we'd like to remove the now empty vector.
+               // However, doing so is again sus, since other functions (for example on_client_disappeared)
+               // might actually iterate on waiting, and this invalidates the vector.
+               // Again, let's clean up in on_start instead.
+       }
+
+       void signal(V v)
+       {
+               GError *err = nullptr;
+               if (!g_dbus_connection_emit_signal(connection, nullptr, bus_object.data(), bus_iface.data(), completion_signal.data(),
+                               vals_to_g_variant(session_uid, v), &err))
+                       g_error_throw(err, "Failed to emit a signal: ");
+       }
+
+       static void glib_client_disappeared(GDBusConnection *conn, const gchar *name, gpointer user_data)
+       // As this is called directly by GLib, we need to swallow exceptions here.
+       try {
+               auto self = static_cast<wait_manager *>(user_data);
+               self->on_client_disappeared(std::string(name));
+       } catch (const std::exception &ex) {
+               std::cerr << "Exception " << ex.what() << "\n" <<
+                       "while handling " << name << " disappearing\n";
+       }
+
+       struct timeout_data {
+               guint timeout_id;
+               wait_manager *self;
+               V v;
+               bool active;
+       };
+       static gboolean glib_timeout(gpointer user_data)
+       // As this is called directly by GLib, we need to swallow exceptions here.
+       try {
+               auto data = static_cast<timeout_data *>(user_data);
+               data->self->on_timeout(data->v);
+               data->active = false;
+               return G_SOURCE_REMOVE;
+       } catch (const std::exception &ex) {
+               std::cerr << "Exception " << ex.what() << "\n" <<
+                       "while timeout\n";
+               return G_SOURCE_REMOVE;
+       }
+
+       int session_uid;
+       GDBusConnection *connection;
+       std::string_view completion_signal;
+
+       std::unordered_map<std::string, guint> registered = {};
+       std::unordered_map<V, std::unordered_set<std::string>> waiting = {};
+
+       // We hold all the timeout_data, because I couldn't get memory management to work otherwise.
+       std::vector<std::unique_ptr<timeout_data>> timeouts = {};
+};
+
 struct sessiond_context {
        sessiond_context() : data(xml), id(bus_name, glib_bus_connected, glib_name_lost, this), loop()
        {
@@ -132,6 +306,9 @@ struct sessiond_context {
                                vals_to_g_variant(session_uid, subsession_id), &err))
                        g_error_throw(err, "Failed to emit a signal: ");
 
+               wait_add.try_emplace(session_uid, session_uid, connection, "AddUserCompleted");
+               wait_add.at(session_uid).on_start(subsession_id);
+
                g_dbus_method_invocation_return_value(invocation, nullptr);
        }
 
@@ -149,6 +326,9 @@ struct sessiond_context {
                                vals_to_g_variant(session_uid, subsession_id), &err))
                        g_error_throw(err, "Failed to emit a signal: ");
 
+               wait_remove.try_emplace(session_uid, session_uid, connection, "RemoveUserCompleted");
+               wait_remove.at(session_uid).on_start(subsession_id);
+
                g_dbus_method_invocation_return_value(invocation, nullptr);
        }
 
@@ -168,6 +348,99 @@ struct sessiond_context {
                                vals_to_g_variant(session_uid, switch_id, prev_subsession_id, next_subsession_id), &err))
                        g_error_throw(err, "Failed to emit a signal: ");
 
+               wait_switch.try_emplace(session_uid, session_uid, connection, "SwitchUserCompleted");
+               wait_switch.at(session_uid).on_start(switch_id);
+
+               g_dbus_method_invocation_return_value(invocation, nullptr);
+       }
+
+       void on_add_user_wait(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
+       {
+               auto [ session_uid ] = tuple_from_g_variant<int>(parameters);
+
+               if (session_uid <= 0) {
+                       g_dbus_method_invocation_return_dbus_error(invocation, "org.freedesktop.DBus.Error.InvalidArgs", "Negative UID passed");
+                       return;
+               }
+
+               wait_add.try_emplace(session_uid, session_uid, connection, "AddUserCompleted");
+               wait_add.at(session_uid).on_client_register(std::string(sender));
+
+               g_dbus_method_invocation_return_value(invocation, nullptr);
+       }
+
+       void on_remove_user_wait(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
+       {
+               auto [ session_uid ] = tuple_from_g_variant<int>(parameters);
+
+               if (session_uid <= 0) {
+                       g_dbus_method_invocation_return_dbus_error(invocation, "org.freedesktop.DBus.Error.InvalidArgs", "Negative UID passed");
+                       return;
+               }
+
+               wait_remove.try_emplace(session_uid, session_uid, connection, "RemoveUserCompleted");
+               wait_remove.at(session_uid).on_client_register(std::string(sender));
+
+               g_dbus_method_invocation_return_value(invocation, nullptr);
+       }
+
+       void on_switch_user_wait(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
+       {
+               auto [ session_uid ] = tuple_from_g_variant<int>(parameters);
+
+               if (session_uid <= 0) {
+                       g_dbus_method_invocation_return_dbus_error(invocation, "org.freedesktop.DBus.Error.InvalidArgs", "Negative UID passed");
+                       return;
+               }
+
+               wait_switch.try_emplace(session_uid, session_uid, connection, "SwitchUserCompleted");
+               wait_switch.at(session_uid).on_client_register(std::string(sender));
+
+               g_dbus_method_invocation_return_value(invocation, nullptr);
+       }
+
+       void on_add_user_done(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
+       {
+               auto [ session_uid, subsession_id ] = tuple_from_g_variant<int, int>(parameters);
+
+               if (session_uid <= 0) {
+                       g_dbus_method_invocation_return_dbus_error(invocation, "org.freedesktop.DBus.Error.InvalidArgs", "Negative UID passed");
+                       return;
+               }
+
+               wait_add.try_emplace(session_uid, session_uid, connection, "AddUserCompleted");
+               wait_add.at(session_uid).on_client_done(subsession_id, std::string(sender));
+
+               g_dbus_method_invocation_return_value(invocation, nullptr);
+       }
+
+       void on_remove_user_done(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
+       {
+               auto [ session_uid, subsession_id ] = tuple_from_g_variant<int, int>(parameters);
+
+               if (session_uid <= 0) {
+                       g_dbus_method_invocation_return_dbus_error(invocation, "org.freedesktop.DBus.Error.InvalidArgs", "Negative UID passed");
+                       return;
+               }
+
+               wait_remove.try_emplace(session_uid, session_uid, connection, "RemoveUserCompleted");
+               wait_remove.at(session_uid).on_client_done(subsession_id, std::string(sender));
+
+               g_dbus_method_invocation_return_value(invocation, nullptr);
+       }
+
+       void on_switch_user_done(GDBusMethodInvocation *invocation, std::string_view sender, GVariant *parameters)
+       {
+               auto [ session_uid, switch_id ] = tuple_from_g_variant<int, uint64_t>(parameters);
+
+               if (session_uid <= 0) {
+                       g_dbus_method_invocation_return_dbus_error(invocation, "org.freedesktop.DBus.Error.InvalidArgs", "Negative UID passed");
+                       return;
+               }
+
+               wait_switch.try_emplace(session_uid, session_uid, connection, "SwitchUserCompleted");
+               wait_switch.at(session_uid).on_client_done(switch_id, std::string(sender));
+
                g_dbus_method_invocation_return_value(invocation, nullptr);
        }
 
@@ -223,6 +496,27 @@ struct sessiond_context {
                                        "<arg name=\"prev_subsession_id\" type=\"i\" direction=\"in\"/>"
                                        "<arg name=\"next_subsession_id\" type=\"i\" direction=\"in\"/>"
                                "</method>"
+                               "<method name=\"AddUserWait\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
+                               "</method>"
+                               "<method name=\"RemoveUserWait\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
+                               "</method>"
+                               "<method name=\"SwitchUserWait\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
+                               "</method>"
+                               "<method name=\"AddUserDone\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
+                                       "<arg name=\"subsession_id\"      type=\"i\" direction=\"in\"/>"
+                               "</method>"
+                               "<method name=\"RemoveUserDone\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
+                                       "<arg name=\"subsession_id\"      type=\"i\" direction=\"in\"/>"
+                               "</method>"
+                               "<method name=\"SwitchUserDone\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"in\"/>"
+                                       "<arg name=\"switch_id\"          type=\"x\" direction=\"in\"/>"
+                               "</method>"
                                "<signal name=\"AddUserStarted\">"
                                        "<arg name=\"session_uid\"        type=\"i\" direction=\"out\"/>"
                                        "<arg name=\"subsession_id\"      type=\"i\" direction=\"out\"/>"
@@ -237,23 +531,44 @@ struct sessiond_context {
                                        "<arg name=\"prev_subsession_id\" type=\"i\" direction=\"out\"/>"
                                        "<arg name=\"next_subsession_id\" type=\"i\" direction=\"out\"/>"
                                "</signal>"
+                               "<signal name=\"AddUserCompleted\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"out\"/>"
+                                       "<arg name=\"subsession_id\"      type=\"i\" direction=\"out\"/>"
+                               "</signal>"
+                               "<signal name=\"RemoveUserCompleted\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"out\"/>"
+                                       "<arg name=\"subsession_id\"      type=\"i\" direction=\"out\"/>"
+                               "</signal>"
+                               "<signal name=\"SwitchUserCompleted\">"
+                                       "<arg name=\"session_uid\"        type=\"i\" direction=\"out\"/>"
+                                       "<arg name=\"switch_id\"          type=\"x\" direction=\"out\"/>"
+                                       "<arg name=\"prev_subsession_id\" type=\"i\" direction=\"out\"/>"
+                                       "<arg name=\"next_subsession_id\" type=\"i\" direction=\"out\"/>"
+                               "</signal>"
                        "</interface>"
                "</node>";
-       constexpr static std::string_view bus_name = "org.tizen.sessiond";
-       constexpr static std::string_view bus_object = "/org/tizen/sessiond";
-       constexpr static std::string_view bus_iface = "org.tizen.sessiond.subsession.Manager";
 
        constexpr static std::array<
                        std::pair<
                                std::string_view,
                                void (sessiond_context::*)(GDBusMethodInvocation *, std::string_view, GVariant *)
-                       >, 3> methods = {
-               std::make_pair("AddUser",    &sessiond_context::on_add_user),
-               std::make_pair("RemoveUser", &sessiond_context::on_remove_user),
-               std::make_pair("SwitchUser", &sessiond_context::on_switch_user),
+                       >, 9> methods = {
+               std::make_pair("AddUser",        &sessiond_context::on_add_user),
+               std::make_pair("RemoveUser",     &sessiond_context::on_remove_user),
+               std::make_pair("SwitchUser",     &sessiond_context::on_switch_user),
+               std::make_pair("AddUserWait",    &sessiond_context::on_add_user_wait),
+               std::make_pair("RemoveUserWait", &sessiond_context::on_remove_user_wait),
+               std::make_pair("SwitchUserWait", &sessiond_context::on_switch_user_wait),
+               std::make_pair("AddUserDone",    &sessiond_context::on_add_user_done),
+               std::make_pair("RemoveUserDone", &sessiond_context::on_remove_user_done),
+               std::make_pair("SwitchUserDone", &sessiond_context::on_switch_user_done),
                // TODO: Add all the methods
        };
 
+       std::unordered_map<int, wait_manager<int>>      wait_add;
+       std::unordered_map<int, wait_manager<int>>      wait_remove;
+       std::unordered_map<int, wait_manager<uint64_t>> wait_switch;
+
        uint64_t switch_id = 0;
 
        introspection_data data;