revamp handling of expunged messages

try to purge sync entries based on which messages are *actually*
expunged, rather than those that are *expected* to be expunged.

to save network bandwidth, the IMAP driver doesn't report all expunges,
so some entry purges would be delayed - potentially indefinitely, e.g.,
when only --pull-new --push is used, or Trash isn't used (nor
ExpungeSolo, prospectively). so keep a fallback path to avoid this.
This commit is contained in:
Oswald Buddenhagen 2022-04-23 14:45:44 +02:00
parent 1a1ac25bc8
commit 1631361f66
5 changed files with 52 additions and 20 deletions

View File

@ -283,8 +283,9 @@ struct driver {
/* Expunge deleted messages from the current mailbox and close it. /* Expunge deleted messages from the current mailbox and close it.
* There is no need to explicitly close a mailbox if no expunge is needed. */ * There is no need to explicitly close a mailbox if no expunge is needed. */
// If reported is true, the expunge callback was called reliably.
void (*close_box)( store_t *ctx, void (*close_box)( store_t *ctx,
void (*cb)( int sts, void *aux ), void *aux ); void (*cb)( int sts, int reported, void *aux ), void *aux );
/* Cancel queued commands which are not in flight yet; they will have their /* Cancel queued commands which are not in flight yet; they will have their
* callbacks invoked with DRV_CANCELED. Afterwards, wait for the completion of * callbacks invoked with DRV_CANCELED. Afterwards, wait for the completion of

View File

@ -3082,21 +3082,33 @@ imap_set_flags_p3( imap_set_msg_flags_state_t *sts )
/******************* imap_close_box *******************/ /******************* imap_close_box *******************/
#define IMAP_CMD_EXPUNGE \
void (*callback)( int sts, int reported, void *aux ); \
void *callback_aux;
typedef union { typedef union {
imap_cmd_refcounted_state_t gen; imap_cmd_refcounted_state_t gen;
struct { struct {
IMAP_CMD_REFCOUNTED_STATE IMAP_CMD_REFCOUNTED_STATE
void (*callback)( int sts, void *aux ); IMAP_CMD_EXPUNGE
void *callback_aux;
}; };
} imap_expunge_state_t; } imap_expunge_state_t;
typedef union {
imap_cmd_t gen;
struct {
IMAP_CMD
IMAP_CMD_EXPUNGE
};
} imap_cmd_close_t;
static void imap_close_box_p2( imap_store_t *, imap_cmd_t *, int ); static void imap_close_box_p2( imap_store_t *, imap_cmd_t *, int );
static void imap_close_box_p3( imap_expunge_state_t * ); static void imap_close_box_p3( imap_expunge_state_t * );
static void imap_close_box_simple_p2( imap_store_t *, imap_cmd_t *, int );
static void static void
imap_close_box( store_t *gctx, imap_close_box( store_t *gctx,
void (*cb)( int sts, void *aux ), void *aux ) void (*cb)( int sts, int reported, void *aux ), void *aux )
{ {
imap_store_t *ctx = (imap_store_t *)gctx; imap_store_t *ctx = (imap_store_t *)gctx;
@ -3132,8 +3144,8 @@ imap_close_box( store_t *gctx,
// Note that, to save bandwidth, we don't use EXPUNGE. Also, in many // Note that, to save bandwidth, we don't use EXPUNGE. Also, in many
// cases, we wouldn't be able to map the EXPUNGE responses' seq numbers // cases, we wouldn't be able to map the EXPUNGE responses' seq numbers
// anyway, due to not having fetched the messages. // anyway, due to not having fetched the messages.
INIT_IMAP_CMD(imap_cmd_simple_t, cmd, cb, aux) INIT_IMAP_CMD(imap_cmd_close_t, cmd, cb, aux)
imap_exec( ctx, &cmd->gen, imap_done_simple_box, "CLOSE" ); imap_exec( ctx, &cmd->gen, imap_close_box_simple_p2, "CLOSE" );
} }
} }
@ -3149,7 +3161,17 @@ imap_close_box_p2( imap_store_t *ctx ATTR_UNUSED, imap_cmd_t *cmd, int response
static void static void
imap_close_box_p3( imap_expunge_state_t *sts ) imap_close_box_p3( imap_expunge_state_t *sts )
{ {
DONE_REFCOUNTED_STATE(sts) DONE_REFCOUNTED_STATE_ARGS(sts, , 1)
}
static void
imap_close_box_simple_p2( imap_store_t *ctx ATTR_UNUSED,
imap_cmd_t *cmd, int response )
{
imap_cmd_close_t *cmdp = (imap_cmd_close_t *)cmd;
transform_box_response( &response );
cmdp->callback( response, 0, cmdp->callback_aux );
} }
/******************* imap_trash_msg *******************/ /******************* imap_trash_msg *******************/

View File

@ -1795,7 +1795,7 @@ maildir_trash_msg( store_t *gctx, message_t *gmsg,
static void static void
maildir_close_box( store_t *gctx, maildir_close_box( store_t *gctx,
void (*cb)( int sts, void *aux ), void *aux ) void (*cb)( int sts, int reported, void *aux ), void *aux )
{ {
maildir_store_t *ctx = (maildir_store_t *)gctx; maildir_store_t *ctx = (maildir_store_t *)gctx;
maildir_message_t *msg; maildir_message_t *msg;
@ -1819,7 +1819,7 @@ maildir_close_box( store_t *gctx,
ctx->expunge_callback( &msg->gen, ctx->callback_aux ); ctx->expunge_callback( &msg->gen, ctx->callback_aux );
#ifdef USE_DB #ifdef USE_DB
if (ctx->db && (ret = maildir_purge_msg( ctx, msg->base )) != DRV_OK) { if (ctx->db && (ret = maildir_purge_msg( ctx, msg->base )) != DRV_OK) {
cb( ret, aux ); cb( ret, 1, aux );
return; return;
} }
#endif /* USE_DB */ #endif /* USE_DB */
@ -1827,11 +1827,11 @@ maildir_close_box( store_t *gctx,
} }
} }
if (!retry) { if (!retry) {
cb( DRV_OK, aux ); cb( DRV_OK, 1, aux );
return; return;
} }
if ((ret = maildir_rescan( ctx )) != DRV_OK) { if ((ret = maildir_rescan( ctx )) != DRV_OK) {
cb( ret, aux ); cb( ret, 1, aux );
return; return;
} }
} }

