Управление памятью в noBackend js apps
Apr. 8th, 2015 09:54 pm![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Мы все привыкли, что в javascript-коде, особенно клиентском, можно особо насчёт RAM не париться. Автоматические сборщики мусора уже очень хороши во всех браузерах и прекрасно переваривают даже существенно корявый код и сложно зацикленные указатели.
Но есть одно исключение, когда памятью в js нужно управлять прямо – то-есть делать allocate-deallocate кодом, а не полагаться на автомат. В полный рост это исключение проявляется в noBackend приложениях.
Жизнь без пермалинков
Дело в том, что для бинари-ресурсов, хранимых локально в браузере, не существует пермалинков. То-есть, если надо вставить картинку, то src="http://url.com/image.jpg" написать не получится – нет у картинок урла.
И у документов для скачивания нет. И у скриптов. Проблема разрешима двумя путями:
1. Вставлять в атрибут src полный base64 картинки. Так, например, сделано в http://cloudwall.me/os/docs/app.html – картинки заинлайнены. Это хорошо для единичных картинок и называется data URL (RFC 2397). Что-то длинное (десятки мегабайт) так заинлайнить не получится – можно обвалить DOM.
2. Можно загружать картинки из БД как иммутабельный Blob и делать из блобов objectURL – специальный сессионный URL, который живёт до перезагрузки страницы, и который как-бы шорткат на реальные данные в блобе. Это хорошо для повторяющихся картинок – иконки там, то-сё. Ещё так можно давать ссылки для скачивания на большие куски данных – десятки мегабайт вполне ок.
Выглядят такие ссылки примерно так blob:http%3A//jquerymy.com/96e56d1d-e76d-41a8-8c7a-a77035eb95d4, живьём посмотреть можно тут http://jquerymy.com/api.html#CW-inlined-re.
Если перезагрузить страницу с примером, ссылка поменяется.
Чем же страшен objectURL?
С блобами и objectURL всё не так просто. Разумно было бы предположить, что раз Blob штука иммутабельная – то и objectURL из этого блоба должен получаться одинаковый, если мы его в рамках одной сессии попросим несколько раз сгенерить из одних и тех же данных.
А вот фиг.
УРЛ каждый раз будет разный, даже в пределах одной сессии. Более того, браузер продублирует исходные данные столько раз, сколько мы попросили сделать урл из блоба. Блоб то может под garbage collect-ом невзначай умереть – а ссылка должна жить, и данные под ней тоже.
В js-мире objectURL – единственная сущность, которая имеет deallocate. Во избежание утечек памяти мы должны явно освободить УРЛ, как только он стал не нужен.
Как детектить мусор?
Главный вопрос – определить момент, когда ресурс, занимающий память, и сессионый УРЛ для него, действительно стали больше не нужны.
Доверять приложениям самим освобождать ресурсы ненадёжно – приложение может по дороге помереть, а ресурс останется занятым.
Стало быть, это должна делать ОС.
Раз основной (практически – почти единственный) источник появления таких УРЛов – файловые аттачи в локальной БД, значит надо централизовать их чтение и генерацию сессионных УРЛов.
То-есть, мы освобождаем память, как только закрылся последний экземпляр приложения, читавшего этот аттач к документу. Выделяем память, соответственно, когда аттач читается впервые.
В CloudWall спецом для этого сделан метод для чтения аттачей, который доступен внутри любого приложения.
Отвлечённое соображение
Вообще, полное отсутствие в модных фреймворках типа React+Flux даже намёков на понимание существования проблемы делает их, по-хорошему, неюзабельными для offline/noBackend мира.
То-есть, как только мы пробуем сделать что-то с помощью React, что будет self-contained приложением для noBackend мира, мы практически сразу наталкиваемся на невероятный геморрой и отсутствие тулсетов.
Вариантов всего два. Либо мы должны брать PhoneGap и делать из сайта полноценное приложение под Андроид (иОС, МакОС) – чтобы засунуть туда все ресурсы. Либо расчитывать, что мы во время работы будем онлайн.
$.my + cloudwall, напротив, позволяют вообще по этому поводу не париться. В cloudwall codebase управление памятью кста – всего 150 строк.
---
ЗЫ. Есть ещё один случай, когда надо “руками” allocate/deallocate делать – как ни странно, это нужно для управления CSS-стилями, локальными для приложений, – но про это в другой раз.