Внезапно Одной Системы Сбора Мусора Недостаточно

Вот небольшая история о загадочных сбоях серверов, которые мне пришлось отлаживать год назад (статья от 5 декабря 2018, прим.

).

Серверы какое-то время работали нормально, а потом в какой-то момент начали глючить.

После этого попытки запустить практически любую программу, находившуюся на серверах, заканчивались ошибками «На устройстве нет места», хотя файловая система сообщала, что на дисках ~20 ГБ занято лишь несколько гигабайт. Оказалось, что проблема была вызвана системой журналирования.

Это было приложение Ruby, которое записывало файлы журналов, отправляло данные на удаленный сервер и удаляло старые файлы.

Ошибка заключалась в том, что открытые файлы журналов не были закрыты явно.

Вместо этого приложение позволяло автоматическому сборщику мусора Ruby очищать объекты File. Проблема в том, что объекты File не используют много памяти, поэтому система журналирования теоретически может сохранять открытыми миллионы журналов, прежде чем потребуется сбор мусора.

Файловые системы в *nix разделяют имена файлов и данные в файлах.

Данные на диске могут иметь несколько имен файлов, указывающих на них (т. е.

жесткие ссылки), и данные удаляются только при удалении последней ссылки.

Дескриптор открытого файла считается ссылкой, поэтому, если файл будет удален во время его чтения программой, имя файла исчезнет из каталога, но данные файла останутся активными, пока программа не закроет его.

Вот что случилось с регистратором.

Команда du («использование диска») ищет файлы, используя списки каталогов, поэтому она не видит гигабайты данных файлов для тысяч файлов журналов, которые все еще были открыты.

Эти файлы были найдены только после запуска lsof («список открытых файлов»).

Конечно, подобная ошибка встречается и в других подобных случаях.

Пару месяцев назад мне пришлось иметь дело с Java-приложением, которое через несколько дней вышло из строя из-за утечки сетевых соединений.

Раньше я писал большую часть своего кода на C, а затем на C++.

Тогда я думал, что ручного управления ресурсами достаточно.

Насколько это было сложно? Для каждой функции malloc() требуется функция free(), а для каждой функции open() — функция close().

Только.

Вот только не все программы просты, поэтому ручное управление ресурсами со временем превратилось в смирительную рубашку.

Затем однажды я открыл для себя подсчет ссылок и сборку мусора.

Я думал, что это решит все мои проблемы, и полностью перестал заботиться об управлении ресурсами.

Опять же, для простых программ это нормально, но не все программы просты.

Вы не можете полагаться на сбор мусора, поскольку он решает только проблему управления памятью, а сложным программам приходится иметь дело не только с памятью.

На это есть популярный мем, который отвечает: память — это 95% проблем с ресурсами .

Можно даже сказать, что все ресурсы — это 0% ваших проблем — пока у вас не закончится один из них.

Тогда этот ресурс станет 100% ваших проблем.

Однако такое мышление по-прежнему рассматривает ресурсы как особый случай.

Более глубокая проблема заключается в том, что по мере усложнения программ все становится ресурсом.

Например, возьмем программу-календарь.

Сложная программа календаря позволяет нескольким пользователям управлять несколькими общими календарями, а также событиями, которые можно совместно использовать в нескольких календарях.

Любая часть данных в конечном итоге повлияет на несколько частей программы и должна быть актуальной и правильной.

Следовательно, всем динамическим данным нужен владелец, а не только управление памятью.

По мере добавления новых функций потребуется обновлять и другие части программы.

Если вы в здравом уме, вы позволите обновлять данные только из одной части программы за раз, поэтому право и ответственность за обновление данных сами по себе становятся ограниченным ресурсом.

Моделирование изменяемых данных с помощью неизменяемых структур не приводит к исчезновению этих проблем, а лишь переносит их в другую парадигму.

Планирование владения и срока службы ресурсов — неизбежная часть сложного проектирования программного обеспечения.

Будет проще, если вы воспользуетесь некоторыми распространенными шаблонами.

Один из шаблонов — взаимозаменяемые ресурсы.

Примером является неизменяемая строка «foo», которая семантически такая же, как и любая другая неизменяемая строка «foo».

Этот тип ресурса не требует заранее определенного срока существования или владения.

Фактически, чтобы сделать систему максимально простой, лучше не устанавливать заранее определенный срок действия или владение.

Другая модель – это ресурсы, которые не являются взаимозаменяемыми, но имеют детерминированный срок службы.

Сюда входят сетевые подключения, а также более абстрактные концепции, такие как право контролировать часть данных.

Самое разумное — явно обеспечить срок службы таких вещей при кодировании.

Обратите внимание, что автоматическая сборка мусора действительно хороша для реализации первого шаблона, но не для второго, тогда как методы ручного управления ресурсами (такие как RAII) отлично подходят для реализации второго шаблона, но ужасны для первого.

Эти два подхода дополняют друг друга в сложных программах.

Теги: #программирование #Промышленное программирование #ups #gc #gc #надежное программирование

Вместе с данным постом часто просматривают: