среда, 2 декабря 2020 г.

[prog.c++] SObjectizer-5 десять лет! Мой субъективный взгляд на его прошлое и настоящее

В октябре 2010-го года начались работы над SObjectizer-5. Что означает, что SObjectizer-5 развивается уже десять лет.

Немаленький срок.

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

Вот о чем пойдет речь:

Подробности под катом (букв очень много)...


Историческая ретроспектива

В 2002-ом году в компании "Интервэйл" появился внутренний инструмент SObjectizer-4 с использованием которого в 2002-2004гг было разработано и запущено несколько программных продуктов. SObjectizer-4 активно развивался где-то до 2005 года, но затем работы над ним застопорились и возникло ощущение, что достигнут потолок, было непонятно как и куда улучшать SObjectizer-4 дальше.

Тогда было решено вывести SObjectizer-4 в мир. Сперва появилась обзорная статья SObjectizer: I Love This Game!, а потом и публикация архивов с исходниками SObjectizer-а под BSD-3-Clause лицензией на SourceForge в 2006-ом году.

Пермиссивная лицензия была выбрана для того, чтобы у потенциальных пользователей не было препятствий для использования SObjectizer. "Интервэйл" на SObjectizer зарабатывать не планировал, цель была в том, чтобы снизить себестоимость владения SObjectizer-ом.

Какого-либо заметного интереса к себе SObjectizer не вызвал. В этом плане выгод от перевода SObjectizer-а в OpenSource не случилось.

Но зато стало понятно, что SObjectizer-4 действительно изжил себя и нужно делать что-то новое, чтобы избавится от недостатков SO-4. И даже появились некоторые соображения о том, куда и как следует двигаться.

А далее пришлось несколько лет ждать пока звезды выстроятся должным образом и появится возможность начать разработку SObjectizer-5. Эти годы потребовались и для того, чтобы понять что же и как именно нужно делать. И для того, чтобы собрать команду, с которой можно будет все это претворить в жизнь.

Работы над новым, пятым уже SObjectizer-ом, начались в октябре 2010-го.

Отмечу, что основную часть разработки SObjectizer-5 изначально выполнял Коля Гродзицкий. Именно он стал автором первых версий SObjectizer-5 и сопутствующих библиотек. Мое участие до 2013-го года сводилось к передаче опыта, обсуждению идей, проведению code review, ну и общему руководству.

SObjectizer-5 быстро приобрел черты пригодного к повседневному использованию инструмента и сразу же пошел в повседневную работу. Уже в 2011-ом мы сделали на SO-5 несколько компонентов, которые были запущены в боевую эксплуатацию. Часть из них, по слухам, до сих пор работает.

Изначально SObjectizer-5 задумывался как OpenSource проект. Но его публикация долго откладывалась. Не так-то просто найти время и ресурсы дабы привести в надлежащий вид внутренний инструмент, который используется каждый день для решения насущных задач. Поэтому публикация SO-5 на том же SourceForge произошла только весной 2013-го.

После моего ухода из "Интервэйл" в 2013-ом, SObjectizer-5 развивается уже как полностью OpenSource проект. Сперва он жил на SourceForge (и по прежнему там находится документация по старым версиям), затем был недолгий переезд на BitBucket, а в итоге проект приземлился на GitHub-е.

Рассказывать же о SObjectizer-е на публике мы начали еще позже, летом 2014-го. Под "мы" здесь понимается небольшая группа бывших коллег, условно называемая The SObjectizer Team.

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

С 2013-го по 2016-го года за спиной у SObjectizer-а не было никакой компании, которая могла бы брать на себя ответственность за SObjectizer. В 2016-ом я и двое моих бывших коллег создали свою маленькую фирму "StiffStream", которая и занимается на данный момент SObjectizer-ом.

Года с 2016-го мы начали пытаться делать анонсы релизов SObjectizer-а на англоязычных ресурсах, типа reddit и Hacker News. Но интереса эти анонсы не вызывали. Впрочем, о проблемах продвижения SObjectizer-а на профильных ресурсах я еще скажу пару слов ниже.

Также с 2016-го года на Habr-е было опубликовано несколько десятков статей про SObjectizer в частности и Модель Акторов в переложении на C++ вообще.

Отдельно стоит упомянуть доклады на конференциях CoreHard C++ в Минске и C++ Russia в Москве и Питере, где я пытался рассказать и об использовании акторов в C++, и о SObjectizer-е, и об опыте разработки акторного фреймворка для C++. Все это относится к 2016-2018 годам, когда тема еще была не замыленной и мне было чем поделиться не впадая в повторение одного и того же.