View File

@ -58,7 +58,6 @@ BIT_ENUM(
ST_SENT_TRASH, ST_SENT_TRASH,
ST_CLOSING, ST_CLOSING,
ST_CLOSED, ST_CLOSED,
ST_DID_EXPUNGE,
ST_SENT_CANCEL, ST_SENT_CANCEL,
ST_CANCELED, ST_CANCELED,
) )
@ -465,6 +464,7 @@ message_expunged( message_t *msg, void *aux )
(void)svars; (void)svars;
if (msg->srec) { if (msg->srec) {
msg->srec->status |= S_GONE(t);
msg->srec->msg[t] = NULL; msg->srec->msg[t] = NULL;
msg->srec = NULL; msg->srec = NULL;
} }
@ -1867,7 +1867,7 @@ msg_rtrashed( int sts, uint uid ATTR_UNUSED, copy_vars_t *vars )
sync_close( svars, t ); sync_close( svars, t );
} }
static void box_closed( int sts, void *aux ); static void box_closed( int sts, int reported, void *aux );
static void box_closed_p2( sync_vars_t *svars, int t ); static void box_closed_p2( sync_vars_t *svars, int t );
static void static void
@ -1891,10 +1891,23 @@ sync_close( sync_vars_t *svars, int t )
} }
static void static void
box_closed( int sts, void *aux ) box_closed( int sts, int reported, void *aux )
{ {
SVARS_CHECK_RET; SVARS_CHECK_RET;
svars->state[t] |= ST_DID_EXPUNGE; if (!reported) {
for (sync_rec_t *srec = svars->srecs; srec; srec = srec->next) {
if (srec->status & S_DEAD)
continue;
// Note that this logic is somewhat optimistic - theoretically, it's
// possible that a message was concurrently undeleted before we tried
// to expunge it. Such a message would be subsequently re-propagated
// by a refresh, and in the extremely unlikely case of this happening
// on both sides, we'd even get a duplicate. That's why this is only
// a fallback.
if (srec->status & S_DEL(t))
srec->status |= S_GONE(t);
}
}
box_closed_p2( svars, t ); box_closed_p2( svars, t );
} }
@ -1931,10 +1944,6 @@ box_closed_p2( sync_vars_t *svars, int t )
for (srec = svars->srecs; srec; srec = srec->next) { for (srec = svars->srecs; srec; srec = srec->next) {
if (srec->status & S_DEAD) if (srec->status & S_DEAD)
continue; continue;
if ((srec->status & S_DEL(F)) && (svars->state[F] & ST_DID_EXPUNGE))
srec->status |= S_GONE(F);
if ((srec->status & S_DEL(N)) && (svars->state[N] & ST_DID_EXPUNGE))
srec->status |= S_GONE(N);
if (!srec->uid[N] || (srec->status & S_GONE(N))) { if (!srec->uid[N] || (srec->status & S_GONE(N))) {
if (!srec->uid[F] || (srec->status & S_GONE(F)) || if (!srec->uid[F] || (srec->status & S_GONE(F)) ||
((srec->status & S_EXPIRED) && svars->maxuid[F] >= srec->uid[F] && svars->maxxfuid >= srec->uid[F])) { ((srec->status & S_EXPIRED) && svars->maxuid[F] >= srec->uid[F] && svars->maxxfuid >= srec->uid[F])) {

View File

@ -18,7 +18,7 @@ BIT_ENUM(
S_DUMMY(2), // f/n message is only a placeholder S_DUMMY(2), // f/n message is only a placeholder
S_SKIPPED, // pre-1.4 legacy: the entry was not propagated (message is too big) S_SKIPPED, // pre-1.4 legacy: the entry was not propagated (message is too big)
S_GONE(2), // ephemeral: f/n message has been expunged S_GONE(2), // ephemeral: f/n message has been expunged
S_DEL(2), // ephemeral: f/n message would be subject to expunge S_DEL(2), // ephemeral: f/n message would be subject to non-selective expunge
S_DELETE, // ephemeral: flags propagation is a deletion S_DELETE, // ephemeral: flags propagation is a deletion
S_UPGRADE, // ephemeral: upgrading placeholder, do not apply MaxSize S_UPGRADE, // ephemeral: upgrading placeholder, do not apply MaxSize
S_PURGE, // ephemeral: placeholder is being nuked S_PURGE, // ephemeral: placeholder is being nuked