Зависание 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 собственной файловой системы зависает при чтении больших каталогов. Простая повторная попытка после ожидания решит проблему. У меня ушла хорошая неделя, чтобы прийти к этому решению, поэтому подумал, что стоит поделиться.

Другие вопросы по тегам