В 2017-ом мы начали делать сопутствующий проект so5extra, в котором накапливаются различные дополнения для SObjectizer. Изначальная идея была в том, что ядро SObjectizer распространяется бесплатно под BSD-3-Clause лицензией, а so5extra идет уже под двойной лицензией и за использование so5extra в закрытых коммерческих разработках нам будут платить. Идея себя не оправдала, so5extra не окупился, поэтому начиная с версии 1.4 so5extra также распространяется под BSD-3-Clause, т.е. практически даром :)

В общем, SObjectizer-5 поступательно развивается уже 10 лет, что лично меня приятно удивляет. Ведь запаса SObjectizer-4 хватило всего-то на 3-4 года. Тогда как SObjectizer-5 может эволюционировать еще несколько лет и насущной необходимости делать условный SObjectizer-6 на новых идеях пока не видно.


Стоил ли SObjectizer вложенных в него усилий?

На этот счет у меня однозначное мнение: SObjectizer существенно облегчает разработку некоторых типов многопоточных приложений. Это было уже проверенно неоднократно и мной лично, и моими коллегами, и совсем незнакомыми мне людьми, которые узнали про SObjectizer где-то на просторах Интернета.

Когда в распоряжении есть SObjectizer (или его аналог), то на многие вещи начинаешь смотреть иначе. Бывает, встречаешь на каком-то из профильных ресурсов обсуждение той или иной задачи и удивляешься, зачем здесь кому-то строить велосипеды, если можно взять просто готовый инструмент, реализующий обмен сообщениями между акторами и все. Вот один из совсем недавних примеров такого рода. Как бы смешно это не звучало, но когда у тебя в руках хороший молоток, то все вокруг начинает выглядеть как гвозди.

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


А не пытаюсь ли я впарить вам SObjectizer любой ценой?

Как бы это странно не звучало, но у меня уже давным-давно нет желания продать SObjectizer.

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

Для меня важно чтобы SObjectizer не пытались использовать там, где это заведомо невыгодно.

Чего нам точно не нужно, так это отзывов вида "Да говно этот ваш SObjectizer, я долго пытался на нем запилить распараллеливание вычислений, забабахался вусмерть, а потом просто взял OpenMP и у меня все взлетело".

Так что если вы сомневаетесь, брать ли SObjectizer под какую-то задачу или нет, то не постесняйтесь спросить у меня. Конфиденциальность гарантируется ;)


Почему я лично занимаюсь SObjectizer-ом столько времени?

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

Но я с упорством достойного лучшего применения продолжал вкладываться в SObjectizer. Почему?

Можно перечислить три причины в порядке возрастания их важности.

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

Во-вторых, я живу в небольшом городе и переезжать в более крупные города с большим количеством вариантов для ИТ-специалистов (вроде Москвы, Минска, Киева или Санкт-Петербурга) не хочу. А в силу специфического опыта, амбиций и склонностей особых перспектив найти интересную для себя работу наемным сотрудником в родном городе не видел. Поэтому было желание попробовать создать собственный бизнес вокруг разработки инструментария для программистов. И SObjectizer виделся как один из элементов этого собственного бизнеса.

Третьей и главной причиной было желание сделать таки доведенный до ума вариант SObjectizer-а.

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

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

В 2013-2015 годах у меня сложились условия, в которых я мог себе позволить заниматься только SObjectizer-ом. Чтобы довести этот проект до ума и избавиться от необходимости оправдываться что до каких-то фич пока что не дошли руки, что документация пока не готова, что примеры вот-вот подъедут... И я решил этим шансом воспользоваться, потому что понимал, что другого такого не будет.

Воспользовался.

Теперь SObjectizer для меня выглядит готовым программным продуктом. Без скидок на то, что времени хронически не хватало, т.к. нужно было бизнес-фичи для заказчика клепать.

Свое желание удовлетворил. Получил теплое чувство выполненного долга внутри.

Выглядит ли SObjectizer как готовый продукт или нет каждый теперь может оценить сам.


SObjectizer широко востребованным инструментом не стал

Изначально про SObjectizer мы рассказывали на русскоязычных профильных форумах вроде RSDN, LOR и sources.ru. Затем начали постить информацию о SObjectizer на reddit и Hacker News. Потом начали публиковать статьи о SObjectizer на Habr-е. Чем, в общем-то, до сих пор занимаемся и планируем заниматься дальше.

Прошло уже больше шести лет, как мы начали это делать, и можно уверенно сказать, что широкого распространения и известности SObjectizer-5 не получил. Я лично рассчитывал на большее, когда в 2014-ом мы начали "продвижение" SObjectizer-а в массы.

