[geary] Clean up network monitor handling.



commit 0b156120a76ece5c721f3755ebb3dfeb64406026
Author: Michael James Gratton <mike vee net>
Date:   Tue Sep 26 15:34:23 2017 +1000

    Clean up network monitor handling.
    
    Also works around Bug 788170 when running under flatpak.
    
    * src/engine/imap/transport/imap-client-session-manager.vala: Break out
      code that uses Glib.NetworkMonitor into a seperate util class, and hook
      that up.
    
    * src/engine/util/util-connectivity-manager.vala: New util class that
      coalases network change events, to ensure the engine is only notified
      once when an enpoint becomes reachable or unreachable.

 src/CMakeLists.txt                                 |    1 +
 .../transport/imap-client-session-manager.vala     |  112 +++++------------
 src/engine/util/util-connectivity-manager.vala     |  132 ++++++++++++++++++++
 3 files changed, 167 insertions(+), 78 deletions(-)
---
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 64c541e..1e4213f 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -291,6 +291,7 @@ engine/state/state-mapping.vala
 
 engine/util/util-ascii.vala
 engine/util/util-collection.vala
+engine/util/util-connectivity-manager.vala
 engine/util/util-converter.vala
 engine/util/util-files.vala
 engine/util/util-generic-capabilities.vala
