[geary/wip/remote-retry: 3/3] Much closer: retries are consistent and don't hang



commit 4a9254e30d5f691265bd5cd5253a5a293a9edb63
Author: Jim Nelson <jim yorba org>
Date:   Tue Jan 20 18:37:51 2015 -0800

    Much closer: retries are consistent and don't hang
    
    However, if the folder is closed in an orderly fashion, the queue is
    flushed and operations are dropped, so not quite there yet.

 .../imap-engine/imap-engine-minimal-folder.vala    |   54 +++++++++++---------
 .../imap-engine/imap-engine-replay-operation.vala  |   22 ++++++---
 .../imap-engine/imap-engine-replay-queue.vala      |   32 ++++++------
 src/engine/imap-engine/imap-engine.vala            |    4 ++
 .../replay-ops/imap-engine-create-email.vala       |   21 ++++++--
 .../replay-ops/imap-engine-move-email.vala         |   25 +++++++---
 .../replay-ops/imap-engine-replay-disconnect.vala  |    9 +++-
 .../nonblocking-abstract-semaphore.vala            |   12 ----
 src/engine/nonblocking/nonblocking-mailbox.vala    |    3 +-
 .../nonblocking-reporting-semaphore.vala           |    4 +-
 10 files changed, 109 insertions(+), 77 deletions(-)
---
diff --git a/src/engine/imap-engine/imap-engine-minimal-folder.vala 
b/src/engine/imap-engine/imap-engine-minimal-folder.vala
index 98a657b..b7b5ac7 100644
--- a/src/engine/imap-engine/imap-engine-minimal-folder.vala
+++ b/src/engine/imap-engine/imap-engine-minimal-folder.vala
@@ -7,8 +7,8 @@
 private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport.Copy,
     Geary.FolderSupport.Mark, Geary.FolderSupport.Move {
     private const int FORCE_OPEN_REMOTE_TIMEOUT_SEC = 10;
-    private const int DEFAULT_REESTABLISH_DELAY_MSEC = 100;
-    private const int MAX_REESTABLISH_DELAY_MSEC = 30000;
+    private const int DEFAULT_REESTABLISH_DELAY_MSEC = 500;
+    private const int MAX_REESTABLISH_DELAY_MSEC = 60 * 1000;
     
     public override Account account { get { return _account; } }
     
@@ -44,7 +44,7 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
     private int open_count = 0;
     private bool remote_opened = false;
     private Nonblocking.ReportingSemaphore<bool>? remote_semaphore = null;
-    private ReplayQueue? replay_queue = null;
+    private ReplayQueue replay_queue;
     private int remote_count = -1;
     private uint open_remote_timer_id = 0;
     private int reestablish_delay_msec = DEFAULT_REESTABLISH_DELAY_MSEC;
@@ -59,6 +59,7 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
         this.local_folder = local_folder;
         _special_folder_type = special_folder_type;
         _properties.add(local_folder.get_properties());
+        replay_queue = new ReplayQueue(this);
         
         email_flag_watcher = new EmailFlagWatcher(this);
         email_flag_watcher.email_flags_changed.connect(on_email_flags_changed);
@@ -208,11 +209,6 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
         assert(local_earliest_id.has_uid());
         assert(local_latest_id.has_uid());
         
-        //
-        // TODO: Flush retry commands on the ReplayQueue (but not pending commands, as they may
-        // have come in naturally while waiting for open to complete)
-        //
-        
         // if any messages are still marked for removal from last time, that means the EXPUNGE
         // never arrived from the server, in which case the folder is "dirty" and needs a full
         // normalization
@@ -526,9 +522,6 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
         
         remote_semaphore = new Geary.Nonblocking.ReportingSemaphore<bool>(false);
         
-        // start the replay queue
-        replay_queue = new ReplayQueue(this);
-        
         // Unless NO_DELAY is set, do NOT open the remote side here; wait for the ReplayQueue to
         // require a remote connection or wait_for_open_async() to be called ... this allows for
         // fast local-only operations to occur, local-only either because (a) the folder has all
@@ -752,17 +745,31 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
         if (remote_folder != null)
             _properties.remove(remote_folder.properties);
         
-        yield close_internal_async(CloseReason.LOCAL_CLOSE, CloseReason.REMOTE_CLOSE, true,
-            cancellable);
+        // block anyone from wait_until_open_async(), as this is no longer open
+        remote_semaphore.reset();
+        
+        ReplayDisconnect disconnect_op = new ReplayDisconnect(this,
+            Imap.ClientSession.DisconnectReason.REMOTE_CLOSE, true, cancellable);
+        replay_queue.schedule(disconnect_op);
+        
+        yield disconnect_op.wait_for_ready_async(cancellable);
     }
     
     // Close the remote connection and, if open_count is zero, the Folder itself.  A Mutex is used
     // to prevent concurrency.
     //
