вторник, 5 ноября 2024 г.

[prog.c++] Послесловие к релизу SO-5.8.3: будет ли SO-5.9 и если будет, то когда?

Осенью 2014-го года мы выпустили первую версию в ветке 5.5. Эта ветка затем развивалась пять лет без серьезных ломающих изменений. Я бы был не против и дальше обходится без заметных переделок, но, к сожалению, версия 5.5 набрала такой груз разнообразных фич, который стало уже тяжело нести. Накопился опыт, взгляды на какие-то вещи принципиально поменялись и было решено разгрести накопившийся наслоения не всегда хорошо сочетающейся функциональности. Так в 2019-ом появилась ветка 5.6.

Cчитаю, что в течении последних пяти лет именно эта ветка и развивается. Хотя, формально, мы сделали переход от 5.6 к 5.7, а затем и к 5.8, поскольку были пусть и небольшие, но ломающие совместимость изменения. Тем не менее, различия между 5.6 и 5.8 гораздо меньше, чем между 5.5 и 5.6. Так что для меня лично 5.6/5.7/5.8 -- это развитие одной и той же линии романа и изменение номера версии всего лишь дань формализму.

Сейчас заканчивается 2024-й год и получается, что семейство 5.6/5.7/5.8 поступательно развивается уже пять лет. Вроде бы повторяется история с пятилетним циклом жизни 5.5 и пора задумываться о том, что дальше.

На данный момент, в отличии от ситуации с 5.5, я не вижу каких-то фатальных недостатков в семействе 5.6/5.7/5.8. Необходимости разгрести авгиевы конюшни пока нет. ИМХО, у 5.8 еще есть запас прочности для продолжения в том же духе.

Поэтому какой-то насущной необходимости начинать ветку 5.9 нет.

Насущной нет, но есть вопрос с освоением новых стандартов C++. И в 2025-ом нужно будет всерьез задумываться над этим вопросом.

Пока что хватает и C++17. И для библиотеки хорошо, когда она отстает от самого-самого свежего стандарта -- тем самым больше проектов могут ее использовать. Но в какой-то момент возникает вопрос: а стоит ли это "хорошо для сторонних проектов" того, что мы отказываемся от плюшек современного C++?

В 2019-ом было решено, что невыгодно дальше держаться за C++11.

Полагаю, в ближайшие полтора-два года так же невыгодно станет держаться и за C++17. Вот тогда на горизонте и появится ветка 5.9.

Это один возможный сценарий развития событий.

Но может быть и другой, хотя пока что он совсем уж фантастический. Есть у меня давнишняя мысль о том, чтобы двигать SObjectizer в направлении, когда в нем можно будет держать работу с динамической памятью под полным контролем.

Ведь сейчас в SObjectizer аллокации/деаллокации буквально на каждом шагу: за send-ом скрывается new, постановка заявки в очередь к агенту может вести к аллокациям, подписка или установка delivery filter требует аллокаций, даже смена состояния агента, если для состояния используется time_limit, требует аллокаций.

Подобное поведение автоматически ставит крест на применении SObjectizer в системах реального времени. Что иронично, т.к. SObjectizer вырос из SCADA Objectizer, создававшегося именно под реальное время. Но в этом я не вижу ничего страшного, ну нет и нет.

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

Да, это не просто. Да, придется прилагать серьезные усилия как на этапе проектирования софта, так и на этапах его разработки и тестирования. Из-за этого как раз зачастую на выживание при bad_alloc-е и не обращают внимания, у многих разработчиков есть целая мантра, что-то вроде "если нет памяти, то нет и жизни".

Прекрасно понимаю почему так, да и сам далеко не всегда готов платить за разработку дороже, если крах при bad_alloc-е вполне себе допустим.