Думаю, что у этой неудачи есть две составляющие.

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

Во-вторых, роль C++ в современном мире уже не такая серьезная, как это было лет 10-15 назад. Думаю, что сделай мы SObjectizer для Java, C#, Go или (особенно) Rust-а, то внимание наша разработка могла бы привлечь гораздо больше. Кроме того, C++ сегментирован и не везде наши проектные решения (например, использование RTTI, динамической памяти и исключений) позволяли использовать SObjectizer. Плюс в C++ сообществе очень распространен NIH-синдром, поэтому зачем брать чужой велосипед, если можно запилить свой? Так что, полагаю, для мира C++ универсальные акторные фреймворки сейчас объективно нужны гораздо меньше, чем в 2000-х. А мы стали говорить о SObjectizer-е когда "поляна" уже была занята другими разработками (QP/C++ и CAF).


SObjectizer мейнстримом не стал и что из того?

Если рассматривать результат SObjectizer-а с точки зрения "успеха", то никакого успеха, понятное дело, нет. Широкие массы как не знали про SObjectizer, так и не знают. Как были у SObjectizer-а эпизодические внедрения, так и остались.

Но знаете что? Лично я вообще искренне удивляюсь тому, что SObjectizer вообще использует кто-то кроме нас. Серьезно.

Тут ведь как: есть какой-то eao197, хз кто это, откуда и что он из себя представляет. Так вот этот eao197 бегает по Рунету с со своей самоделкой непонятного назначения и непонятного качества. Какова вероятность, что это что-то стоящее?

Одно дело, если бы это был человек из Яндекса или Лаборатории Касперского (не говоря уже про Google, Microsoft или Amazon), который бы рассказывал о том, что используется внутри Яндекса/ЛК (не говоря уже про Google, Microsoft, Amazon). Тогда ведь совсем другое дело. Глобально и надежно. Открытый продукт от крупной компании -- это же сразу +100500 в карму.

А тут никто и звать никак. С непойми чем.

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

В общем, если сравнивать SObjectizer с QP/C++ или CAF-ом, то SObjectizer, определенно, не взлетел. И не взлетит никогда. Ну и что? Жизнь-то из-за этого не закончится...


Что я делал неправильно в продвижении SObjectizer-а?

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

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

Что ж, поехали по пунктам.

Публикация на SourceForge в Svn. Исходники в Subversion и на SourceForge -- это в 2006-ом еще могло быть уместным. В 2013-ом нужно было брать Git и публиковаться на BitBucket-е или GitHub-е (лучше бы сразу на GitHub-е).

Отсутствие поддержки CMake. Изначально в SObjectizer-е были проектные файлы только для нашей собственной системы сборки MxxRu. Поддержка CMake появилась только спустя несколько лет. И даже не нашими силами. Нужно было сразу делать поддержку CMake самим, даже несмотря на то, что в 2013-ом распространенность была пониже, чем сейчас.

Выходить с анонсами на Reddit нужно было раньше. Не нужно было ждать пока у нас появится достаточный объем документации для SObjectizer-а на английском. Анонсы на Reddit-е нужно было начинать делать еще в 2014-ом, как только мы комментарии внутри SObjectizer-а перевели на английский. А в 2016-ом было поздновато.

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

Отсутствие портфолио и невозможность рассказывать о том, что и как было сделано на базе SObjectizer-а. С самого начала была проблема: я не мог рассказывать о том, что и как было разработано на базе SObjectizer-е в "Интервэйл". Тому были свои причины, но суть в том, что я мог на свой страх и риск сказать в общих чертах, что SObjectizer-а лежал в основе шлюза SMS+USSD трафика. А также на SObjectizer-е был написан компонент обслуживания платежей за мобильную связь через банкоматы одного из крупнейших банков РФ. И что код, написанный с использованием SObjectizer-а, работал в системах, эксплуатировавшихся Сбербанком, МТС и Мегафоном. Но не более того.

В общем, получалось, что "у нас есть такие приборы, но мы вам о них не расскажем".

Да и впоследствии я никогда не заморачивался тем, чтобы просить пользователей SObjectizer-а прислать нам информацию о том, где и как они SObjectizer используют. И до сих пор не заморачиваюсь. Тут получается как с врачами. Знаю, что к таким-то и таким-то врачам нужно ходить раз в год-два просто для того, чтобы убедиться, что все нормально. Но всю жизнь ничего подобного не делаю. Так же и с портфолио.

Отсутствие открытого проекта, который бы демонстрировал возможности SObjectizer-а. Была мысль сделать на базе SObjectizer-а какой-то открытый проект, который можно было бы скачать, попробовать, посмотреть как в нем все устроено.

