Skip to content

Commit 7ba4b72

Browse files
committed
when running multiple retention updates take into account the last one
1 parent d3aae0a commit 7ba4b72

5 files changed

Lines changed: 297 additions & 60 deletions

File tree

lib/api/mailboxes.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -636,10 +636,15 @@ module.exports = (db, server, mailboxHandler) => {
636636
});
637637
}
638638

639-
await updateMailbox(user, mailbox, updates);
640-
641-
if ('retention' in updates) {
642-
await taskHandler.ensure('mailbox-retention', { user, mailbox }, { user, mailbox });
639+
const updateResult = await updateMailbox(user, mailbox, updates);
640+
641+
if ('retention' in updates && updateResult && updateResult.changes && updateResult.changes.retention) {
642+
await taskHandler.ensure(
643+
'mailbox-retention',
644+
{ user, mailbox },
645+
{ user, mailbox, retentionCounter: updateResult.mailbox.retentionCounter || 0 },
646+
{ updateExistingData: true }
647+
);
643648
}
644649

645650
return res.json({

lib/mailbox-handler.js

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ class MailboxHandler {
8888
modifyIndex: 1,
8989
subscribed: true,
9090
flags: [],
91-
retention: userData.retention
91+
retention: userData.retention,
92+
retentionCounter: 0
9293
};
9394

9495
Object.keys(opts || {}).forEach(key => {
@@ -188,22 +189,39 @@ class MailboxHandler {
188189
return callback(err, 'ALREADYEXISTS');
189190
}
190191

191-
let $set = { path: newname };
192+
const $set = { path: newname };
193+
const $inc = {};
194+
const changes = {};
192195

193196
Object.keys(opts || {}).forEach(key => {
194197
if (!['_id', 'user', 'path'].includes(key)) {
195-
$set[key] = opts[key];
198+
if (mailboxData[key] !== opts[key]) {
199+
$set[key] = opts[key];
200+
changes[key] = true;
201+
}
196202
}
197203
});
198204

205+
if (changes.retention) {
206+
$inc.retentionCounter = 1;
207+
}
208+
209+
const update = {
210+
$set
211+
};
212+
213+
if ($inc.retentionCounter) {
214+
update.$inc = $inc;
215+
}
216+
199217
this.database.collection('mailboxes').findOneAndUpdate(
200218
{
201219
_id: mailbox
202220
},
221+
update,
203222
{
204-
$set
223+
returnDocument: 'after'
205224
},
206-
{},
207225
(err, item) => {
208226
if (err) {
209227
return callback(err);
@@ -233,7 +251,11 @@ class MailboxHandler {
233251
},
234252
() => {
235253
this.notifier.fire(mailboxData.user);
236-
return callback(null, true, mailbox);
254+
return callback(null, true, mailbox, {
255+
updated: true,
256+
mailbox: item.value,
257+
changes
258+
});
237259
}
238260
);
239261
}
@@ -408,31 +430,58 @@ class MailboxHandler {
408430
}
409431

410432
if (updates.path && updates.path !== mailboxData.path) {
411-
return this.rename(user, mailbox, updates.path, updates, callback);
433+
return this.rename(user, mailbox, updates.path, updates, (err, status, renamedMailbox, updateResult) => {
434+
if (err) {
435+
return callback(err);
436+
}
437+
438+
return callback(null, updateResult || { updated: !!status, mailbox: renamedMailbox, changes: {} });
439+
});
412440
}
413441

414-
let $set = {};
442+
const $set = {};
443+
const $inc = {};
444+
const changes = {};
415445
let hasChanges = false;
416446

417447
Object.keys(updates || {}).forEach(key => {
418448
if (!['_id', 'user', 'path'].includes(key)) {
419-
$set[key] = updates[key];
420-
hasChanges = true;
449+
if (mailboxData[key] !== updates[key]) {
450+
$set[key] = updates[key];
451+
changes[key] = true;
452+
hasChanges = true;
453+
}
421454
}
422455
});
423456

424457
if (!hasChanges) {
425-
return callback(null, true);
458+
return callback(null, {
459+
updated: false,
460+
mailbox: mailboxData,
461+
changes
462+
});
463+
}
464+
465+
if (changes.retention) {
466+
$inc.retentionCounter = 1;
467+
}
468+
469+
const update = {
470+
$set
471+
};
472+
473+
if ($inc.retentionCounter) {
474+
update.$inc = $inc;
426475
}
427476

428477
this.database.collection('mailboxes').findOneAndUpdate(
429478
{
430479
_id: mailbox
431480
},
481+
update,
432482
{
433-
$set
483+
returnDocument: 'after'
434484
},
435-
{},
436485
(err, item) => {
437486
if (err) {
438487
return callback(err);
@@ -445,7 +494,11 @@ class MailboxHandler {
445494
return callback(err, 'NONEXISTENT');
446495
}
447496

448-
return callback(null, true);
497+
return callback(null, {
498+
updated: true,
499+
mailbox: item.value,
500+
changes
501+
});
449502
}
450503
);
451504
}

lib/task-handler.js

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class TaskHandler {
4747
options = options || {};
4848

4949
let now = new Date();
50+
let updateExistingData = !!options.updateExistingData;
5051

5152
let notBefore = false;
5253

@@ -61,26 +62,29 @@ class TaskHandler {
6162
query[`data.${key}`] = matchQuery[key];
6263
});
6364

64-
let r = await this.database.collection('tasks').findOneAndUpdate(
65-
query,
66-
{
67-
$setOnInsert: {
68-
task: type,
69-
locked: notBefore ? true : false,
70-
lockedUntil: notBefore || now,
71-
created: now,
72-
status: notBefore ? 'delayed' : 'waiting',
73-
data
74-
},
75-
$set: {
76-
updated: new Date()
77-
}
65+
let update = {
66+
$setOnInsert: {
67+
task: type,
68+
locked: notBefore ? true : false,
69+
lockedUntil: notBefore || now,
70+
created: now,
71+
status: notBefore ? 'delayed' : 'waiting',
72+
data
7873
},
79-
{
80-
upsert: true,
81-
returnDocument: 'after'
74+
$set: {
75+
updated: new Date()
8276
}
83-
);
77+
};
78+
79+
if (updateExistingData) {
80+
update.$set.data = data;
81+
delete update.$setOnInsert.data;
82+
}
83+
84+
let r = await this.database.collection('tasks').findOneAndUpdate(query, update, {
85+
upsert: true,
86+
returnDocument: 'after'
87+
});
8488

8589
if (!r || !r.value) {
8690
throw new Error('Failed to create task');

lib/tasks/mailbox-retention.js

Lines changed: 108 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,37 +5,79 @@ const db = require('../db');
55
const consts = require('../consts');
66
const tools = require('../tools');
77

8+
let getState = async (task, data) => {
9+
const [mailboxData, taskData] = await Promise.all([
10+
db.database.collection('mailboxes').findOne(
11+
{
12+
_id: data.mailbox,
13+
user: data.user
14+
},
15+
{
16+
projection: {
17+
retention: true,
18+
retentionCounter: true
19+
}
20+
}
21+
),
22+
db.database.collection('tasks').findOne(
23+
{
24+
_id: task._id
25+
},
26+
{
27+
projection: {
28+
'data.retentionCounter': true
29+
}
30+
}
31+
)
32+
]);
33+
34+
return {
35+
mailboxData,
36+
mailboxRetentionCounter: mailboxData?.retentionCounter || 0,
37+
taskRetentionCounter: taskData?.data?.retentionCounter || 0
38+
};
39+
};
40+
841
let run = async (task, data) => {
942
let processed = 0;
1043
let passes = 0;
1144
let rerun = false;
45+
let activeRetentionCounter = data?.retentionCounter || 0;
1246

1347
do {
1448
passes++;
1549
rerun = false;
16-
17-
const passStart = new Date();
1850
let lastUid = 0;
1951
let hasMore = true;
2052

2153
while (hasMore) {
22-
const mailboxData = await db.database.collection('mailboxes').findOne(
23-
{
24-
_id: data.mailbox,
25-
user: data.user
26-
},
27-
{
28-
projection: {
29-
retention: true
30-
}
31-
}
32-
);
54+
const state = await getState(task, data);
55+
const mailboxData = state.mailboxData;
3356

3457
if (!mailboxData) {
3558
log.verbose('Tasks', 'task=mailbox-retention id=%s user=%s mailbox=%s status=missing-mailbox', task._id, data.user, data.mailbox);
3659
return { processed, passes };
3760
}
3861

62+
if (state.taskRetentionCounter < state.mailboxRetentionCounter) {
63+
log.verbose(
64+
'Tasks',
65+
'task=mailbox-retention id=%s user=%s mailbox=%s status=stale taskRetentionCounter=%s mailboxRetentionCounter=%s',
66+
task._id,
67+
data.user,
68+
data.mailbox,
69+
state.taskRetentionCounter,
70+
state.mailboxRetentionCounter
71+
);
72+
return { processed, passes };
73+
}
74+
75+
if (state.taskRetentionCounter > activeRetentionCounter) {
76+
activeRetentionCounter = state.taskRetentionCounter;
77+
rerun = true;
78+
break;
79+
}
80+
3981
const messages = await db.database
4082
.collection('messages')
4183
.find({
@@ -60,6 +102,25 @@ let run = async (task, data) => {
60102
continue;
61103
}
62104

105+
const latestTaskData = await db.database.collection('tasks').findOne(
106+
{
107+
_id: task._id
108+
},
109+
{
110+
projection: {
111+
'data.retentionCounter': true
112+
}
113+
}
114+
);
115+
116+
const latestTaskRetentionCounter = latestTaskData?.data?.retentionCounter || 0;
117+
118+
if (latestTaskRetentionCounter > activeRetentionCounter) {
119+
activeRetentionCounter = latestTaskRetentionCounter;
120+
rerun = true;
121+
break;
122+
}
123+
63124
const operations = messages.map(messageData => {
64125
// Messages get a fresh ObjectId when they are copied or moved, so its timestamp tracks arrival to this mailbox.
65126
const retentionState = tools.getMessageRetentionState(mailboxData, messageData._id.getTimestamp().getTime());
@@ -98,21 +159,44 @@ let run = async (task, data) => {
98159
lastUid = messages[messages.length - 1].uid;
99160
}
100161

101-
const taskData = await db.database.collection('tasks').findOne(
102-
{
103-
_id: task._id
104-
},
105-
{
106-
projection: {
107-
updated: true
108-
}
162+
if (!rerun) {
163+
const state = await getState(task, data);
164+
165+
if (!state.mailboxData) {
166+
log.verbose('Tasks', 'task=mailbox-retention id=%s user=%s mailbox=%s status=missing-mailbox', task._id, data.user, data.mailbox);
167+
return { processed, passes };
168+
}
169+
170+
if (state.taskRetentionCounter < state.mailboxRetentionCounter) {
171+
log.verbose(
172+
'Tasks',
173+
'task=mailbox-retention id=%s user=%s mailbox=%s status=stale taskRetentionCounter=%s mailboxRetentionCounter=%s',
174+
task._id,
175+
data.user,
176+
data.mailbox,
177+
state.taskRetentionCounter,
178+
state.mailboxRetentionCounter
179+
);
180+
return { processed, passes };
109181
}
110-
);
111182

112-
rerun = !!(taskData && taskData.updated && taskData.updated > passStart);
183+
if (state.taskRetentionCounter > activeRetentionCounter) {
184+
activeRetentionCounter = state.taskRetentionCounter;
185+
rerun = true;
186+
}
187+
}
113188
} while (rerun);
114189

115-
log.verbose('Tasks', 'task=mailbox-retention id=%s user=%s mailbox=%s processed=%s passes=%s', task._id, data.user, data.mailbox, processed, passes);
190+
log.verbose(
191+
'Tasks',
192+
'task=mailbox-retention id=%s user=%s mailbox=%s processed=%s passes=%s retentionCounter=%s',
193+
task._id,
194+
data.user,
195+
data.mailbox,
196+
processed,
197+
passes,
198+
activeRetentionCounter
199+
);
116200

117201
return { processed, passes };
118202
};

0 commit comments

Comments
 (0)