← Back to team overview

elementaryart team mailing list archive

[Merge] lp:~victored/cerbere/session-manager-client into lp:cerbere

 

Victor Eduardo has proposed merging lp:~victored/cerbere/session-manager-client into lp:cerbere.

Requested reviews:
  elementary Pantheon team (elementary-pantheon)
Related bugs:
  Bug #1007977 in Cerbere: "Lots of stuff takes forever to start because Cerbere doesn't register in session manager on launch"
  https://bugs.launchpad.net/cerbere/+bug/1007977

For more details, see:
https://code.launchpad.net/~victored/cerbere/session-manager-client/+merge/113806

Say goodbye to the long desktop startup times ...
-- 
https://code.launchpad.net/~victored/cerbere/session-manager-client/+merge/113806
Your team elementaryart (old) is subscribed to branch lp:cerbere.
=== modified file 'src/CMakeLists.txt'
--- src/CMakeLists.txt	2012-06-14 22:29:47 +0000
+++ src/CMakeLists.txt	2012-07-06 21:51:19 +0000
@@ -16,6 +16,7 @@
   VALA_C
     Cerbere.vala
     SettingsManager.vala
+    SessionManager.vala
     Watchdog.vala
     ProcessInfo.vala
   PACKAGES
@@ -26,6 +27,7 @@
     --target-glib=2.32 # Remember to keep this updated.
 )
 
-add_executable(cerbere ${VALA_C})
-target_link_libraries(cerbere ${DEPS_LIBRARIES})
-install(TARGETS cerbere RUNTIME DESTINATION bin)
+set(EXEC_NAME ${CMAKE_PROJECT_NAME})
+add_executable(${EXEC_NAME} ${VALA_C})
+target_link_libraries(${EXEC_NAME} ${DEPS_LIBRARIES})
+install(TARGETS ${EXEC_NAME} RUNTIME DESTINATION bin)