Не удалось найти хорошую идею для такого проекта. Была мысль написать очередной MQ-шный брокер. Например, альтернативу Paho и mosquitto для MQTT протокола. Но вместо такого брокера мы начали пилить RESTinio. Что, вероятно, было оправданным решением, т.к. RESTinio оказался востребован больше, чем SObjectizer. В общем, какого-то демо-проекта для SObjectizer-а пока не случилось.


Состояние SObjectizer-а и его перспективы

SObjectizer есть, он работает. Есть документация, есть примеры.

Если у кого-то возникают проблемы, то мы помогаем.
Если кто-то находит ошибки и открывает issue, то мы их исправляем.

Если кто-то спрашивает у меня совета по поводу SObjectizer-а, то я стараюсь ответить.

Так что с SObjectizer-ом все хорошо.

Время от времени ко мне обращаются незнакомые люди за советом: мол, взяли ваш SObjectizer, все работает, но вот как вы посоветуете сделать вот такую вот штуку? Т.е. люди разбираются с SObjectizer-ом только за счет имеющейся документации и статей, и не их не беспокоят баги или какие-либо другие проблемы.

Надеюсь, что это что-то да значит.

Но нужно понимать две вещи:

Во-первых, SObjectizer уже вобрал в себя гораздо больше того, что лично мне изначально в нем хотелось видеть. Все, что там есть появилось не на пустом месте, а потому что это кому-то было нужно. Так что SObjectizer уже наполнен полезными вещами. И добавлять в него что-то новое мы будем только когда в этом возникнет реальная необходимость. С которой либо мы сами столкнемся, либо с этим столкнется кто-то из пользователей SObjectizer-а. Если кто-то возьмет на себя труд донести до нас свои пожелания (либо в виде issue на GitHub-е, либо в частной переписке). Однако, период запихивания в SObjectizer всякой всячины уже несколько лет как закончился.

Во-вторых, сам SObjectizer не приносит нам денег напрямую. Т.е. никто извне не финансирует наши работы над SObjectizer-ом.

SObjectizer, с одной стороны, создает нам имя и репутацию. А с другой, позволяет выполнять какие-то конкретные работы для какого-то конкретного заказчика быстрее и проще (см. выше про ценность SObjectizer-а).

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

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


Брать или не брать такой SObjectizer в работу?

А почему бы и нет?

Это открытый проект с полностью доступными исходниками и пермиссивной лицензией. Мы от него не отказались, поддержка осуществляется. Реализация не сложная, при желании разобраться можно.

А то, что за SObjectizer-ом стоит какой-то никому неизвестный StiffStream, а не могущественный Google?

Ну так в наше время это мало что значит. Google замечательно выбрасывает свои разработки на мороз. И уж точно Google не будет допиливать свои открытые разработки под нужды условных "Рогов и Копыт". Как раз мелкая компания, которая отнюдь не купается в деньгах, скорее прислушается к чужим пожеланиям.

В общем, по-моему, выбор SObjectizer-а достаточно безопасен. Даже при учете риска того, что StiffStream может исчезнуть так же внезапно, как и появился. SObjectizer -- это небольшой проект с нормальным качеством кода, так что в случае форс-мажора потянуть его можно и своими силами (уверен в этом, т.к. люди берут и допиливают SObjectizer под себя даже не обращаясь к нам за помощью).

Если же кому-то нужно что-то новое в SObjectizer-е или кто-то хочет адаптированный под себя SObjectizer, то давайте пообщаемся. Кстати говоря, это касается и RESTinio ;)


Надеюсь, что я не похоронил SObjectizer, но даже если...

Если кто-то воспримет вышесказанное как анти-рекламу SObjectizer-а и подумает, что при таком раскладе с SObjectizer-ом лучше не связываться, то что ж поделать. Се ля ви.

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

В конце-концов, SObjectizer-5 развивается уже десять лет. О чем-то это да должно говорить. Наверное.


Благодарности

Хочу высказать слова искренней благодарности и отметить людей, без общения с которыми SObjectizer вряд ли приобрел бы свои современные черты. Людей, которые сделали для SObjectizer-а то, что мы сами бы и не сделали:

Дмитрий Вьюков
Лёша Сырников
Андрей Углик
Дмитрий Москальчук
Григорий Демченко
Павел Вайнерман

Отдельное спасибо организаторам конференций CoreHard C++ и C++ Russia за возможность выступить с докладами. И персонально Антону Наумовичу и Сергею Платонову.

Большое спасибо Александру Боргардту. Слова "Если вы хотите, чтобы у вас все работало, то просто берите SObjectizer" на CoreHard C++ в Минске до сих пор греют душу :)

