[geary/wip/remote-retry: 3/3] Much closer: retries are consistent and don't hang
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/wip/remote-retry: 3/3] Much closer: retries are consistent and don't hang
- Date: Wed, 21 Jan 2015 02:44:10 +0000 (UTC)
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]