Skip to content

Commit

Permalink
quota-clone: Move state tracking to user context
Browse files Browse the repository at this point in the history
This way there's no need to do multiple flushes when e.g. multiple
mailboxes change.

The flush had to be removed from close(), because the code path recurses:

 * mailbox_transaction_commit() when mail is saved
 * quota_count() starts counting the quota, which opens and closes mailbox
 * quota_clone_mailbox_close() attempts to flush quota, which again recurses
into quota_count(), which returns 0 for the quota value.

Based on patch by Michael Slusarz
  • Loading branch information
sirainen authored and cmouse committed Aug 7, 2018
1 parent 3117285 commit 4e5e57d
Showing 1 changed file with 59 additions and 45 deletions.
104 changes: 59 additions & 45 deletions src/plugins/quota-clone/quota-clone-plugin.c
Expand Up @@ -31,20 +31,15 @@ static MODULE_CONTEXT_DEFINE_INIT(quota_clone_storage_module,
struct quota_clone_user {
union mail_user_module_context module_ctx;
struct dict *dict;
bool quota_flushing;
};

struct quota_clone_mailbox {
union mailbox_module_context module_ctx;
struct timeout *to_quota_flush;
bool quota_changed;
bool quota_flushing;
};

static void quota_clone_flush_real(struct mailbox *box)
static void quota_clone_flush_real(struct mail_user *user)
{
struct quota_clone_mailbox *qbox = QUOTA_CLONE_CONTEXT(box);
struct quota_clone_user *quser =
QUOTA_CLONE_USER_CONTEXT_REQUIRE(box->storage->user);
QUOTA_CLONE_USER_CONTEXT_REQUIRE(user);
struct dict_transaction_context *trans;
struct quota_root_iter *iter;
struct quota_root *root;
Expand All @@ -53,12 +48,12 @@ static void quota_clone_flush_real(struct mailbox *box)
enum quota_get_result bytes_res, count_res;

/* we'll clone the first quota root */
iter = quota_root_iter_init(box);
iter = quota_root_iter_init_user(user);
root = quota_root_iter_next(iter);
quota_root_iter_deinit(&iter);
if (root == NULL) {
/* no quota roots defined for this mailbox - ignore */
qbox->quota_changed = FALSE;
/* no quota roots defined - ignore */
quser->quota_changed = FALSE;
return;
}

Expand Down Expand Up @@ -109,103 +104,121 @@ static void quota_clone_flush_real(struct mailbox *box)
if (dict_transaction_commit(&trans, &error) < 0)
i_error("quota_clone_plugin: Failed to commit dict update: %s", error);
else
qbox->quota_changed = FALSE;
quser->quota_changed = FALSE;
}

static void quota_clone_flush(struct mailbox *box)
static void quota_clone_flush(struct mail_user *user)
{
struct quota_clone_mailbox *qbox = QUOTA_CLONE_CONTEXT(box);
struct quota_clone_user *quser =
QUOTA_CLONE_USER_CONTEXT_REQUIRE(box->storage->user);
QUOTA_CLONE_USER_CONTEXT_REQUIRE(user);

timeout_remove(&qbox->to_quota_flush);
timeout_remove(&quser->to_quota_flush);

if (quser->quota_flushing) {
/* recursing back from quota recalculation */
} else if (qbox->quota_changed) {
} else if (quser->quota_changed) {
quser->quota_flushing = TRUE;
quota_clone_flush_real(box);
quota_clone_flush_real(user);
quser->quota_flushing = FALSE;
}
}

static struct mail_user *quota_mailbox_get_user(struct mailbox *box)
{
struct mail_namespace *ns = mailbox_list_get_namespace(box->list);
return ns->owner != NULL ? ns->owner : ns->user;
}

static void quota_clone_changed(struct mailbox *box)
{
struct quota_clone_mailbox *qbox = QUOTA_CLONE_CONTEXT(box);
struct mail_user *user = quota_mailbox_get_user(box);
struct quota_clone_user *quser =
QUOTA_CLONE_USER_CONTEXT_REQUIRE(user);

qbox->quota_changed = TRUE;
if (qbox->to_quota_flush == NULL) {
qbox->to_quota_flush = timeout_add(QUOTA_CLONE_FLUSH_DELAY_MSECS,
quota_clone_flush, box);
quser->quota_changed = TRUE;
if (quser->to_quota_flush == NULL) {
quser->to_quota_flush = timeout_add(QUOTA_CLONE_FLUSH_DELAY_MSECS,
quota_clone_flush, user);
}
}

static int quota_clone_save_finish(struct mail_save_context *ctx)
{
struct quota_clone_mailbox *qbox =
union mailbox_module_context *qbox =
QUOTA_CLONE_CONTEXT(ctx->transaction->box);

quota_clone_changed(ctx->transaction->box);
return qbox->module_ctx.super.save_finish(ctx);
return qbox->super.save_finish(ctx);
}

static int
quota_clone_copy(struct mail_save_context *ctx, struct mail *mail)
{
struct quota_clone_mailbox *qbox =
union mailbox_module_context *qbox =
QUOTA_CLONE_CONTEXT(ctx->transaction->box);

quota_clone_changed(ctx->transaction->box);
return qbox->module_ctx.super.copy(ctx, mail);
return qbox->super.copy(ctx, mail);
}

static void
quota_clone_mailbox_sync_notify(struct mailbox *box, uint32_t uid,
enum mailbox_sync_type sync_type)
{
struct quota_clone_mailbox *qbox = QUOTA_CLONE_CONTEXT(box);
union mailbox_module_context *qbox = QUOTA_CLONE_CONTEXT(box);

if (qbox->module_ctx.super.sync_notify != NULL)
qbox->module_ctx.super.sync_notify(box, uid, sync_type);
if (qbox->super.sync_notify != NULL)
qbox->super.sync_notify(box, uid, sync_type);

if (sync_type == MAILBOX_SYNC_TYPE_EXPUNGE)
quota_clone_changed(box);
}

static void quota_clone_mailbox_close(struct mailbox *box)
{
struct quota_clone_mailbox *qbox = QUOTA_CLONE_CONTEXT(box);

qbox->module_ctx.super.close(box);

quota_clone_flush(box);
}

static void quota_clone_mailbox_allocated(struct mailbox *box)
{
struct quota_clone_user *quser =
QUOTA_CLONE_USER_CONTEXT(box->storage->user);
struct mailbox_vfuncs *v = box->vlast;
struct quota_clone_mailbox *qbox;
union mailbox_module_context *qbox;

if (quser == NULL)
return;

qbox = p_new(box->pool, struct quota_clone_mailbox, 1);
qbox->module_ctx.super = *v;
box->vlast = &qbox->module_ctx.super;
qbox = p_new(box->pool, union mailbox_module_context, 1);
qbox->super = *v;
box->vlast = &qbox->super;

v->save_finish = quota_clone_save_finish;
v->copy = quota_clone_copy;
v->sync_notify = quota_clone_mailbox_sync_notify;
v->close = quota_clone_mailbox_close;
MODULE_CONTEXT_SET(box, quota_clone_storage_module, qbox);
MODULE_CONTEXT_SET_SELF(box, quota_clone_storage_module, qbox);
}

static void quota_clone_mail_user_deinit_pre(struct mail_user *user)
{
struct quota_clone_user *quser = QUOTA_CLONE_USER_CONTEXT_REQUIRE(user);

dict_wait(quser->dict);
/* Check once more if quota needs to be updated. This needs to be done
in deinit_pre(), because at deinit() the quota is already
deinitialized. */
if (quser->to_quota_flush != NULL) {
i_assert(!quser->quota_flushing);
quota_clone_flush(user);
dict_wait(quser->dict);
i_assert(quser->to_quota_flush == NULL);
}
quser->module_ctx.super.deinit_pre(user);
}

static void quota_clone_mail_user_deinit(struct mail_user *user)
{
struct quota_clone_user *quser = QUOTA_CLONE_USER_CONTEXT_REQUIRE(user);

/* wait once more, just in case something changed quota during
deinit_pre() */
dict_wait(quser->dict);
i_assert(quser->to_quota_flush == NULL);
dict_deinit(&quser->dict);
quser->module_ctx.super.deinit(user);
}
Expand Down Expand Up @@ -239,6 +252,7 @@ static void quota_clone_mail_user_created(struct mail_user *user)
quser = p_new(user->pool, struct quota_clone_user, 1);
quser->module_ctx.super = *v;
user->vlast = &quser->module_ctx.super;
v->deinit_pre = quota_clone_mail_user_deinit_pre;
v->deinit = quota_clone_mail_user_deinit;
quser->dict = dict;
MODULE_CONTEXT_SET(user, quota_clone_user_module, quser);
Expand Down

0 comments on commit 4e5e57d

Please sign in to comment.