Javascript-форум (https://javascript.ru/forum/)
-   Events/DOM/Window (https://javascript.ru/forum/events/)
-   -   Переполнение памяти при работе с дерними окнами (https://javascript.ru/forum/events/83052-perepolnenie-pamyati-pri-rabote-s-dernimi-oknami.html)

Azazaza 03.09.2021 10:27

Переполнение памяти при работе с дерними окнами
 
Здравствуйте. У меня задача - пройти по ссылкам на странице, и каждую ссылку открыть в новом дочернем окне, провести в нем определенные действия, вернуть результат выполнения в родительское окно и закрыть дочернее.

Я написал функцию, которая вызывается в цикле. В нее передаются URL из ссылки и дейтсвие, которое необходимо выполнить в дочернем окне. Она открывает новое дочернее окно, и возвращает Promise - ждет пока дочернее окно выполнит свою задачу.

/**
 * Функция выполняет определенное действие на дочерней странице.
 * Ожидается объект с результатом выполнения
 * 
 * @param url URL дочерней страницы
 * @param action Действие. которое требуется произвести
 * @param timeout Максимальное время ожидание результата
 * @returns {Promise}
 */
window.promiseDoActionChildWindow = function (url, action, timeout)
{
    return new Promise(async (resolve, reject) =>
    {
        // Формирование URL
        url = new URL(url);
        url.searchParams.set('helper_action', action);
        // Открытие нового дочернего окна
        window.childWindow = window.open(url);

        // Функция, которая вызывается из дочернего окна после выполнения работ на ней
        window.closeChildWindow = function (result)
        {
            clearTimeout(window.actionTimeout);
            window.childWindow.close();
            if (result.status === 'ok')
            {
                resolve(result);
            }
            else
            {
                reject(result);
            }
        }

        // Принудительное закрытие дочернего окна по таймеру
        window.actionTimeout = setTimeout(function ()
        {
            window.childWindow.close();
            reject({
                status: 'error',
                message: 'Окно закрыто по таймеру.',
                url: url,
                action: action
            });
        }, timeout || 10000);

    });
}


После выполнения действия в дочернем окне - из него вызывается инициализированная в функции promiseDoActionChildWindow() функция closeChildWindow().

window.opener.closeChildWindow(result);


Такой способ реализации работает, но на каждой итерации сжирает плюс примерно 30Мб оперативной памяти, а итераций очень много. Я пробовал делать интервал в 10 секунд между итерациями, что бы браузер успевал почистить память, но результат тот-же.

Еще безуспешно пробовал обнулять ссылки так:

window.promiseDoActionChildWindow= function (url, action, timeout)
{
    return new Promise(async (resolve, reject) =>
    {
        url = new URL(url);
        url.searchParams.set('helper_action', action);
        window.childWindow = window.open(url);

        window.closeChildWindow = function (result)
        {
            window.closeChildWindow = undefined;
            clearTimeout(window.actionTimeout);
            window.actionTimeout = null;
            window.childWindow.close();
            window.childWindow = null;
            if (result.status === 'ok')
            {
                resolve(result);
            }
            else
            {
                reject(result);
            }
        }

        window.actionTimeout = setTimeout(function ()
        {
            window.closeChildWindow = undefined;
            window.childWindow.close();
            window.childWindow = null;
            reject({
                status: 'error',
                message: 'Окно закрыто по таймеру.',
                url: url,
                action: action
            });
        }, timeout || 10000);

    });
}


Помогите разобраться, в чем проблема.

voraa 03.09.2021 10:59

А покажите цикл в котором Вы вызываете promiseDoActionChildWindow

(И зачем async в return new Promise(async (... ?)

Azazaza 04.09.2021 06:17

voraa, благодарю за внимание. "async" там действительно не к месту, был нужен в свое время, но потом забыл убрать - увяряю Вас, это не причина проблемы, с которой я столкнулся.

Вот сам цикл:
async function iterateChannels()
    {
        let item = document.querySelector(SELECTOR_CHANNEL + ':nth-child(1)')
        while (item)
        {
            try
            {
                let result = await window.promiseDoActionChildWindow(item.href, 'search_id');
                item = item.nextSibling;
                console.log(result.status, result);
            }
            catch (result)
            {
                console.error(result.status, result);
                console.log('Повторная попытка.');
            }
            await delay(10000); // Просто задержка на 10 секунд
        }
    }

Aetae 04.09.2021 08:23

Непонятно когда вызывается closeChildWindow, ну да ладно, есть очевидная причина: console.log. Консоль точно также хранит ссылку на каждый логированный объект.)
Так если не хотите впустую тратить память - не логируйте ничего жирного, особенно в больших циклах - это перманентная трата памяти до console.clear().)

voraa 04.09.2021 15:19

Мне кажется, что это проблема не скрипта, а браузера. Они по разному обращаются с памятью. Я провел некоторые тесты, открывающие и закрывающие последовательно 50 окон.
У Chrome действительно занятая память все время возрастает. Спустя некоторое время после последнего окна, он освобождает какую то часть памяти, но не до первоначального значения. Похоже он считает, что раз ему эта память понадобилась разок, то можно ее придержать.

А у Firefox она прыгает, то растет, то уменьшается

voraa 04.09.2021 15:21

Цитата:

Сообщение от Aetae
Непонятно когда вызывается closeChildWindow,

Открытое окно вызывает
window.opener.closeChildWindow(result);

voraa 04.09.2021 15:34

Цитата:

Сообщение от Aetae
Так если не хотите впустую тратить память - не логируйте ничего жирного,

Круто! Действительно в моих тестах после удаления логов память расти перестала.

voraa 04.09.2021 16:19

Ho тем не менее проблемы утечки памяти тут все равно остаются.

Открытое окно делает
window.opener.closeChildWindow(result);

result - объект. Но это объект из окружения этого окна. Он имеет ссылки на другие объекты этого окружения. Хотя бы на Object из этого окружения.
И пока ссылки на него или его части будут существовать в главном окне, все это окружение (или его значительная часть) будет оставаться в памяти.
Тут все зависит от того, какая работа ведется в главном окне. Можно ли корректно скопировать все в своем окружении и очистить эти ссылки.

Если result содержит поля только примитивных типов, или другие объекты с полями примитивных типов, то можно сделать
window.closeChildWindow = function (result)
        {
             result = JSON.parse(JSON.stringify(result))
......
         }

Aetae 04.09.2021 22:39

Кстати в последние версии браузеров WeakRef подъехал, осталось консоль научить с ним прозрачно работать и совесем хорошо будет.)

Azazaza 06.09.2021 23:50

Ура проблема решена))))
Aetae, благодарю за наводку про console.log. :thanks:
voraa, огромное спасибо за активную помощь. :thanks:

Сделал именно так:

Цитата:

Сообщение от voraa (Сообщение 539985)
window.closeChildWindow = function (result)
        {
             result = JSON.parse(JSON.stringify(result))
......
         }



Часовой пояс GMT +3, время: 06:10.