И, конечно же, ничего бы вышеописанного не было бы, если бы не ребята, которые в разные годы работали под моим началом в "Интервэйл": Боря Сивко, Лёня Борисенко, Игорь Мирончик, Ваня Матылицкий, Лёша Стенюхин, Коля Гродзицкий, Денис Томашенко, Артур Суднеко, Коля Шмаков.

И наверняка я кого-то еще не упомянул забыл, так что прошу извинить.


Изменения, которые произошли в SObjectizer за время его развития

За период с 2013-го по 2020-й SObjectizer-5 довольно сильно изменился, прошел от версии 5.1 до 5.7. При этом было выпущено, если не ошибаюсь, больше 30 релизов. С трепетным отношением к сохранению совместимости между релизами. Серьезно ломающих совместимость релизов было всего 5 или 6 штук.

Пару лет назад на Habr-е уже была статья с рассказом о том, как менялся SObjectizer. Но там речь идет только о об изменениях в рамках ветки 5.5. Тогда как ниже не такой подробный список, как в упомянутой статье, но зато за период с 2013-го года.

Итак, вот что случилось с SObjectizer-5 за время его эволюции:

Переход только на использование исключений для информирования об ошибках. Изначально в SObjectizer-5 был выбор: выбрасывает ли метод/функция исключение об ошибке, либо же возвращается код ошибки без порождения исключения. Что-то похожее на то, что есть в Asio, но под другим соусом. В итоге, мы перешли только на использование исключений.

Разделение сообщений на сообщения (которые обязательно содержат какие-то данные) и сигналы (которые не могут содержать в себе данные в принципе). Был только тип so_5::message_t, появился еще и тип so_5::signal_t.

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

Добавлена поддержка взаимоотношений родитель-потомок для коопераций. Эта фича была в SObjectizer-4, но в SObjectizer-5 она перебралась не сразу.

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

Изменился механизм реакций на вылетающие из событий агентов исключения. Теперь есть понятие exception reaction, которое может определяться на уровне отдельного агента, или кооперации агентов, или всего SOEnv.

Появился и какое-то время просуществовал механизм синхронного взаимодействия между агентами. Начиная с версии 5.6 этот механизм в сильно измененном виде переехал в дополнительную библиотеку so5extra.

Появились и какое-то время просуществовали ad-hoc агенты, т.е. агенты, для которых не нужно было описывать отдельные C++ классы. Существенной пользы не принесли, а вот забот по мере развития SObjectizer-а добавляли. Были полностью изъяты в версии 5.6.

Изменилась схема обслуживания заявок для агентов. Изначально у каждого агента была своя очередь сообщений. А у диспетчера была очередь агентов, которые ждали своего обслуживания. При отсылке сообщения выполнялось обновление двух очередей: очереди сообщений у агента и очереди заявок у диспетчера. Затем была принята схема, в которой очередями управляет только диспетчер и никаких собственных очередей у агентов больше нет.

Понятие message box (mbox) было разделено на два типа: Multi-Producer/Multi-Consumer (MPMC) и Multi-Producer/Single-Consumer (MPSC). Изначально все mbox-ы были только MPMC, затем появились MPSC, которые более эффективно работали в сценариях 1-to-1. Затем была добавлена и возможность создавать произвольное количество MPSC mbox-ов для агента.

Расширился список штатных диспетчеров. К первоначальным one_thread, active_obj и active_group добавились thread_pool, adv_thread_pool. Плюс три диспетчера, поддерживающие приоритеты агентов. Кстати говоря, такого понятия, как приоритет агента также раньше не было, оно появилось со временем. Еще в этот список диспетчеров можно добавить диспетчеры asio_one_thread и asio_thread_pool из so5extra.

Вместе с adv_thread_pool появилось и такое понятие, как thread-safety для событий агентов. Диспетчер adv_thread_pool может одновременно запустить несколько событий одного агента, если все они помечены как thread-safe.

Изначально SObjectizer использовал библиотеку ACE в качестве базового слоя. Со временем мы от зависимости от ACE полностью избавились. В том числе и за счет самодельной системы работы с таймерами.

Механизм динамического добавления новых диспетчеров несколько раз модифицировался. Изначально диспетчеры можно было создавать только до запуска SOEnv. Затем добавилась возможность добавлять диспетчеры уже в работающем SOEnv. Потом появились private dispatchers, которые автоматически уничтожались когда их переставали использовать. В итоге в версии 5.6 сформировался унифицированный механизм диспетчеров, который используется до сих пор.

Появился механизм message_limits, который позволяет защищать агентов от перегрузок.

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