Но что делать когда крах при bad_alloc-е ну такое себе? Вот тот же прокси-сервер с 200k одновременных подключений... Он рестартует, это OK. Вполне вероятно, что он рестартует за считанные секунды, а то и быстрее. Тогда как восстановление этих 200k соединений -- это же не быстрый процесс. Это неизбежно скажется на впечатлении пользователей от качества сервиса. Но, что хуже, у нас нет устойчивости против подобных падений в будущем. Допустим, мы принимаем 200k подключений и работаем нормально, но затем приходит одно специфическое подключение, при работе с которым мы вынуждены активно потреблять память и это ведет нас напрямую к очередному bad_alloc-у. И мы опять падаем роняя 200k соединений. А через какое-то время все повторяется вновь.

Если же поставить себе целью написать код, который может пережить bad_alloc, то придется придумать какую-то стратегию выживания. У нас должен быть набор действий по восстановлению (например, закрытие проблемного соединения и очистка связанных с ним ресурсов). И, что важно, мы должны быть уверены, что при выполнении данного набора действий у нас нет никаких динамических аллокаций (ведь каждая новая аллокация в таких условиях может вести к повторному bad_alloc-у).

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

А теперь представим, что подобное приложение пишется на SObjectizer-е и этот самый набор действий по восстановлению включает отсылку и обработку SObjectizer-овских сообщений.

К сожалению, SObjectizer пока не приспособлен для ситуаций, когда мы вынуждены обходиться набором заранее преаллоцированных объектов или должны использовать некий специфический аллокатор. Мне лично хотелось бы, чтобы SObjectizer позволял и такое. Не знаю насколько это реализуемо, но рискну предположить что все-таки реализуемо, хоть и не представляю себе всего масштаба проблемы.

Вот под добавление в SObjectizer чего-то подобного я бы начал ветку 5.9 не раздумывая прям завтра (но не сегодня, сегодня уже поздновато). Но...

...но когда это произойдет и произойдет ли вообще -- вопрос открытый. Не случилось пока того волшебного пендаля, который бы двинул меня в этом направлении. Так что поживем -- увидим.

8 комментариев:

  1. Один узел не обеспечит отказоустойчивость. Сегодня память кончилась, завтра место на диске или сам сервер откажет. Может лучше распределять нагрузку по сети узлов?

    ОтветитьУдалить
    Ответы
    1. SObjectizer не поддерживает распределенность. Поверх SObjectizer-а можно делать распределенные приложения, но это уже именно что поверх.

      Контроль и распределение нагрузки должны делаться какими-то внешними по отношению к SObjectizer средствами.

      Исчерпание памяти может быть вызвано какими-то проблемами в самой программе. Например, при обработке какого-то типа соединений память начинает расходоваться слишком быстро (причем речь не про утечки, а про то, что обработка чего-то может быть неоптимизирована по памяти). Что может привести к bad_alloc-у. Если приложение может пережить bad_alloc, то оно может и отрубить лишние соединения.

      Но для того, чтобы пережить bad_alloc, нужно a) прилагать некоторые усилия и при проектировании, и при программировании. И b) нужно использовать инструменты, которые сами способны пережить bad_alloc.

      Если говорить о десктопном софте, то представьте, что вы набираете большой документ и вставляете туда картинку на 500Mb. И у вас на машите тупо заканчивается оперативка (грубо говоря, на ноуте с 8Gb слишком большие и тяжелые документы не поредактируешь). Если редактор способен восстанавливаться от bad_alloc, он вам напишет, что не может вставить картинку. А если не может, то грохнется и вы потеряете часть своих правок. Что обидно, по меньшей мере.

      Удалить
  2. В Linux, наверное, ваш процесс все равно убьет oom-killer или его придется как-то специально настраивать/отключать. Есть ли подобные механизмы в Windows или Solaris не знаю.

    ОтветитьУдалить
    Ответы
    1. Если bad_alloc-а из-за поведения ОС не будет, то об этом можно и не беспокоится. А вот если он таки будет, то хорошо бы уметь это переживать. Тем более что те, кому надо, оверкоммит в Linux-е выключают, как и своп под Windows.

      Удалить
  3. Правильно я понимаю, что сейчас процесс, получив bad_alloc просто падает?

    ОтветитьУдалить