Зависание API файловой системы Chrome
Отказ от ответственности, сообщение с самоответчиком, чтобы, надеюсь, сэкономить время других.
Настройка:
Я использовал реализацию API файловых систем в Chrome, [1] [2] [3].
Для этого необходимо включить флаг chrome: // flags / #native-file-system-api.
Для начала я хочу рекурсивно прочитать каталог и получить список файлов. Это достаточно просто:
paths = [];
let recursiveRead = async (path, handle) => {
let reads = [];
// window.handle = handle;
for await (let entry of await handle.getEntries()) { // <<< HANGING
if (entry.isFile)
paths.push(path.concat(entry.name));
else if (/* check some whitelist criteria to restrict which dirs are read*/)
reads.push(recursiveRead(path.concat(entry.name), entry));
}
await Promise.all(reads);
console.log('done', path, paths.length);
};
chooseFileSystemEntries({type: 'openDirectory'}).then(handle => {
recursiveRead([], handle).then(() => {
console.log('COMPLETELY DONE', paths.length);
});
});
Я также реализовал нерекурсивную версию while-loop-queue. И, наконец, я реализовал узелfs.readdir
версия. Все 3 решения подходят для небольших каталогов.
Эта проблема:
Но затем я попытался запустить его в некоторых подкаталогах исходного кода хрома ("база", "компоненты" и "хром"); вместе 3 подкаталога содержат ~63000 файлов. Хотя реализация узла работала нормально (и, что удивительно, использовались кешированные результаты между запусками, что приводило к мгновенным запускам после первого), обе реализации браузера зависали.
Попытка отладки:
Иногда они возвращали полные 63 КБ файлов и печатали 'COMPLETLEY DONE'
как и ожидалось. Но чаще всего (в 90% случаев) они читали файлы размером 10-40 тысяч перед зависанием.
Я покопался в подвешивании и, видимо, for await
линия висела. Итак, я добавил строкуwindow.handle = handle
непосредственно перед циклом for; когда функция зависла, я запускал цикл for прямо в консоли браузера, и он работал правильно! Так что теперь я застрял. У меня вроде бы рабочий код, который случайно зависает.
1 ответ
Решение:
Я пробовал пропускать каталоги, которые зависали:
let whitelistDirs = {src: ['base', 'chrome', 'components', /*'ui'*/]}; // 63800
let readDirEntry = (handle, timeout = 500) => {
return new Promise(async (resolve, reject) => {
setTimeout(() => reject('timeout'), timeout);
let entries = [];
for await (const entry of await handle.getEntries())
entries.push(entry);
resolve(entries);
});
};
let readWhile = async entryHandle => {
let paths = [];
let pending = [{path: [], handle: entryHandle}];
while (pending.length) {
let {path, handle} = pending.pop();
await readDirEntry(handle)
.then(entries =>
entries.forEach(entry => {
if (entry.isFile)
paths.push({path: path.concat(entry.name), handle: entry});
else if (path.length || !whitelistDirs[handle.name] || whitelistDirs[handle.name].includes(entry.name))
pending.push({path: path.concat(entry.name), handle: entry});
}))
.catch(() => console.log('skipped', handle.name));
console.log('paths read:', paths.length, 'pending remaining:', pending.length, path);
}
console.log('read complete, paths.length');
return paths;
};
chooseFileSystemEntries({type: 'openDirectory'}).then(handle => {
readWhile(handle).then(() => {
console.log('COMPLETELY DONE', paths.length);
});
});
И результаты показали закономерность. После того, как чтение каталога зависло и было пропущено, последующие ~10 чтения каталога также зависнут и будут пропущены. Затем следующие считывания возобновят правильную работу до следующего подобного инцидента.
// begins skipping
paths read: 45232 pending remaining: 49 (3) ["chrome", "browser", "favicon"]
VM60:25 skipped extensions
VM60:26 paths read: 45239 pending remaining: 47 (3) ["chrome", "browser", "extensions"]
VM60:25 skipped enterprise_reporting
VM60:26 paths read: 45239 pending remaining: 46 (3) ["chrome", "browser", "enterprise_reporting"]
VM60:25 skipped engagement
VM60:26 paths read: 45266 pending remaining: 45 (3) ["chrome", "browser", "engagement"]
VM60:25 skipped drive
VM60:26 paths read: 45271 pending remaining: 44 (3) ["chrome", "browser", "drive"]
// begins working properly again
Так что проблема казалась временной. Я добавил простую оболочку повтора с ожиданием 500 мс между повторами, и чтение начало работать нормально.
readDirEntryRetry = async (handle, timeout = 500, tries = 5, waitBetweenTries = 500) => {
while (tries--) {
try {
return await readWhile(handle, timeout);
} catch (e) {
console.log('readDirEntry failed, tries remaining:', tries, handle.name);
await sleep(waitBetweenTries);
if (!tries)
return e;
}
}
};
Вывод:
Нестандартный API собственной файловой системы зависает при чтении больших каталогов. Простая повторная попытка после ожидания решит проблему. У меня ушла хорошая неделя, чтобы прийти к этому решению, поэтому подумал, что стоит поделиться.