Были добавлены т.н. delivery filters, которые позволяют отфильтровать ненужные получателю сообщения по их содержимому. Фильтрация осуществляется в момент отсылки сообщения, так что если агента сообщение не интересует, то оно даже не попадет к агенту в очередь.

Для отсылки сообщений было введено семейство send-функций. Которое пережило несколько пертурбаций и теперь является единственным способом отсылки сообщений в SObjectizer.

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

Была добавлена возможность отсылки сообщений произвольных типов. Изначально все типы сообщений должны были наследоваться от SObjectizer-овского типа message_t. Теперь это уже необязательно.

Изменялись функции для запуска SObjectizer-а. Сперва были не очень удобные run_so_environment, на смену которых пришли функции launch. А потом появился и класс wrapped_env_t, который упрощает запуск SOEnv на отдельном рабочем контексте.

Появились CSP-шные каналы в виде mchain-ов. Со временем добавилась и функция select для чтения/записи сообщений в mchain, чем-то похожая на select из Go.

Агенты в SObjectizer изначально были конечными автоматами, но со временем была добавлена и поддержка продвинутых возможностей иерархических КА (за исключением ортогональных состояний).

В SObjectizer появились "мутабельные сообщения". Изначально все сообщения в SObjectizer рассматривались как константные объекты и доступ к ним получатели сообщений имели только через константные указатели/ссылки. Появление мутабельных сообщений значительно упростило некоторые сценарии взаимодействия агентов в режиме 1-to-1.

Пользователь получил возможность создавать собственные mbox-ы.

В SObjectizer было добавлено такое понятие, как stop guard. Это такой объект, который приостанавливает процесс завершения работы SOEnv до тех пор, пока какой-то агент, владеющий stop guard-ом, не выполнит до конца важные действия.

Изменился подход к представлению самого SOEnv. Если раньше была единственная реализация SOEnv, то затем в нем было выделено понятие environment infrastructure и у пользователя появилась возможность делать собственные кастомные версии environment infrastructures. Так что сейчас из коробки доступны три разные environment infrastructure: штатная многопоточная версия, однопоточная, а также использующая Asio в качестве базового слоя (эта environment infrastructure реализована в so5extra).

Появилась такая штука, как deadletter handler. Эта штука позволяет перехватывать сообщения, которые не были обработаны агентом из-за отсутствия подписки в текущем состоянии.

Появилось понятие enveloped messages. Это сделало возможным передавать вместе с сообщением какую-то сопутствующую информацию. Например, за счет "конвертов" можно теперь определить дошло ли сообщение до получателя. Или можно сделать так, чтобы сообщение не обрабатывалось, если до получателя оно дошло после какого-то времени.

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


Пара примеров кода. SObjectizer 5.1 vs SObjectizer 5.7

В этом разделе наглядно можно будет сравнить как выглядел код на SObjectizer-5.1 в 2013-ом и как этот же код выглядит на SObjectizer-5.7 в 2020-ом.

Первый пример -- это традиционный HelloWorld. Стартует агент, при старте он сам себе отсылает приветственное сообщение, получает его и завершает работу.

SO-5.1.0 SO-5.7.2
#include <iostream>

#include <so_5/rt/h/rt.hpp>
#include <so_5/api/h/api.hpp>

struct msg_hello : public so_5::rt::message_t
{
   std::string m_message;
};

class a_hello_t : public so_5::rt::agent_t
{
public:
   a_hello_t( so_5::rt::so_environment_t & env )
      : so_5::rt::agent_t( env )
      , m_self_mbox( so_environment().create_local_mbox() )
   {}

   void so_define_agent() override
   {
      so_subscribe( m_self_mbox ).event( &a_hello_t::evt_hello );
   }

   void so_evt_start() override
   {
      std::unique_ptr< msg_hello > msg( new msg_hello );
      msg->m_message = "Hello, world! This is SObjectizer v.5.";

      m_self_mbox->deliver_message( msg );
   }

private:
   so_5::rt::mbox_ref_t m_self_mbox;

   void evt_hello(
      const so_5::rt::event_data_t< msg_hello > & msg )
   {
      std::cout << msg->m_message << std::endl;

      so_environment().stop();
   }
};

void init( so_5::rt::so_environment_t & env )
{
   so_5::rt::agent_coop_unique_ptr_t coop = env.create_coop(
      so_5::rt::nonempty_name_t( "coop" ) );

   coop->add_agent( so_5::rt::agent_ref_t( new a_hello_t( env ) ) );

   env.register_coop( std::move( coop ) );
}

