javascript

Как старый роутер съел 2.5 ГБ ОЗУ в моей вкладке, или cетевой инфаркт асинхронного кода

  • воскресенье, 25 января 2026 г. в 00:00:03
https://habr.com/ru/articles/988398/

Интро

Это история о том, как «кривой» роутер научил меня смотреть на память браузера иначе. Есть вкладка с ИИ-чатом, есть WebSocket/Streaming, есть обычный i5. И есть момент, когда все это превращается в кирпич: вкладка раздувается до гигабайтов, процессор залипает, страница оживает только на пару минут после перезагрузки.

Лид

Проблема оказалась не в нейросетях и не в JS. Виновник — старый домашний роутер, который не вывозил IPv6 и фрагментацию. Итог — застрявшие пакеты, нарастающий буфер в браузере и тысячи незавершенных async/await-машин в памяти.

TL;DR

- Вкладка с WebSocket раздувается из-за сетевых затыков.
- Роутер ломает MTU/IPv6, пакеты зависают, bufferedAmount растет.
- Асинхронные цепочки не завершаются и копятся в Heap.
- Фикс: MTU 1400 + отключение IPv6.
- В коде: мониторинг bufferedAmount, таймауты и AbortController.

Симптом

- Вкладка с ИИ-чатом пухнет до 1–2.5 ГБ.
- CPU уходит в 100%, интерфейс замирает.
- Перезагрузка помогает на 5 минут, дальше все повторяется.

Диагностика: спускаемся в сеть

Первый шаг — лезем не в код, а в сеть. Идем в логи роутера и видим:

- Exception: tNetTask
- Всплески потерь пакетов
- Пинг до 450 мс

Роутер Mercusys не вытягивал IPv6 и фрагментацию пакетов (MTU). В результате часть трафика «зависала» в пути. Браузер уже отправил данные, а они застряли в системных буферах.

Корень зла: как сеть убивает память

Это важный момент для понимания. В JavaScript async/await — это конечный автомат. Пока сетевой запрос не завершен, состояние машины живет в куче (Heap). Что происходит при «глючной» сети:

- Каждая отправка создает новую асинхронную цепочку.
- Ответ не приходит, цепочка не завершается.
- Состояния копятся в Heap.
- GC не может почистить память, потому что задачи не завершены.
- Task Scheduler ждет роутер, а роутер ждет чудо.

Итог: плохая сеть == утечка памяти даже в «правильном» асинхронном коде.

Лечение: грубая сила побеждает

Я сделал два радикальных шага:
1. Установил MTU 1400.
2. Полностью отключил IPv6.

После этого:
- вкладка стабилизировалась на ~200 МБ;
- роутер перестал зависать;
- сеть стала предсказуемой.

Да, это не «красивое» решение. Но оно спасло систему и вернуло контроль.

Минимальная защита в коде: bufferedAmount

Ниже — «сетевой предохранитель». Этот кусок должен быть в любом JS-приложении,
которое использует WebSocket или стриминг. Он проверяет, сколько данных уже
застряло в буфере браузера, и не позволяет бесконтрольно плодить задачи.

/**
 * Функция безопасной отправки данных в WebSocket.
 * Предотвращает раздувание памяти (Heap) при сетевых задержках.
 */

function safeSend(socket, data) {
    const MAX_BUFFER = 1024 * 1024; // 1 МБ — критический порог затора
    if (socket.readyState === WebSocket.OPEN) {
        // Проверяем, сколько данных застряло в буфере браузера
        if (socket.bufferedAmount > MAX_BUFFER) {
            console.warn('Network congestion detected: skipping packet to save RAM.');
            // Здесь можно реализовать логику пропуска кадров (LIFO)
            // или уведомление пользователя "Проблемы с сетью"
            return false;
        }
       try {
            socket.send(data);
            return true;
        } catch (e) {
            console.error('Send failed:', e);
            return false;
        }
    }
    return false;
}

// Пример использования в цикле (например, отправка весов или логов)
// Если safeSend вернул false — мы не плодим новые async-задачи и ждем

Почему это критично:

- bufferedAmount показывает, сколько данных уже ушло из JS, но не улетело в сеть.
- Если роутер завис на 30 секунд, буфер вырастет до гигабайтов.
- Этот if режет каскад и спасает память пользователя.

Мораль для разработчиков

- Следите за bufferedAmount в сокетах.
- Используйте AbortController и принудительные таймауты.
- Не рассчитывайте на «вечный» TCP-коннект.

Плохая сеть — это не частный случай. Это нормальное состояние мира. И если
мы не защищаем асинхронный код от сетевого хаоса, то наш код ломает машины
пользователей. Даже если он «правильный».