+    // This is best called using a ReplayDisconnect operation, which ensures an orderly disconnect
+    // by going through the ReplayQueue.  There are certain situations in open_remote_async() where
+    // this is not possible (because the queue hasn't been started).
+    //
     // NOTE: This bypasses open_count and forces the Folder closed, reestablishing a connection if
     // open_count is greater than zero
     internal async void close_internal_async(Folder.CloseReason local_reason, Folder.CloseReason 
remote_reason,
         bool flush_pending, Cancellable? cancellable) {
+        // make sure no open is waiting in the wings to start; close_internal_locked_async() will
+        // reestablish a connection if necessary
+        cancel_remote_open_timer();
+        
         int token;
         try {
             token = yield close_mutex.claim_async(cancellable);
@@ -781,8 +788,6 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
     // Should only be called when close_mutex is locked, i.e. use close_internal_async()
     private async void close_internal_locked_async(Folder.CloseReason local_reason,
         Folder.CloseReason remote_reason, bool flush_pending, Cancellable? cancellable) {
-        cancel_remote_open_timer();
-        
         // only flushing pending ReplayOperations if this is a "clean" close, not forced due to
         // error and if specified by caller (could be a non-error close on the server, i.e. "BYE",
         // but the connection is dropping, so don't flush pending)
@@ -806,12 +811,10 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
             // Close the replay queues; if a "clean" close, flush pending operations so everything
             // gets a chance to run; if forced close, drop everything outstanding
             try {
-                if (replay_queue != null) {
-                    debug("Closing replay queue for %s... (flush_pending=%s)", to_string(),
-                        flush_pending.to_string());
-                    yield replay_queue.close_async(flush_pending);
-                    debug("Closed replay queue for %s", to_string());
-                }
+                debug("Closing replay queue for %s (flush_pending=%s): %s", to_string(),
+                    flush_pending.to_string(), replay_queue.to_string());
+                yield replay_queue.close_async(flush_pending);
+                debug("Closed replay queue for %s: %s", to_string(), replay_queue.to_string());
             } catch (Error replay_queue_err) {
                 debug("Error closing %s replay queue: %s", to_string(), replay_queue_err.message);
             }
@@ -882,8 +885,6 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
         remote_folder = null;
         remote_count = -1;
         
-        remote_semaphore.reset();
-        
         // only signal waiters in wait_for_open_async() that the open failed if there is no cx
         // reestablishment to occur
         if (open_count <= 0) {
@@ -941,6 +942,7 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
             // prevent driving the value up
             folder.open_count = Numeric.int_floor(folder.open_count - 1, 0);
             
+            debug("Reestablishing broken connection to %s", folder.to_string());
             yield folder.open_async(OpenFlags.NO_DELAY, null);
         } catch (Error err) {
             debug("Error reestablishing broken connection to %s: %s", folder.to_string(), err.message);
@@ -1195,7 +1197,11 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
     private void on_remote_disconnected(Imap.ClientSession.DisconnectReason reason) {
         debug("on_remote_disconnected: reason=%s", reason.to_string());
         
-        replay_queue.schedule(new ReplayDisconnect(this, reason));
+        // reset remote_semaphore to indicate that callers must again wait for the remote to open...
+        // do this now to avoid race conditions w/ wait_for_open_async()
+        remote_semaphore.reset();
+        
+        replay_queue.schedule(new ReplayDisconnect(this, reason, false, null));
     }
     
     //
diff --git a/src/engine/imap-engine/imap-engine-replay-operation.vala 
b/src/engine/imap-engine/imap-engine-replay-operation.vala
index 0f20bf0..84b87ad 100644
--- a/src/engine/imap-engine/imap-engine-replay-operation.vala
+++ b/src/engine/imap-engine/imap-engine-replay-operation.vala
@@ -4,7 +4,7 @@
  * (version 2.1 or later).  See the COPYING file in this distribution.
  */
 
-private abstract class Geary.ImapEngine.ReplayOperation : Geary.BaseObject {
+private abstract class Geary.ImapEngine.ReplayOperation : Geary.BaseObject, Gee.Comparable<ReplayOperation> {
     /**
      * Scope specifies what type of operations (remote, local, or both) are needed by this operation.
      *
@@ -35,10 +35,8 @@ private abstract class Geary.ImapEngine.ReplayOperation : Geary.BaseObject {
         IGNORE
     }
     
-    private static int next_opnum = 0;
-    
     public string name { get; set; }
-    public int opnum { get; private set; }
+    public int64 submission_number { get; set; default = -1; }
     public Scope scope { get; private set; }
     public OnError on_remote_error { get; protected set; }
     public int remote_retry_count { get; set; default = 0; }
@@ -49,7 +47,6 @@ private abstract class Geary.ImapEngine.ReplayOperation : Geary.BaseObject {
     
     public ReplayOperation(string name, Scope scope, OnError on_remote_error = OnError.THROW) {
         this.name = name;
-        opnum = next_opnum++;
         this.scope = scope;
         this.on_remote_error = on_remote_error;
     }
@@ -156,11 +153,22 @@ private abstract class Geary.ImapEngine.ReplayOperation : Geary.BaseObject {
     
     public abstract string describe_state();
     
+    // The Comparable interface is merely to ensure the ReplayQueue sorts operations by their
+    // submission order, ensuring that retry operations are retried in order of submissions
+    public int compare_to(ReplayOperation other) {
+        assert(submission_number >= 0);
+        assert(other.submission_number >= 0);
+        
+        return (int) (submission_number - other.submission_number).clamp(-1, 1);
+    }
+    
     public string to_string() {
         string state = describe_state();
         
-        return (String.is_empty(state)) ? "[%d] %s".printf(opnum, name)
-            : "[%d] %s: %s".printf(opnum, name, state);
+        return String.is_empty(state)
+            ? "[%s] %s remote_retry_count=%d".printf(submission_number.to_string(), name, remote_retry_count)
+            : "[%s] %s: %s remote_retry_count=%d".printf(submission_number.to_string(), name, state,
+                remote_retry_count);
     }
 }
 
diff --git a/src/engine/imap-engine/imap-engine-replay-queue.vala 
b/src/engine/imap-engine/imap-engine-replay-queue.vala
index 34ca427..af43bad 100644
--- a/src/engine/imap-engine/imap-engine-replay-queue.vala
+++ b/src/engine/imap-engine/imap-engine-replay-queue.vala
@@ -51,12 +51,12 @@ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject {
     
     private weak MinimalFolder owner;
     private Nonblocking.Mailbox<ReplayOperation> local_queue = new Nonblocking.Mailbox<ReplayOperation>();
-    private Nonblocking.Mailbox<ReplayOperation> remote_queue = new Nonblocking.Mailbox<ReplayOperation>(
-        remote_replay_operation_comparator);
+    private Nonblocking.Mailbox<ReplayOperation> remote_queue = new Nonblocking.Mailbox<ReplayOperation>();
     private ReplayOperation? local_op_active = null;
     private ReplayOperation? remote_op_active = null;
     private Gee.ArrayList<ReplayOperation> notification_queue = new Gee.ArrayList<ReplayOperation>();
     private Scheduler.Scheduled? notification_timer = null;
+    private int64 next_submission_number = 0;
     
     private bool is_closed = false;
     
@@ -150,6 +150,10 @@ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject {
             return false;
         }
         
+        // assign a submission number to operation ... this *must* happen before it's submitted to
+        // any Mailbox
+        op.submission_number = next_submission_number++;
+        
         // note that in order for this to work (i.e. for sent and received operations to be handled
         // in order), it's *vital* that even REMOTE_ONLY operations go through the local queue,
         // only being scheduled on the remote queue *after* local operations ahead of it have
@@ -345,10 +349,6 @@ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject {
         }
     }
     
-    private static int remote_replay_operation_comparator(ReplayOperation a, ReplayOperation b) {
-        return a.remote_retry_count - b.remote_retry_count;
-    }
-    
     private async void do_replay_local_async() {
         bool queue_running = true;
         while (queue_running) {
@@ -488,10 +488,8 @@ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject {
             
             Error? remote_err = null;
             if (folder_opened || is_close_op) {
-                if (op.remote_retry_count > 0) {
-                    debug("Retrying op %s on %s: retry_count=%d", op.to_string(), to_string(),
-                        op.remote_retry_count);
-                }
+                if (op.remote_retry_count > 0)
+                    debug("Retrying op %s on %s", op.to_string(), to_string());
                 
                 try {
                     yield op.replay_remote_async();
@@ -501,20 +499,22 @@ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject {
                     
                     // If a hard failure and operation allows remote replay, schedule now
                     if ((op.on_remote_error == ReplayOperation.OnError.RETRY) && 
is_hard_failure(replay_err)) {
-                        debug("Schedule op retry %s on %s: remote_retry_count=%d", op.to_string(),
-                            to_string(), op.remote_retry_count);
+                        debug("Schedule op retry %s on %s", op.to_string(), to_string());
                         
-                        // use the retry count to sort the command at the top of the queue and
-                        // take another go at it ... the Folder will disconnect and reconnect due
-                        // to the hard error and wait_for_open_async() will block this command until
-                        // reconnected and normalized
+                        // the Folder will disconnect and reconnect due to the hard error and
+                        // wait_for_open_async() will block this command until reconnected and
+                        // normalized
                         op.remote_retry_count++;
                         remote_queue.send(op);
                         
                         continue;
                     } else if (op.on_remote_error == ReplayOperation.OnError.IGNORE) {
                         // ignoring error, simply notify as completed and continue
+                        debug("Ignoring op %s on %s", op.to_string(), to_string());
                     } else {
+                        debug("Throwing remote error for op %s on %s: %s", op.to_string(), to_string(),
+                            replay_err.message);
+                        
                         // store for notification
                         remote_err = replay_err;
                     }
diff --git a/src/engine/imap-engine/imap-engine.vala b/src/engine/imap-engine/imap-engine.vala
index 9380dab..97b60c3 100644
--- a/src/engine/imap-engine/imap-engine.vala
+++ b/src/engine/imap-engine/imap-engine.vala
@@ -63,6 +63,10 @@ private void on_synchronizer_stopped(Object? source, AsyncResult result) {
  * is due to software reasons, like credential failure or protocol violation.\
  */
 private static bool is_hard_failure(Error err) {
+    // CANCELLED is not a hard error
+    if (err is IOError.CANCELLED)
+        return false;
+    
     // Treat other errors -- most likely IOErrors -- as hard failures
     if (!(err is ImapError) && !(err is EngineError))
         return true;
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-create-email.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-create-email.vala
index bb8974b..f171357 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-create-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-create-email.vala
@@ -8,7 +8,7 @@ private class Geary.ImapEngine.CreateEmail : Geary.ImapEngine.SendReplayOperatio
     public Geary.EmailIdentifier? created_id { get; private set; default = null; }
     
     private MinimalFolder engine;
-    private RFC822.Message rfc822;
+    private RFC822.Message? rfc822;
     private Geary.EmailFlags? flags;
     private DateTime? date_received;
     private Cancellable? cancellable;
@@ -47,18 +47,29 @@ private class Geary.ImapEngine.CreateEmail : Geary.ImapEngine.SendReplayOperatio
         if (cancellable.is_cancelled())
             throw new IOError.CANCELLED("CreateEmail op cancelled immediately");
         
-        // use IMAP APPEND command on remote folders, which doesn't require opening a folder
-        created_id = yield engine.remote_folder.create_email_async(rfc822, flags, date_received);
+        // use IMAP APPEND command on remote folders, which doesn't require opening a folder ...
+        // if retrying after a successful create, rfc822 will be null
+        if (rfc822 != null)
+            created_id = yield engine.remote_folder.create_email_async(rfc822, flags, date_received);
+        
+        // because this command retries, the create completed, remove the RFC822 message to prevent
+        // creating it twice
+        rfc822 = null;
         
         // If the user cancelled the operation, we need to wipe the new message to keep this
         // operation atomic.
         if (cancellable.is_cancelled()) {
-            yield engine.remote_folder.remove_email_async(
-                new Imap.MessageSet.uid(((ImapDB.EmailIdentifier) created_id).uid).to_list(), null);
+            if (created_id != null) {
+                yield engine.remote_folder.remove_email_async(
+                    new Imap.MessageSet.uid(((ImapDB.EmailIdentifier) created_id).uid).to_list(), null);
+            }
             
             throw new IOError.CANCELLED("CreateEmail op cancelled after create");
         }
         
+        if (created_id == null)
+            return ReplayOperation.Status.COMPLETED;
+        
         // TODO: need to prevent gaps that may occur here
         Geary.Email created = new Geary.Email(created_id);
         Gee.Map<Geary.Email, bool> results = yield engine.local_folder.create_or_merge_email_async(
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-move-email.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-move-email.vala
index 45a1a60..d0ae775 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-move-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-move-email.vala
@@ -11,6 +11,7 @@ private class Geary.ImapEngine.MoveEmail : Geary.ImapEngine.SendReplayOperation
     private Cancellable? cancellable;
     private Gee.Set<ImapDB.EmailIdentifier>? moved_ids = null;
     private int original_count = 0;
+    private Gee.List<Imap.MessageSet>? remaining_msg_sets = null;
 
     public MoveEmail(MinimalFolder engine, Gee.List<ImapDB.EmailIdentifier> to_move, 
         Geary.FolderPath destination, Cancellable? cancellable = null) {
@@ -62,16 +63,26 @@ private class Geary.ImapEngine.MoveEmail : Geary.ImapEngine.SendReplayOperation
         if (moved_ids.size == 0)
             return ReplayOperation.Status.COMPLETED;
         
-        // don't use Cancellable throughout I/O operations in order to assure transaction completes
-        // fully
-        if (cancellable != null && cancellable.is_cancelled())
-            throw new IOError.CANCELLED("Move email to %s cancelled", engine.remote_folder.to_string());
+        // Remaining MessageSets are persisted in case of network retries
+        if (remaining_msg_sets == null)
+            remaining_msg_sets = Imap.MessageSet.uid_sparse(ImapDB.EmailIdentifier.to_uids(moved_ids));
         
-        Gee.List<Imap.MessageSet> msg_sets = Imap.MessageSet.uid_sparse(
-            ImapDB.EmailIdentifier.to_uids(moved_ids));
-        foreach (Imap.MessageSet msg_set in msg_sets) {
+        if (remaining_msg_sets == null || remaining_msg_sets.size == 0)
+            return ReplayOperation.Status.COMPLETED;
+        
+        Gee.Iterator<Imap.MessageSet> iter = remaining_msg_sets.iterator();
+        while (iter.next()) {
+            // don't use Cancellable throughout I/O operations in order to assure transaction completes
+            // fully
+            if (cancellable != null && cancellable.is_cancelled())
+                throw new IOError.CANCELLED("Move email to %s cancelled", engine.remote_folder.to_string());
+            
+            Imap.MessageSet msg_set = iter.get();
             yield engine.remote_folder.copy_email_async(msg_set, destination, null);
             yield engine.remote_folder.remove_email_async(msg_set.to_list(), null);
+            
+            // completed successfully, remove from list in case of retry
+            iter.remove();
         }
         
         return ReplayOperation.Status.COMPLETED;
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-replay-disconnect.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-replay-disconnect.vala
index 0d2f7e1..2acf58f 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-replay-disconnect.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-replay-disconnect.vala
@@ -7,12 +7,17 @@
 private class Geary.ImapEngine.ReplayDisconnect : Geary.ImapEngine.ReplayOperation {
     private MinimalFolder owner;
     private Imap.ClientSession.DisconnectReason reason;
+    private bool flush_pending;
+    private Cancellable? cancellable;
     
-    public ReplayDisconnect(MinimalFolder owner, Imap.ClientSession.DisconnectReason reason) {
+    public ReplayDisconnect(MinimalFolder owner, Imap.ClientSession.DisconnectReason reason,
+        bool flush_pending, Cancellable? cancellable) {
         base ("Disconnect", Scope.LOCAL_ONLY);
         
         this.owner = owner;
         this.reason = reason;
+        this.flush_pending = flush_pending;
+        this.cancellable = cancellable;
     }
     
     public override void notify_remote_removed_position(Imap.SequenceNumber removed) {
@@ -37,7 +42,7 @@ private class Geary.ImapEngine.ReplayDisconnect : Geary.ImapEngine.ReplayOperati
             // ReplayDisconnect is only used when remote disconnects, so never flush pending, the
             // connection is down or going down
             owner.close_internal_async.begin(Geary.Folder.CloseReason.LOCAL_CLOSE, remote_reason,
-                false, null);
+                flush_pending, cancellable);
             
             return false;
         });
diff --git a/src/engine/nonblocking/nonblocking-abstract-semaphore.vala 
b/src/engine/nonblocking/nonblocking-abstract-semaphore.vala
index f689003..2c3672a 100644
--- a/src/engine/nonblocking/nonblocking-abstract-semaphore.vala
+++ b/src/engine/nonblocking/nonblocking-abstract-semaphore.vala
@@ -46,9 +46,6 @@ public abstract class Geary.Nonblocking.AbstractSemaphore : BaseObject {
     private bool passed = false;
     private Gee.List<Pending> pending_queue = new Gee.LinkedList<Pending>();
     
-    public virtual signal void at_reset() {
-    }
-    
     protected AbstractSemaphore(bool broadcast, bool autoreset, Cancellable? cancellable = null) {
         this.broadcast = broadcast;
         this.autoreset = autoreset;
@@ -70,10 +67,6 @@ public abstract class Geary.Nonblocking.AbstractSemaphore : BaseObject {
             cancellable.cancelled.disconnect(on_cancelled);
     }
     
-    protected virtual void notify_at_reset() {
-        at_reset();
-    }
-    
     private void trigger(bool all) {
         if (pending_queue.size == 0)
             return;
@@ -138,12 +131,7 @@ public abstract class Geary.Nonblocking.AbstractSemaphore : BaseObject {
     }
     
     public virtual void reset() {
-        if (!passed)
-            return;
-        
         passed = false;
-        
-        notify_at_reset();
     }
     
     public bool is_passed() {
diff --git a/src/engine/nonblocking/nonblocking-mailbox.vala b/src/engine/nonblocking/nonblocking-mailbox.vala
index 3337b44..93854da 100644
--- a/src/engine/nonblocking/nonblocking-mailbox.vala
+++ b/src/engine/nonblocking/nonblocking-mailbox.vala
@@ -28,8 +28,7 @@ public class Geary.Nonblocking.Mailbox<G> : BaseObject {
     private Nonblocking.Spinlock spinlock = new Nonblocking.Spinlock();
     
     public Mailbox(owned CompareDataFunc<G>? comparator = null) {
-        // can't use ternary here, Vala bug
-        if (comparator == null)
+        if (comparator == null && !typeof(G).is_a(typeof(Gee.Comparable)))
             queue = new Gee.LinkedList<G>();
         else
             queue = new Gee.PriorityQueue<G>((owned) comparator);
diff --git a/src/engine/nonblocking/nonblocking-reporting-semaphore.vala 
b/src/engine/nonblocking/nonblocking-reporting-semaphore.vala
index fd8cda5..0ffcb7f 100644
--- a/src/engine/nonblocking/nonblocking-reporting-semaphore.vala
+++ b/src/engine/nonblocking/nonblocking-reporting-semaphore.vala
@@ -17,11 +17,11 @@ public class Geary.Nonblocking.ReportingSemaphore<G> : Geary.Nonblocking.Semapho
         result = default_result;
     }
     
-    protected override void notify_at_reset() {
+    public override void reset() {
         result = default_result;
         err = null;
         
-        base.notify_at_reset();
+        base.reset();
     }
     
     public void notify_result(G result, Error? err) throws Error {


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