=== modified file 'src/Cerbere.vala'
--- src/Cerbere.vala	2012-06-14 22:28:44 +0000
+++ src/Cerbere.vala	2012-07-06 21:51:19 +0000
@@ -28,17 +28,28 @@
 public class Cerbere : Application {
 
     public static SettingsManager settings { get; private set; default = null; }
-    private Watchdog watchdog;
+    private Watchdog? watchdog = null;
+    private SessionManager.ClientService? sm_client = null;
 
     construct {
         application_id = "org.pantheon.cerbere";
         flags = ApplicationFlags.IS_SERVICE;
+        Log.set_handler (null, LogLevelFlags.LEVEL_MASK, log_handler);
+    }
+
+    private static void log_handler (string? domain, LogLevelFlags level, string message) {
+        if (level >= LogLevelFlags.LEVEL_INFO)
+            level = LogLevelFlags.LEVEL_MESSAGE;
+        Log.default_handler (domain, level, message);
     }
 
     protected override void startup () {
+        // Try to register Cerbere with the session manager.
+        // This is a non-blocking action.
+        register_session_client_async ();
+
         this.settings = new SettingsManager ();
-
-        this.start_processes (this.settings.process_list);
+        start_processes (this.settings.process_list);
 
         // Monitor changes to the process list
         this.settings.process_list_changed.connect (this.start_processes);
@@ -47,6 +58,27 @@
         main_loop.run ();
     }
 
+    private async void register_session_client_async () {
+        if (this.sm_client != null)
+            return;
+
+        this.sm_client = new SessionManager.ClientService (this.application_id);
+
+        try {
+            this.sm_client.register ();
+        } catch (SessionManager.ConnectionError e) {
+            critical (e.message);
+        }
+
+        if (this.sm_client != null) {
+            // The session manager may ask us to quit the service, and so we do.
+            this.sm_client.stop_service.connect ( () => {
+                message ("Exiting...");
+                this.quit_mainloop ();
+            });
+        }
+    }
+
     private void start_processes (string[] process_list) {
         if (this.watchdog == null) {
             this.watchdog = new Watchdog ();

=== modified file 'src/ProcessInfo.vala'
--- src/ProcessInfo.vala	2012-06-17 16:22:40 +0000
+++ src/ProcessInfo.vala	2012-07-06 21:51:19 +0000
@@ -43,7 +43,7 @@
 
     public void reset_crash_count () {
         this.crash_count = 0;
-        debug ("Crash count of '%s' has been reset", this.command);
+        message ("Crash count of '%s' has been reset", this.command);
     }
 
     public async void run_async () {
@@ -51,10 +51,10 @@
     }
 
     private void run () {
-        message ("STARTING process: %s", command);
+        debug ("STARTING process: %s", command);
 
         if (this.status == Status.RUNNING) {
-            message ("Process %s is already running", command);
+            warning ("Process %s is already running. Not starting it again...", command);
             return;
         }
 
@@ -102,7 +102,7 @@
         if (pid != this.pid)
             return;
 
-        message ("Process '%s' exited", command);
+        message ("Process '%s' watch exit", command);
 
         // Check exit status
         if (Process.if_exited (status) || Process.if_signaled (status) || Process.core_dump (status)) {
@@ -126,13 +126,12 @@
             double elapsed_secs = this.timer.elapsed ();
             double crash_time_interval_secs = (double)Cerbere.settings.crash_time_interval / 1000.0;
 
-            debug ("Elapsed time = %f secs", elapsed_secs);
-            debug ("Min allowed time = %f secs", crash_time_interval_secs);
+            message ("ET = %f secs\tMin allowed time = %f", elapsed_secs, crash_time_interval_secs);
 
             if (elapsed_secs <= crash_time_interval_secs) { // process crashed
                 this.crash_count ++;
                 normal_exit = false;
-                message ("PROCESS '%s' CRASHED (#%u)", this.command, this.crash_count);
+                warning ("PROCESS '%s' CRASHED (#%u)", this.command, this.crash_count);
             }
 
             // Remove the current timer

=== added file 'src/SessionManager.vala'
--- src/SessionManager.vala	1970-01-01 00:00:00 +0000
+++ src/SessionManager.vala	2012-07-06 21:51:19 +0000
@@ -0,0 +1,205 @@
+/* -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- */
+/*
+ * Copyright (C) 2012 Victor Eduardo <victoreduardm@xxxxxxxxx>
+ *
+ * Cerbere is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Cerbere is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Cerbere; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA  02110-1301  USA
+ *
+ * Authors: Victor Eduardo <victoreduardm@xxxxxxxxx>
+ */
+
+namespace SessionManager {
+
+    public errordomain ConnectionError {
+        CONNECTION_FAILED,
+        CLIENT_REGISTRATION_FAILED
+    }
+
+
+    /**
+     * GNOME Session Manager DBus API
+     *
+     * API Reference: [[http://www.gnome.org/~mccann/gnome-session/docs/gnome-session.html]]
+     * (Consulted on July 4, 2012.)
+     */
+
+    private const string DBUS_NAME = "org.gnome.SessionManager";
+    private const string DBUS_PATH = "/org/gnome/SessionManager";
+
+    [DBus (name = "org.gnome.SessionManager")]
+    private interface SessionManager : Object {
+        // Many API methods have been left out. Feel free to add them when required.
+        public abstract void RegisterClient (string app_id, string client_startup_id,
+                                             out string client_id) throws IOError;
+        public abstract void UnregisterClient (ObjectPath client_id) throws IOError;
+    }
+
+    [DBus (name = "org.gnome.SessionManager.ClientPrivate")]
+    private interface ClientPrivate : Object {
+        public abstract void EndSessionResponse (bool is_ok, string reason) throws IOError;
+        public signal void QueryEndSession (uint flags);
+        public signal void EndSession (uint flags);
+        public signal void CancelEndSession ();
+        public signal void Stop ();
+    }
+
+
+    /**
+     * CLIENT
+     *
+     * This class handles both the registration of the service,
+     * and action requests coming from the session-manager side.
+     */
+
+    public class ClientService : Object {
+        public signal void stop_service ();
+
+        private SessionManager? session_manager = null;
+        private ClientPrivate? client = null;
+        private string? client_id = null;
+        private string? app_id = null;
+
+        public ClientService (string app_id) {
+            this.app_id = app_id;
+        }
+
+        public void register () throws ConnectionError {
+            bool connected = true;
+
+            if (session_manager == null) {
+                connected = connect_session ();
+
+                if (!connected) {
+                    throw new ConnectionError.CONNECTION_FAILED ("Could not connect to session manager");
+                }
+            }
+
+            connected = register_client ();
+
+            if (!connected) {
+                // Disconnect from SM
+                session_manager = null;
+                throw new ConnectionError.CLIENT_REGISTRATION_FAILED ("Unable to register client with session manager");
+            }
+
+        }
+
+        public void unregister () {
+            return_if_fail (session_manager != null && client_id != null);
+
+            debug ("Unregistering client: %s", client_id);
+
+            try {
+                session_manager.UnregisterClient (new ObjectPath (client_id));
+            } catch (IOError e) {
+                critical (e.message);
+            }
+        }
+
+        private bool connect_session () {
+            if (session_manager == null) {
+                try {
+                    session_manager = Bus.get_proxy_sync (BusType.SESSION, DBUS_NAME, DBUS_PATH);
+                } catch (IOError e) {
+                    critical (e.message);
+                }
+            }
+
+            return session_manager != null;
+        }
+
+        private bool register_client () {
+            return_val_if_fail (session_manager != null && app_id != null, false);
+
+            string? startup_id = Environment.get_variable ("DESKTOP_AUTOSTART_ID");
+
+            if (startup_id == null) {
+                critical ("Could not get value of environment variable DESKTOP_AUTOSTART_ID");
+                return false;
+            }
+
+            // Register client
+            if (client == null) {
+                try {
+                    session_manager.RegisterClient (app_id, startup_id, out client_id);
+                } catch (IOError e) {
+                    critical ("Could not register client: %s", e.message);
+                }
+
+                return_val_if_fail (client_id != null, false);
+
+                debug ("Registered session manager client: %s", client_id);
+
+                // Get client
+                try {
+                    client = Bus.get_proxy_sync (BusType.SESSION, DBUS_NAME, client_id);
+                } catch (IOError e) {
+                    critical ("Could not get client: %s", e.message);
+                    return_val_if_reached (false);
+                }
+
+                debug ("Obtained gnome-session client proxy");
+
+                // Connect signals
+                client.QueryEndSession.connect (on_client_query_end_session);
+                client.EndSession.connect (on_client_end_session);
+                client.CancelEndSession.connect (on_client_cancel_end_session);
+                client.Stop.connect (on_client_stop);
+            }
+
+            return client != null;
+        }
+
+
+        /** ClientPrivate Signal handlers **/
+
+        private void on_client_query_end_session (uint flags) {
+            debug ("Client query end session");
+            return_if_fail (client != null);
+
+            send_end_session_response (true);
+        }
+
+        private void on_client_end_session (uint flags) {
+            debug ("Client end session");
+            return_if_fail (client != null);
+
+            send_end_session_response (true);
+            on_client_stop ();
+        }
+
+        private void on_client_cancel_end_session () {
+            debug ("Client: Received EndSessionCanceled signal");
+        }
+
+        private void on_client_stop () {
+            debug ("Client: Received Stop signal");
+            this.unregister ();
+            this.stop_service ();
+        }
+
+        private inline void send_end_session_response (bool is_okay, string reason = "")  {
+            return_if_fail (client != null);
+
+            // Tell the session manager whether it's okay to logout, shut down, etc.
+            try {
+                debug ("Sending is_okay = %s to session manager", is_okay.to_string ());
+                client.EndSessionResponse (true, "");
+            } catch (IOError e) {
+                warning ("Couldn't reply to session manager: %s", e.message);
+            }
+        }
+    }
+}

=== modified file 'src/SettingsManager.vala'
--- src/SettingsManager.vala	2012-06-14 22:28:44 +0000
+++ src/SettingsManager.vala	2012-07-06 21:51:19 +0000
@@ -30,7 +30,7 @@
     static const string CRASH_TIME_INTERVAL_KEY = "crash-time-interval";
     static const string MONITORED_PROCESSES_KEY = "monitored-processes";
 
-    public string[] process_list   { get; set; }
+    public string[] process_list    { get; set; }
     public uint max_crashes         { get; set; default = 0; }
     public uint crash_time_interval { get; set; default = 0; }
 
@@ -47,6 +47,6 @@
     }
 
     private void on_process_list_changed () {
-        this.process_list_changed (this.settings.get_strv (MONITORED_PROCESSES_KEY));
+        this.process_list_changed (this.process_list);
     }
 }

=== modified file 'src/Watchdog.vala'
--- src/Watchdog.vala	2012-06-17 04:31:37 +0000
+++ src/Watchdog.vala	2012-07-06 21:51:19 +0000
@@ -25,10 +25,10 @@
  *          Victor Eduardo <victoreduardm@xxxxxxxxx>
  */
 
-public class Watchdog {
+public class Watchdog : Object {
+
     // Contains ALL the processes that are being monitored
     private Gee.HashMap<string, ProcessInfo> processes;
-    private Mutex data_lock;
 
     public Watchdog () {
         this.processes = new Gee.HashMap<string, ProcessInfo> ();
@@ -43,16 +43,17 @@
             return;
 
         // Check if a process for this command has already been created
-        if (this.processes.has_key (command))
+        if (this.processes.has_key (command)) {
             return;
+        }
 
         // Create new process
         var process = new ProcessInfo (command);
 
         // Add it to the table
-        this.data_lock.lock ();
-        this.processes[command] = process;
-        this.data_lock.unlock ();
+        lock (this.processes) {
+            this.processes[command] = process;
+        }
 
         // Exit handler. Respawning occurs here
         process.exited.connect ( (normal_exit) => {
@@ -62,6 +63,8 @@
                 process.reset_crash_count ();
             }
 
+            bool remove_process = false;
+
             // if still in the list, relaunch if possible
             if (command in Cerbere.settings.process_list) {
                 // Check if the process is still present in the table since it could have been removed.
@@ -73,25 +76,28 @@
                         process.run_async (); // Reload right away
                     }
                     else {
-                        message ("'%s' exceeded the maximum number of crashes allowed (%s). It won't be launched again",
-                                 command, max_crashes.to_string ());
+                        warning ("'%s' exceeded the maximum number of crashes allowed (%s). It won't be launched again", command, max_crashes.to_string ());
+                        remove_process = true;
                     }
                 }
                 else {
-                    // If a process is not in the table, it means it wasn't re-launched after it exited, so
-                    // in theory this code is never reached.
-                    warning ("You should NEVER get this message. If you're getting it, file a bug!");
+                    // If a process is not in the table, it means it wasn't re-launched
+                    // after it exited, so in theory this code is never reached.
+                    critical ("Please file a bug at http://launchpad.net/cerbere and attach your ~/.xsession-errors file.");
                 }
             }
             else {
-                message ("'%s' is no longer on settings. It will not be monitored anymore", command);
-
+                warning ("'%s' is no longer in settings (not monitored))", command);
                 process.reset_crash_count (); // reset
+                remove_process = true;
+            }
 
-                // Remove from the list. At this point the reference count should drop to 0 and free the process.
-                this.data_lock.lock ();
-                this.processes.unset (command);
-                this.data_lock.unlock ();
+            // Remove from the table. At this point the reference count should
+            // drop to 0 and free the process info.
+            if (remove_process) {
+                lock (this.processes) {
+                    this.processes.unset (command);
+                }
             }
         });
 


Follow ups