tgoop.com/super_oleg_dev/218
Create:
Last Update:
Last Update:
Привет!
Продолжая кейс с высоким потреблением памяти, обнаружили таки утечку в приложении, и она довольно интересная.
Есть такая либа day.js, и ее система плагинов - https://day.js.org/docs/en/plugin/plugin
В этой документации рекомендованный подход к расширению - перезапись прототипа класса dayjs:
// overriding existing API
// e.g. extend dayjs().format()
const oldFormat = dayjsClass.prototype.format
dayjsClass.prototype.format = function(arguments) {
// original format result
const result = oldFormat.bind(this)(arguments)
// return modified result
}
У приложения - своя обертка над day.js где как раз есть такой плагин, перезаписывающий метод прототипа.
Далее, что мы имеем:
- эта обертка и сам day.js вынесены в shared Module Federation чанк
- хостовое приложение поставляет этот чанк
- в приложении загружается код микрофронта, который использует этот чанк
Код микрофронта имеет такой жизненный цикл на сервере:
- загружаем код микрофронта как строку
- компилируем строку и получаем JS модуль с экспортами
- сохраняем этот модуль в LRU кэш (микрофронтов может быть много, новые версии появляются, старые становятся не нужны)
- инициализируем Module Federation
Проблема - код микрофронта и его скомпилированный результат многократно дублируются в памяти, при этом прослеживается очень длинный трейс - ссылка на метод dayjs.
После долгой отладки мы обнаружили, что:
- код микрофронта в какой-то момент вытесняется из кэша и загружается заново
- код микрофронта инициализируется и вызывает shared модуль - обертку над day.js, что бы получить его экспорты
- shared модуль переиспользуется тот же самый, но заново выполняется код плагина через
dayjs.extend(plugin)
Только на этом вызове, метод в прототипе
const oldFormat = dayjsClass.prototype.format
- это уже тот метод, который один раз мы заманкипатчили на предыдущем вызове плагина!В итоге на каждое вытеснение микрофронта из кэша мы получаем матрешку перезаписи метода в прототипе, если визуализировать это в псевдокоде:
dayjsClass.prototype.format = myFnc
- dayjsClass.prototype.format = myFnc(myFnc)
- dayjsClass.prototype.format = myFnc(myFnc(myFnc))
- dayjsClass.prototype.format = myFnc(myFnc(myFnc(myFnc)))
...
Наглядный кейс про сайд-эффекты, shared зависимости и синглтоны.
Что осталось не до конца понятным - как именно утекает ссылка на исходники микрофронта в контекст условной m
yFnc
в коде shared чанка.BY SuperOleg dev notes
Share with your friend now:
tgoop.com/super_oleg_dev/218