diff --git a/src/engine/imap/transport/imap-client-session-manager.vala 
b/src/engine/imap/transport/imap-client-session-manager.vala
index 45b9338..6bba536 100644
--- a/src/engine/imap/transport/imap-client-session-manager.vala
+++ b/src/engine/imap/transport/imap-client-session-manager.vala
@@ -47,16 +47,16 @@ public class Geary.Imap.ClientSessionManager : BaseObject {
     
     /**
      * Indicates if the {@link Endpoint} the {@link ClientSessionManager} connects to is reachable,
-     * according to the NetworkMonitor.
+     * according to NetworkMonitor.
      *
-     * By default, this is true, optimistic the network is reachable.  It is updated even if the
+     * By default, this is false, pessimistic that the network is reachable.  It is updated even if the
      * {@link ClientSessionManager} is not open, maintained for the lifetime of the object.
      */
-    public bool is_endpoint_reachable { get; private set; default = true; }
-    
+    public bool is_endpoint_reachable { get; private set; default = false; }
+
     private AccountInformation account_information;
     private Endpoint endpoint;
-    private NetworkMonitor network_monitor;
+       private ConnectivityManager connectivity;
     private Gee.HashSet<ClientSession> sessions = new Gee.HashSet<ClientSession>();
     private int pending_sessions = 0;
     private Nonblocking.Mutex sessions_mutex = new Nonblocking.Mutex();
@@ -65,41 +65,36 @@ public class Geary.Imap.ClientSessionManager : BaseObject {
     private bool untrusted_host = false;
     private uint authorized_session_error_retry_timeout_id = 0;
     private int authorized_session_retry_sec = AUTHORIZED_SESSION_ERROR_MIN_RETRY_TIMEOUT_SEC;
-    private bool checking_reachable = false;
-    
+
     public signal void login_failed(StatusResponse? response);
-    
+
     public ClientSessionManager(AccountInformation account_information) {
         this.account_information = account_information;
+        this.account_information.notify["imap-credentials"].connect(on_imap_credentials_notified);
+
         // NOTE: This works because AccountInformation guarantees the IMAP endpoint not to change
         // for the lifetime of the AccountInformation object; if this ever changes, will need to
         // refactor for that
-        endpoint = account_information.get_imap_endpoint();
-        network_monitor = NetworkMonitor.get_default();
-        
-        account_information.notify["imap-credentials"].connect(on_imap_credentials_notified);
-        endpoint.untrusted_host.connect(on_imap_untrusted_host);
-        endpoint.notify[Endpoint.PROP_TRUST_UNTRUSTED_HOST].connect(on_imap_trust_untrusted_host);
-        
-        network_monitor.network_changed.connect(on_network_changed);
-        network_monitor.notify["network-available"].connect(on_network_available_changed);
-        
-        // get this started now
-        check_endpoint_reachable(null);
+        this.endpoint = account_information.get_imap_endpoint();
+        this.endpoint.notify[Endpoint.PROP_TRUST_UNTRUSTED_HOST].connect(on_imap_trust_untrusted_host);
+        this.endpoint.untrusted_host.connect(on_imap_untrusted_host);
+
+               this.connectivity = new ConnectivityManager(this.endpoint);
+               this.connectivity.notify["is-reachable"].connect(on_connectivity_change);
+               this.connectivity.check_reachable.begin();
     }
-    
+
     ~ClientSessionManager() {
         if (is_open)
             warning("Destroying opened ClientSessionManager");
-        
+
         account_information.notify["imap-credentials"].disconnect(on_imap_credentials_notified);
         endpoint.untrusted_host.disconnect(on_imap_untrusted_host);
         endpoint.notify[Endpoint.PROP_TRUST_UNTRUSTED_HOST].disconnect(on_imap_trust_untrusted_host);
-        
-        network_monitor.network_changed.disconnect(on_network_changed);
-        network_monitor.notify["network-available"].disconnect(on_network_available_changed);
+               this.connectivity.cancel_check();
+               this.connectivity = null;
     }
-    
+
     public async void open_async(Cancellable? cancellable) throws Error {
         if (is_open)
             throw new EngineError.ALREADY_OPEN("ClientSessionManager already open");
@@ -156,9 +151,9 @@ public class Geary.Imap.ClientSessionManager : BaseObject {
     // TODO: Need a more thorough and bulletproof system for maintaining a pool of ready
     // authorized sessions.
     private async void adjust_session_pool() {
-        if (!is_open)
+        if (!this.is_open)
             return;
-        
+
         int token;
         try {
             token = yield sessions_mutex.claim_async();
@@ -502,55 +497,17 @@ public class Geary.Imap.ClientSessionManager : BaseObject {
                 adjust_session_pool.begin();
         }
     }
-    
-    private void on_network_changed() {
-        // Always check if reachable because IMAP server could be on localhost.  (This is a Linux
-        // program, after all...)
-        check_endpoint_reachable(null);
-    }
-    
-    private void on_network_available_changed() {
-        // If network is available and endpoint is reachable, do nothing more, all is good,
-        // otherwise check (see note in on_network_changed)
-        if (network_monitor.network_available && is_endpoint_reachable)
-            return;
-        
-        check_endpoint_reachable(null);
-    }
-    
-    private void check_endpoint_reachable(Cancellable? cancellable) {
-        if (checking_reachable)
-            return;
-        
-        debug("Checking if IMAP host %s reachable...", endpoint.to_string());
-        
-        checking_reachable = true;
-        check_endpoint_reachable_async.begin(cancellable);
-    }
-    
-    // Use check_endpoint_reachable to properly schedule
-    private async void check_endpoint_reachable_async(Cancellable? cancellable) {
-        try {
-            is_endpoint_reachable = yield network_monitor.can_reach_async(endpoint.remote_address,
-                cancellable);
-            message("IMAP host %s considered %s", endpoint.to_string(),
-                is_endpoint_reachable ? "reachable" : "unreachable");
-        } catch (Error err) {
-            // If cancelled, don't change anything
-            if (err is IOError.CANCELLED)
-                return;
-            
-            message("Error determining if IMAP host %s is reachable, treating as unreachable: %s",
-                endpoint.to_string(), err.message);
-            is_endpoint_reachable = false;
-        } finally {
-            checking_reachable = false;
-        }
-        
-        if (is_endpoint_reachable)
-            adjust_session_pool.begin();
-    }
-    
+
+       private void on_connectivity_change() {
+               this.is_endpoint_reachable = this.connectivity.is_reachable;
+               debug("Host %s became %s",
+                         this.endpoint.to_string(),
+                         this.is_endpoint_reachable ? "reachable" : "unreachable");
+               if (this.is_endpoint_reachable) {
+            this.adjust_session_pool.begin();
+               }
+       }
+
     /**
      * Use only for debugging and logging.
      */
@@ -559,4 +516,3 @@ public class Geary.Imap.ClientSessionManager : BaseObject {
             sessions.size, reserved_sessions.size);
     }
 }
-
diff --git a/src/engine/util/util-connectivity-manager.vala b/src/engine/util/util-connectivity-manager.vala
new file mode 100644
index 0000000..22e8e50
--- /dev/null
+++ b/src/engine/util/util-connectivity-manager.vala
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2017 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+/**
+ * Keeps track of network connectivity changes for an endpoint.
+ *
+ * This class is a convenience API for the GIO NetworkMonitor. Since
+ * when connecting and disconnecting from a network, multiple
+ * network-changed signals may be sent, this class coalesces these as
+ * best as possible so the rest of the engine is only notified once
+ * when an endpoint becomes reachable, and once when it becomes
+ * unreachable.
+ *
+ * Note this class is not thread safe and should only be invoked from
+ * the main loop.
+ */
+public class Geary.ConnectivityManager : BaseObject {
+
+       /** Determines if the managed endpoint is currently reachable. */
+       public bool is_reachable { get; private set; default = false; }
+
+    private Endpoint endpoint;
+
+    private NetworkMonitor monitor;
+
+       private Cancellable? existing_check = null;
+
+
+    /**
+     * Constructs a new manager for a specific endpoint.
+     */
+    public ConnectivityManager(Endpoint endpoint) {
+               this.endpoint = endpoint;
+
+        this.monitor = NetworkMonitor.get_default();
+        this.monitor.network_changed.connect(on_network_changed);
+    }
+
+    ~ConnectivityManager() {
+        this.monitor.network_changed.disconnect(on_network_changed);
+    }
+
+       /**
+        * Starts checking if the manager's endpoint is reachable.
+        *
+        * This will cancel any existing check, and start a new one
+        * running, updating the `is_reachable` property on completion.
+        */
+    public async void check_reachable() {
+               // We use a cancellable here as a guard instead of a boolean
+               // "is_checking" var since when a series of checks are
+               // requested in quick succession (as is the case when
+               // e.g. connecting or disconnecting from a network), the
+               // result of the *last* check is authoritative, not the first
+               // one.
+               cancel_check();
+
+               Cancellable cancellable = new Cancellable();
+               this.existing_check = cancellable;
+
+               string endpoint = this.endpoint.to_string();
+               bool is_reachable = this.is_reachable;
+        try {
+                       debug("Checking if %s reachable...", endpoint);
+            is_reachable = yield this.monitor.can_reach_async(
+                               this.endpoint.remote_address,
+                               cancellable
+                       );
+        } catch (Error err) {
+            if (err is IOError.NETWORK_UNREACHABLE &&
+                               this.monitor.network_available) {
+                               // If we get a network unreachable error, but the monitor
+                               // says there actually is a network available, we may be
+                               // running in a Flatpak and hitting Bug 777706. If so,
+                               // just assume the service is reachable is for now. :(
+                               is_reachable = true;
+                               debug("Assuming %s is reachable, despite network unavailability",
+                                         endpoint);
+                       } else if (!(err is IOError.CANCELLED)) {
+                               // Service is unreachable
+                               debug("Error checking %s reachable, treating as unreachable: %s",
+                                               endpoint, err.message);
+                               is_reachable = false;
+                       }
+        } finally {
+                       if (!cancellable.is_cancelled()) {
+                               set_reachable(is_reachable);
+                       }
+            this.existing_check = null;
+        }
+    }
+
+       /**
+        * Cancels any running reachability check, if any.
+        */
+    public void cancel_check() {
+               if (this.existing_check != null) {
+                       this.existing_check.cancel();
+                       this.existing_check = null;
+               }
+       }
+
+    private void on_network_changed(bool some_available) {
+        // Always check if reachable because IMAP server could be on
+        // localhost.  (This is a Linux program, after all...)
+               debug("Network changed: %s",
+                         some_available ? "some available" : "none available");
+               if (some_available) {
+                       // Some hosts may have dropped out despite network being
+                       // still xavailable, so need to check again
+                       this.check_reachable.begin();
+               } else {
+                       // None available, so definitely not reachable
+                       set_reachable(false);
+               }
+    }
+
+       private inline void set_reachable(bool reachable) {
+               // Coalesce changes to is_reachable, since Vala <= 0.34 always
+               // fires notify signals on set, even if the value doesn't
+               // change. 0.36 fixes that, so pull this out when we can
+               // depend on that as a minimum.
+               if (this.is_reachable != reachable) {
+                       this.is_reachable = reachable;
+               }
+       }
+
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]