int main( intchar ** )
{
   try
   {
      so_5::api::run_so_environment( &init );
   }
   catchconst std::exception & ex )
   {
      std::cerr << "Error: " << ex.what() << std::endl;
      return 1;
   }

   return 0;
}
#include <iostream>

#include <so_5/all.hpp>

class a_hello_t final : public so_5::agent_t
{
public:
   using so_5::agent_t::agent_t;

   void so_define_agent() override
   {
      so_subscribe_self().event( &a_hello_t::evt_hello );
   }

   void so_evt_start() override
   {
      so_5::send< std::string >(
         *this"Hello, world! This is SObjectizer-5.");
   }

private :
   void evt_hello( const std::string & msg )
   {
      std::cout << msg << std::endl;

      so_environment().stop();
   }
};

int main()
{
   try
   {
      so_5::launch(
         []( so_5::environment_t & env )
         {
            env.register_agent_as_coop( env.make_agent< a_hello_t >() );
         } );
   }
   catchconst std::exception & ex )
   {
      std::cerr << "Error: " << ex.what() << std::endl;
      return 1;
   }

   return 0;
}

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

SO-5.1.0 SO-5.7.2
#include <iostream>
#include <sstream>

#include <ace/OS.h>
#include <ace/Log_Msg.h>

#include <so_5/rt/h/rt.hpp>
#include <so_5/api/h/api.hpp>

#include <so_5/disp/one_thread/h/pub.hpp>
#include <so_5/disp/active_group/h/pub.hpp>
#include <so_5/disp/active_obj/h/pub.hpp>

#define AGENT_MSG( s ) "TID:%t %T " s

class a_disp_user_t : public so_5::rt::agent_t
{
public:
   a_disp_user_t(
      so_5::rt::so_environment_t & env,
      const std::string & name )
      : so_5::rt::agent_t( env )
      , m_name( name )
   {}

   void so_evt_start() override
   {
      ACE_DEBUG(( LM_INFO,
         AGENT_MSG( "%s.so_evt_start(): start pause\n" ), m_name.c_str() ));

      ACE_OS::sleep( 1 );

      ACE_DEBUG(( LM_INFO,
         AGENT_MSG( "%s.so_evt_start(): finish pause\n" ), m_name.c_str() ));
   }

   void so_evt_finish() override
   {
      ACE_DEBUG(( LM_INFO,
         AGENT_MSG( "%s.so_evt_finish(): start pause\n" ), m_name.c_str() ));

      ACE_OS::sleep( 1 );

      ACE_DEBUG(( LM_INFO,
         AGENT_MSG( "%s.so_evt_finish(): finish pause\n" ), m_name.c_str() ));
   }

private:
   const std::string m_name;
};

std::string create_agent_name( const std::string & base, int i )
{
   return base + "_" + std::to_string(i);
}

void init( so_5::rt::so_environment_t & env )
{
   so_5::rt::agent_coop_unique_ptr_t coop = env.create_coop(
      so_5::rt::nonempty_name_t( "coop" ) );

   forint i = 0; i < 4; ++i )
   {
      const std::string name = create_agent_name( "default_disp", i+1 );

      coop->add_agent(
         so_5::rt::agent_ref_t(
            new a_disp_user_t( env, name ) ) );
   }

   forint i = 0; i < 3; ++i )
   {
      const std::string name = create_agent_name( "single_thread", i+1 );

      coop->add_agent(
         so_5::rt::agent_ref_t( new a_disp_user_t( env, name ) ),
         so_5::disp::one_thread::create_disp_binder(
            "single_thread" ) );
   }

   forint i = 0; i < 2; ++i )
   {
      const std::string name = create_agent_name( "active_group_A", i+1 );

      coop->add_agent(
         so_5::rt::agent_ref_t( new a_disp_user_t( env, name ) ),
         so_5::disp::active_group::create_disp_binder(
            "active_group",
            "A" ) );
   }

   forint i = 0; i < 2; ++i )
   {
      const std::string name = create_agent_name( "active_group_B", i+1 );

      coop->add_agent(
         so_5::rt::agent_ref_t( new a_disp_user_t( env, name ) ),
         so_5::disp::active_group::create_disp_binder(
            "active_group",
            "B" ) );
   }

   forint i = 0; i < 4; ++i )
   {
      const std::string name = create_agent_name( "active_obj", i+1 );

      coop->add_agent(
         so_5::rt::agent_ref_t( new a_disp_user_t( env, name ) ),
         so_5::disp::active_obj::create_disp_binder(
            "active_obj" ) );
   }

   env.register_coop( std::move( coop ) );

   env.stop();
}

