Commit 03d8f4f7 authored by Joel Hockey's avatar Joel Hockey Committed by Chromium LUCI CQ

Fix removeOldItems_() while delete in progress

The inProgress_ Set was meant to stop removeOldItems_() deleting entries
which are currently in progress. For this to work properly, we must
ensure that item are not removed from the inProgress_ Set whilst
removeOldItems_() is running.

The single Set is replaced with Map<string, Set> so that each
TrashConfig is independent. Entries are only ever added to the set,
which is deleted once removeOldItems_() is run for each TrashConfig.

Bug: 953310
Change-Id: I2f07e7f3c3cf8733e3c91fade20f37d9ffc95afb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2563390
Commit-Queue: Joel Hockey <joelhockey@chromium.org>
Reviewed-by: default avatarNoel Gordon <noel@chromium.org>
Cr-Commit-Position: refs/heads/master@{#832689}
parent fedc8e9a
...@@ -32,21 +32,22 @@ class TrashItem { ...@@ -32,21 +32,22 @@ class TrashItem {
class Trash { class Trash {
constructor() { constructor() {
/** /**
* Store TrashDirs to avoid repeated lookup. * Store TrashDirs to avoid repeated lookup, keyed by TrashConfig.id.
* @private {!Object<string, !TrashDirs>} * @private {!Object<string, !TrashDirs>}
* @const * @const
*/ */
this.trashDirs_ = {}; this.trashDirs_ = {};
/** /**
* Set of in-progress deletes. Items in this list are ignored by * Set of in-progress deletes, keyed by TrashConfig.id with Set containing
* removeOldItems_(). Use getInProgressKey_() to create a globally unique * *.trashinfo filename. Items in this list are ignored by
* key. * removeOldItems_(). Each Set entry is removed once removeOldItems_() runs
* for the related TrashConfig.
* *
* @private {!Set<string>} * @private {!Map<string, Set<string>>}
* @const * @const
*/ */
this.inProgress_ = new Set(); this.inProgress_ = new Map(TrashConfig.CONFIG.map(c => [c.id, new Set()]));
} }
/** /**
...@@ -139,7 +140,10 @@ class Trash { ...@@ -139,7 +140,10 @@ class Trash {
trashDirs = await TrashDirs.getTrashDirs(entry.filesystem, config); trashDirs = await TrashDirs.getTrashDirs(entry.filesystem, config);
// Check and remove old items max once per session. // Check and remove old items max once per session.
this.removeOldItems_(trashDirs, Date.now()); this.removeOldItems_(trashDirs, config, Date.now()).then(() => {
// In-progress set is not required after removeOldItems_() runs.
this.inProgress_.delete(config.id);
});
this.trashDirs_[config.id] = trashDirs; this.trashDirs_[config.id] = trashDirs;
return trashDirs; return trashDirs;
} }
...@@ -188,16 +192,6 @@ class Trash { ...@@ -188,16 +192,6 @@ class Trash {
}); });
} }
/**
* Gets a globally unique key to use in the in-progress set.
*
* @param {!DirectoryEntry} trashInfoDir /.Trash/info directory.
* @param {string} trashInfoName name of the *.trashinfo file.
*/
getInProgressKey_(trashInfoDir, trashInfoName) {
return `${trashInfoDir.toURL()}/${trashInfoName}`;
}
/** /**
* Move a file or a directory to the trash. * Move a file or a directory to the trash.
* *
...@@ -215,12 +209,13 @@ class Trash { ...@@ -215,12 +209,13 @@ class Trash {
// Write trashinfo first, then only move file if info write succeeds. // Write trashinfo first, then only move file if info write succeeds.
// If any step fails, the file will be unchanged, and any partial trashinfo // If any step fails, the file will be unchanged, and any partial trashinfo
// file created will be cleaned up when we remove old items. // file created will be cleaned up when we remove old items.
const inProgressKey = this.getInProgressKey_(trashDirs.info, trashInfoName); const inProgress = this.inProgress_.get(config.id);
this.inProgress_.add(inProgressKey); if (inProgress) {
inProgress.add(trashInfoName);
}
const infoEntry = await this.writeTrashInfoFile_( const infoEntry = await this.writeTrashInfoFile_(
trashDirs.info, trashInfoName, config.pathPrefix + entry.fullPath); trashDirs.info, trashInfoName, config.pathPrefix + entry.fullPath);
const filesEntry = await this.moveTo_(entry, trashDirs.files, name); const filesEntry = await this.moveTo_(entry, trashDirs.files, name);
this.inProgress_.delete(inProgressKey);
return new TrashItem(entry.name, filesEntry, infoEntry, config.pathPrefix); return new TrashItem(entry.name, filesEntry, infoEntry, config.pathPrefix);
} }
...@@ -269,9 +264,10 @@ class Trash { ...@@ -269,9 +264,10 @@ class Trash {
/** /**
* Remove any items from trash older than 30d. * Remove any items from trash older than 30d.
* @param {!TrashDirs} trashDirs * @param {!TrashDirs} trashDirs
* @param {!TrashConfig} config trash config for entry.
* @param {number} now Current time in milliseconds from epoch. * @param {number} now Current time in milliseconds from epoch.
*/ */
async removeOldItems_(trashDirs, now) { async removeOldItems_(trashDirs, config, now) {
const ls = (reader) => { const ls = (reader) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
reader.readEntries(results => resolve(results), error => reject(error)); reader.readEntries(results => resolve(results), error => reject(error));
...@@ -324,8 +320,8 @@ class Trash { ...@@ -324,8 +320,8 @@ class Trash {
} }
// Ignore any in-progress files. // Ignore any in-progress files.
if (this.inProgress_.has( const inProgress = this.inProgress_.get(config.id);
this.getInProgressKey_(trashDirs.info, entry.name))) { if (inProgress && inProgress.has(entry.name)) {
console.log(`Ignoring write in progress ${entry.toURL()}`); console.log(`Ignoring write in progress ${entry.toURL()}`);
continue; continue;
} }
......
...@@ -326,7 +326,7 @@ async function testRestore(done) { ...@@ -326,7 +326,7 @@ async function testRestore(done) {
/** /**
* Test removeOldEntries_(). * Test removeOldEntries_().
* *
* @suppress {accessControls} Access removeOldItems_() and inProgress_(). * @suppress {accessControls} Access removeOldItems_() and inProgress_.
*/ */
async function testRemoveOldItems_(done) { async function testRemoveOldItems_(done) {
const trash = new Trash(); const trash = new Trash();
...@@ -343,6 +343,10 @@ async function testRemoveOldItems_(done) { ...@@ -343,6 +343,10 @@ async function testRemoveOldItems_(done) {
const file5 = MockFileEntry.create(fs, '/dir/file5', null, new Blob(['f5'])); const file5 = MockFileEntry.create(fs, '/dir/file5', null, new Blob(['f5']));
const file6 = MockFileEntry.create(fs, '/dir/file6', null, new Blob(['f6'])); const file6 = MockFileEntry.create(fs, '/dir/file6', null, new Blob(['f6']));
// Get TrashConfig.
const config = trash.shouldMoveToTrash(volumeManager, fs.root);
assert(config);
// Move files to trash. // Move files to trash.
for (const f of [file1, file2, file3, file4, file5, file6]) { for (const f of [file1, file2, file3, file4, file5, file6]) {
await trash.removeFileOrDirectory(volumeManager, f, deletePermanently); await trash.removeFileOrDirectory(volumeManager, f, deletePermanently);
...@@ -357,8 +361,7 @@ async function testRemoveOldItems_(done) { ...@@ -357,8 +361,7 @@ async function testRemoveOldItems_(done) {
// Files that are write-in-progress with no DeletionDate should be ignored. // Files that are write-in-progress with no DeletionDate should be ignored.
fs.entries['/.Trash/info/file1.trashinfo'].content = fs.entries['/.Trash/info/file1.trashinfo'].content =
new Blob(['no-deletion-date']); new Blob(['no-deletion-date']);
trash.inProgress_.add( trash.inProgress_.set('downloads-/', new Set(['file1.trashinfo']));
`${fs.entries['/.Trash/info'].toURL()}/file1.trashinfo`);
delete fs.entries['/.Trash/files/file1']; delete fs.entries['/.Trash/files/file1'];
// Files without a matching file in .Trash/files should be deleted. // Files without a matching file in .Trash/files should be deleted.
delete fs.entries['/.Trash/files/file2']; delete fs.entries['/.Trash/files/file2'];
...@@ -373,7 +376,7 @@ async function testRemoveOldItems_(done) { ...@@ -373,7 +376,7 @@ async function testRemoveOldItems_(done) {
const trashDirs = const trashDirs =
new TrashDirs(fs.entries['/.Trash/files'], fs.entries['/.Trash/info']); new TrashDirs(fs.entries['/.Trash/files'], fs.entries['/.Trash/info']);
await trash.removeOldItems_(trashDirs, now); await trash.removeOldItems_(trashDirs, config, now);
assertTrue(!!fs.entries['/']); assertTrue(!!fs.entries['/']);
assertTrue(!!fs.entries['/.Trash']); assertTrue(!!fs.entries['/.Trash']);
assertTrue(!!fs.entries['/.Trash/files']); assertTrue(!!fs.entries['/.Trash/files']);
...@@ -387,18 +390,18 @@ async function testRemoveOldItems_(done) { ...@@ -387,18 +390,18 @@ async function testRemoveOldItems_(done) {
// Items older than 30d should be deleted. // Items older than 30d should be deleted.
const daysAgo29 = now + (29 * 24 * 60 * 60 * 1000); const daysAgo29 = now + (29 * 24 * 60 * 60 * 1000);
const daysAgo31 = now + (31 * 24 * 60 * 60 * 1000); const daysAgo31 = now + (31 * 24 * 60 * 60 * 1000);
await trash.removeOldItems_(trashDirs, daysAgo29); await trash.removeOldItems_(trashDirs, config, daysAgo29);
assertEquals(8, Object.keys(fs.entries).length); assertEquals(8, Object.keys(fs.entries).length);
await trash.removeOldItems_(trashDirs, daysAgo31); await trash.removeOldItems_(trashDirs, config, daysAgo31);
assertTrue(!!fs.entries['/.Trash/info/file1.trashinfo']); assertTrue(!!fs.entries['/.Trash/info/file1.trashinfo']);
assertFalse(!!fs.entries['/.Trash/info/file5.trashinfo']); assertFalse(!!fs.entries['/.Trash/info/file5.trashinfo']);
assertFalse(!!fs.entries['/.Trash/files/file5']); assertFalse(!!fs.entries['/.Trash/files/file5']);
assertEquals(6, Object.keys(fs.entries).length); assertEquals(6, Object.keys(fs.entries).length);
trash.inProgress_.delete( // trashinfo with no matching file, and not in-progress should be deleted.
`${fs.entries['/.Trash/info'].toURL()}/file1.trashinfo`); trash.inProgress_.get('downloads-/').delete('file1.trashinfo');
await trash.removeOldItems_(trashDirs, daysAgo31); await trash.removeOldItems_(trashDirs, config, daysAgo31);
assertFalse(!!fs.entries['/.Trash/info/file1.trashinfo']); assertFalse(!!fs.entries['/.Trash/info/file1.trashinfo']);
assertEquals(5, Object.keys(fs.entries).length); assertEquals(5, Object.keys(fs.entries).length);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment