[geary/wip/actually-imap-logout: 5/5] Log out IMAP connections when stopping Imap.ClientService cleanly



commit 970f7b31d5d9939343abecd015126c9869b29429
Author: Michael Gratton <mike vee net>
Date:   Thu Feb 7 14:08:50 2019 +1100

    Log out IMAP connections when stopping Imap.ClientService cleanly
    
    When closing the a session cleanly (i.e. since the service is being
    stopped or a session is no longer needed, issue a logout instead of
    simply terminating the connection. Use a second cancellable for
    terminating logouts however when closing so we don't hang.

 src/engine/imap/api/imap-client-service.vala | 78 +++++++++++++++++++++-------
 1 file changed, 60 insertions(+), 18 deletions(-)
---
diff --git a/src/engine/imap/api/imap-client-service.vala b/src/engine/imap/api/imap-client-service.vala
index ff854815..0e46b089 100644
--- a/src/engine/imap/api/imap-client-service.vala
+++ b/src/engine/imap/api/imap-client-service.vala
@@ -89,7 +89,8 @@ internal class Geary.Imap.ClientService : Geary.ClientService {
     private Nonblocking.Queue<ClientSession> free_queue =
         new Nonblocking.Queue<ClientSession>.fifo();
 
-    private Cancellable? pool_cancellable = null;
+    private GLib.Cancellable? pool_cancellable = null;
+    private GLib.Cancellable? close_cancellable = null;
 
 
     public ClientService(AccountInformation account,
@@ -109,7 +110,8 @@ internal class Geary.Imap.ClientService : Geary.ClientService {
             );
         }
 
-        this.pool_cancellable = new Cancellable();
+        this.pool_cancellable = new GLib.Cancellable();
+        this.close_cancellable = new GLib.Cancellable();
         notify_started();
     }
 
@@ -125,7 +127,7 @@ internal class Geary.Imap.ClientService : Geary.ClientService {
         notify_stopped();
 
         this.pool_cancellable.cancel();
-        yield close_pool();
+        yield close_pool(true);
 
         // TODO: This isn't the best (deterministic) way to deal with
         // this, but it's easy and works for now
@@ -140,6 +142,12 @@ internal class Geary.Imap.ClientService : Geary.ClientService {
             if (++attempts > 12)
                 break;
         }
+
+        if (this.all_sessions.size > 0) {
+            debug("[%s] Cancelling remaining client sessions...",
+                  this.account.id);
+            this.close_cancellable.cancel();
+        }
     }
 
     /**
@@ -217,7 +225,7 @@ internal class Geary.Imap.ClientService : Geary.ClientService {
         );
 
         if (!this.is_running || this.discard_returned_sessions || too_many_free) {
-            yield force_disconnect(session);
+            yield disconnect_session(session);
         } else if (yield check_session(session, false)) {
             bool free = true;
             MailboxSpecifier? mailbox = null;
@@ -228,18 +236,30 @@ internal class Geary.Imap.ClientService : Geary.ClientService {
                 proto == ClientSession.ProtocolState.SELECTING) {
                 // always close mailbox to return to authorized state
                 try {
-                    yield session.close_mailbox_async(pool_cancellable);
+                    yield session.close_mailbox_async(this.close_cancellable);
                 } catch (ImapError imap_error) {
                     debug("[%s] Error attempting to close released session %s: %s",
                           this.account.id, session.to_string(), imap_error.message);
                     free = false;
                 }
 
-                if (session.get_protocol_state(null) !=
-                    ClientSession.ProtocolState.AUTHORIZED) {
-                    // Closing it didn't work, so drop it
-                    yield force_disconnect(session);
+                // Double check the session after closing it
+                switch (session.get_protocol_state(null)) {
+                case AUTHORIZED:
+                    // This is the desired state, so all good
+                    break;
+
+                case UNCONNECTED:
+                    // No longer connected, so just drop it
                     free = false;
+                    break;
+
+                default:
+                    // Closing it didn't leave it in the desired
+                    // state, so log out and drop it
+                    yield disconnect_session(session);
+                    free = false;
+                    break;
                 }
             }
 
@@ -258,7 +278,7 @@ internal class Geary.Imap.ClientService : Geary.ClientService {
 
     /** Closes the client session pool. */
     protected override void became_unreachable() {
-        this.close_pool.begin();
+        this.close_pool.begin(false);
     }
 
     private async void check_pool(bool is_claiming) {
@@ -303,7 +323,7 @@ internal class Geary.Imap.ClientService : Geary.ClientService {
 
         if (new_session == null) {
             // An error was thrown, so close the pool
-            this.close_pool.begin();
+            this.close_pool.begin(true);
         } else {
             try {
                 yield this.sessions_mutex.execute_locked(() => {
@@ -317,7 +337,7 @@ internal class Geary.Imap.ClientService : Geary.ClientService {
                       this.account.id, context.format_full_error());
                 notify_connection_failed(context);
                 new_session.disconnect_async.begin(null);
-                this.close_pool.begin();
+                this.close_pool.begin(true);
             }
         }
     }
@@ -334,7 +354,7 @@ internal class Geary.Imap.ClientService : Geary.ClientService {
         case ClientSession.ProtocolState.SELECTED:
         case ClientSession.ProtocolState.SELECTING:
             if (claiming) {
-                yield force_disconnect(target);
+                yield disconnect_session(target);
             } else {
                 valid = true;
             }
@@ -351,7 +371,7 @@ internal class Geary.Imap.ClientService : Geary.ClientService {
             break;
 
         default:
-            yield force_disconnect(target);
+            yield disconnect_session(target);
             break;
         }
 
@@ -372,7 +392,7 @@ internal class Geary.Imap.ClientService : Geary.ClientService {
             try {
                 debug("Sending NOOP when claiming a session");
                 yield target.send_command_async(
-                    new NoopCommand(), this.pool_cancellable
+                    new NoopCommand(), this.close_cancellable
                 );
             } catch (Error err) {
                 debug("Error sending NOOP: %s", err.message);
@@ -416,7 +436,7 @@ internal class Geary.Imap.ClientService : Geary.ClientService {
         return new_session;
     }
 
-    private async void close_pool() {
+    private async void close_pool(bool clean_disconnect) {
         debug("Closing the pool, disconnecting %d sessions",
               this.all_sessions.size);
 
@@ -436,11 +456,33 @@ internal class Geary.Imap.ClientService : Geary.ClientService {
         // waiting for any since we don't want to delay closing the
         // others.
         foreach (ClientSession session in to_close) {
-            session.disconnect_async.begin();
+            if (clean_disconnect) {
+                this.disconnect_session.begin(session);
+            } else {
+                this.force_disconnect_session.begin(session);
+            }
         }
     }
 
-    private async void force_disconnect(ClientSession session) {
+    private async void disconnect_session(ClientSession session) {
+        debug("[%s] Logging out session %s",
+              this.account.id, session.to_string());
+
+        // Log out before removing the session since close() only
+        // hangs around until all sessions have been removed before
+        // exiting.
+        try {
+            yield session.logout_async(this.close_cancellable);
+            yield remove_session_async(session);
+        } catch (GLib.Error err) {
+            debug("[%s] Error logging out of session: %s",
+                  this.account.id, err.message);
+            yield force_disconnect_session(session);
+        }
+
+    }
+
+    private async void force_disconnect_session(ClientSession session) {
         debug("[%s] Dropping session %s", this.account.id, session.to_string());
 
         try {


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