int main( intchar ** argv )
{
   ACE_LOG_MSG->open (argv[0], ACE_Log_Msg::STDERR);
   try
   {
      so_5::api::run_so_environment(
         &init,
         so_5::rt::so_environment_params_t()
            .add_named_dispatcher(
               so_5::rt::nonempty_name_t( "single_thread" ),
               so_5::disp::one_thread::create_disp() )
            .add_named_dispatcher(
               so_5::rt::nonempty_name_t( "active_group" ),
               so_5::disp::active_group::create_disp() )
            .add_named_dispatcher(
               so_5::rt::nonempty_name_t( "active_obj" ),
               so_5::disp::active_obj::create_disp() ) );
   }
   catchconst std::exception & ex )
   {
      std::cerr << "Error: " << ex.what() << std::endl;
      return 1;
   }

   return 0;
}
#include <iostream>
#include <sstream>

#include <so_5/all.hpp>

class a_disp_user_t final : public so_5::agent_t
{
public:
   a_disp_user_t( context_t ctx, std::string name )
      : so_5::agent_t( ctx )
      , m_name( std::move(name) )
   {}

   void so_evt_start() override
   {
      SO_5_LOG_ERROR( so_environment(), log_stream )
      { log_stream << m_name << ".so_evt_start(): start pause"; }

      std::this_thread::sleep_for( std::chrono::seconds( 1 ) );

      SO_5_LOG_ERROR( so_environment(), log_stream )
      { log_stream << m_name << ".so_evt_start(): finish pause"; }
   }

   void so_evt_finish() override
   {
      SO_5_LOG_ERROR( so_environment(), log_stream )
      { log_stream << m_name << ".so_evt_finish(): start pause"; }

      std::this_thread::sleep_for( std::chrono::seconds( 1 ) );

      SO_5_LOG_ERROR( so_environment(), log_stream )
      { log_stream << m_name << ".so_evt_finish(): finish pause"; }
   }

private:
   const std::string m_name;
};

std::string create_agent_name( const std::string & base, int i )
{
   return base + "_" + std::to_string(i);
}

void init( so_5::environment_t & env )
{
   env.introduce_coop( []( so_5::coop_t & coop ) {
      forint i = 0; i < 4; ++i )
      {
         coop.make_agent< a_disp_user_t >(
            create_agent_name( "default_disp", i+1 ) );
      }

      {
         auto disp = so_5::disp::one_thread::make_dispatcher(
               coop.environment(), "single_thread" );
         forint i = 0; i < 3; ++i )
         {
            coop.make_agent_with_binder< a_disp_user_t >(
               disp.binder(),
               create_agent_name( "single_thread", i+1 ) );
         }
      }

      {
         auto disp = so_5::disp::active_group::make_dispatcher(
               coop.environment(), "active_group" );

         forint i = 0; i < 2; ++i )
         {
            coop.make_agent_with_binder< a_disp_user_t >(
               disp.binder( "A" ),
               create_agent_name( "active_group_A", i+1 ) );
         }

         forint i = 0; i < 2; ++i )
         {
            coop.make_agent_with_binder< a_disp_user_t >(
               disp.binder( "B" ),
               create_agent_name( "active_group_B", i+1 ) );
         }
      }

      {
         auto disp = so_5::disp::active_obj::make_dispatcher(
               coop.environment(), "active_obj" );
         forint i = 0; i < 4; ++i )
         {
            coop.make_agent_with_binder< a_disp_user_t >(
               disp.binder(),
               create_agent_name( "active_obj", i+1 ) );
         }
      }
   });

   env.stop();
}

int main()
{
   try
   {
      so_5::launch( &init );
   }
   catchconst std::exception & ex )
   {
      std::cerr << "Error: " << ex.what() << std::endl;
      return 1;
   }

   return 0;
}

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


Вместо заключения

Первые 10 лет с SObjectizer-5 пройдено. Потихоньку начнем отматывать следующие десять...

2 комментария:

XX комментирует...

Спасибо, что тратите время и силы на развитие open source!

Я так понимаю, что выстроить бизнесс вокруг SObjectizer так и не получилось, но ведь ваша компания на что-то существует? Видимо за счет частных заказов на разработку. В таком случае вопрос: планируете ли вы еще предпринимать какие-то попытки монетизации своих open source проектов? О проблемах монетизации уже была статья в этом блоге, но осталось непонятным, к какому решению вы все-таки пришли.

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

eao197 комментирует...

> Видимо за счет частных заказов на разработку.

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

> В таком случае вопрос: планируете ли вы еще предпринимать какие-то попытки монетизации своих open source проектов?

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

> Интересуюсь, потому что сам хочу построить компанию, ориентированную на разработку свободного ПО.

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