Как стать автором
Обновить
88
5.2
Евгений Охотников @eao197

Велосипедостроитель, программист-камикадзе

Отправить сообщение
Тут я чего-то не понимаю. Есть некий service (например, пул коннекшенов к БД), есть service consumer (тот, кому нужен коннекшен из пула) и есть service producer (тот, кто владеет пулом и выдает коннекшены consumer-ам по запросу). Типизация интерфейса взаимодействия между service consumer-ом и service producer-ом нужна для того, чтобы producer был уверен, что к нему обращаются только с сообщениями acquire и release, а consumer был уверен, что в ответ он получает take или failure.

Если с течением времени происходит изменение логики работы producer-а (он разбивается на группу акторов), то тогда есть два варианта:

1. Это сказывается на интерфейсе взаимодействия. Т.е. вместо acquire и release появляются другие запросы. Это элементарная ситуация, она отлавливается самим компилятором.

2. Интерфейс взаимодействия не меняется (т.е. остаются acquire и release), но теперь consumer-у нужно взаимодействовать с одним актором для выполнения acquire и с другим актором для выполнения release.

Мне не кажется, что проблемы второго случая имеют отношение к достоинствам или недостаткам модели акторов. И что преодоление таких проблем должно контролироваться системой типов и проверяться в compile-time. Ибо здесь, по сути, мы имеем проблемы с владением actor_reference, а не проблемы типов сообщений.
Я прочел. Но думал, может у вас дополнительная информация есть.

На счет поведения: походу, во втором документе они сами пришли к выводу, что это статически непроверяемо. Да и вообще вряд ли нужно. Особенно, если на актора смотреть как на конечный автомат: внешний наблюдатель знает только общий список входящих сообщений для КА, но не должен иметь представления о том, какие сообщения актуальны для КА в его текущем состоянии. Тем более, если брать в рассмотрение иерархические КА, в которых реакция на входящее сообщение может наследоваться из родительского состояния.

По поводу ответов тут вроде как все тривиально: типизированные почтовые ящики/каналы в качестве получателя ответа успешно закрывают эту тему.
Видимо, у нас с этим не было проблем как раз потому, что сообщения у нас, обычно принадлежат актору-получателю (как показано здесь, вроде file_agent::write_data). И когда происходит рефакторинг актора, то меняется список его вложенных типов (вроде того, что из класса file_agent уходит тип write_data). Посему при перекомпиляции мы сразу получаем от компилятора все места, где использовалось старое имя.
Боюсь, в предыдущем ответе я не очень понятно пошутил.
Речь вот о чем: вы можете сделать сложное приложение с использованием Модели Акторов, которое будет состоять из нескольких самостоятельных процессов, взаимодействующих через IPC. Каждый процесс будет, фактически, представлять из себя актора.

По сути, это будет то же самое, что происходит сейчас в Erlang-е. Только в Erlang-е независимые процессы работают в рамках VM (если речь про одну VM на одной ноде). А тут независимые процессы будут работать в рамках ОС.

Я же хотел подчеркнуть, что для такого подхода, наверное, не потребуется чего-то специализированного. Только какой-то IPC, который будет выполнять роль «почтовых ящиков». Ну а супервизоры делаются внешними средствами (как это и происходит с использованием того же runit-а в Unix-а).
Мысль совершенно правильная, если целью является достижение высокой отказоустойчивости. Только вот в этом случае Модель Акторов как бы и не нужна :) Подобные подходы используются давным давно, наверное еще и до появления Модели Акторов. Все, что здесь нужно — это удобные и эффективные механизмы IPC. Плюс супервизор вроде runit-а.
смена поведения, типизированный отправитель обрабатываемого сообщения

А в чем суть этих нюансов? Хотя бы в двух словах? Ну или ссылку на какое-то описание. Реально поможет лучше разобраться.
Проблема conan-ов, vcpkg, CPM и др. подобных вещей в том, что им требуется централизованный репозиторий пакетов или репозиторий с метаописаниями пакетов. В условиях, когда нет одного доминирующего де-факто стандарта на управление зависимостями это неудобно. Плюс к тому, на Linux-ах разработчики используют зачастую штатные менеджеры пакетов и в сторону внешних инструментов, вроде conan-а или cpm-а смотрят неодобрительно.

Имхо, удобнее, когда управление зависимостями делается без необходимости куда-то загружать пакет или его описание. Например, когда зависимости разруливаются через ExternalProject_Add в CMake. Но только в более человеческом виде :) Используем нечто подобное уже около года — очень удобно, по крайней мере для подключения OpenSource проектов, которые можно забирать с github, bitbucket или sourceforge.
И в процессе этого дела я не представляю как можно пропустить что актор не обрабатывает нужное сообщение…

Думаю, что основная проблема не в этом. Допустим, у нас есть актор A, который обрабатывал сообщения x, y и z. После рефакторинга он стал обрабатывать сообщения x, v и w. Соответственно, тестами мы проверим, что актор A эти сообщения обрабатываются. Ну и кроме как тестами мы это никак не проверим.

Но, в большой программе с актором A могут взаимодействовать акторы B, C и D. Полагаю, проблема, с которой столкнулся PHmaster в том, что нужно вручную проверять, отсылают ли B, C и D сообщения y и z. Если отсылают, то нужно править код. И тут вопрос упирается в количество и качество интеграционных тестов.

Еще хуже ситуация может быть, если какой-то актор N в run-time получает ссылку на актора, которому можно отослать сообщение y. И заранее нельзя сказать, будет ли это актор A или актор AA. После рефакторинга актора A актор N продолжит успешно взаимодействовать с актором AA, но у него возникнет облом с новым актором A. Но это может выясниться спустя несколько месяцев после рефакторинга.

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

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

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

У нас зачастую типы сообщений были вложенными типами для агентов. Т.е., у нас было что-то вроде (пример грубый, просто для иллюстрации):
class file_agent {
public :
  // Сообщения, которыми оперирует агент.
  struct write_data {...};
  struct loaded_data {...};
  ...
};
Соответственно, все взаимодействие шло через имена вида file_agent::write_data и file_agent::loaded_data.

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

Тем не менее, если у пользователей акторных фремворков есть желание работать с типизированными интерфейсами и иметь контроль типов в compile time, то об этом нужно задуматься. Вот, например, что-то подобное вас бы устроило? Или этого недостаточно и хочется чего-то еще более строгого?
Это понятно. Вопрос скорее был вызван тем, чтобы понять, что может устроить пользователя акторного фреймворка, а что нет.
Все не так просто. В двух словах: такое наследование не будет проблемой, если нет удаления наследника через указатель на базу. Что, вообще-то говоря, нужно бывает далеко не всегда. Плюс, в современном C++ тот же shared_ptr временами может закрывать и эту проблему.
Язык, фреймворк и психическая адекватность программиста.

Когда речь заходит о гарантиях во время компиляции, все, что касается самого программиста, учитывать не стоит. Единственная гарантия, которую может дать фреймворк — это выброс исключения при попытке приведения нетипизированной ссылки на удаленного актора (грубо говоря actor_reference<>) к типизированной (грубо говоря, к actor_reference<Mgs1, Msg2, Msg3>). Вас устраивает, если на этом этапа в run-time вы получаете исключение (ведь нет никаких гарантий в compile-time, что каст всегда пройдет успешно)?
Понятно, спасибо за подробности.

Правильно ли я понимаю, что этот опыт был получен на основе использования Akka в Scala/Java?
А кто гарантирует, что реальный актор, который стоит за «фасадом», действительно поддерживает интерфейс «фасада»?
В принципе, множество примеров можно найти в Google поискав по «Erlang success stories» или «Akka success stories». На сайте продавцов Akka даже целый раздел есть (правда там в основном околомаркетинговый булшит).

Из чего-то более толкового можно посмотреть вот это: How To Make An Infinitely Scalable Relational Database Management System (RDBMS)
Или вот это: REST Commander: Scalable Web Server Management and Monitoring.

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

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

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

Тут выше уже говорили про такой аспект, как распределенность. Жизнь показала, что жестко специфицированные интерфейсы в распределенных системах (те же самые CORBA и DCOM) отнюдь не так хороши, как жесткотипизированное взаимодействие в рамках одного процесса. Например, сложности с расширением интерфейсов со временем. Тогда как взаимодействие систем на основе обмена сообщениями (причем с поддержкой разных версий) оказывается удобнее и гибче.
Я говорю о том, что нетипизированные акторы в статически типизированном языке лишают меня тех гарантий, которые дает мне этот язык.

Статическая типизация никуда не девается. Вы вешаете обработчик на сообщение типа Msg1 и в этот обработчик приходит только сообщение типа Msg1. Сообщения других типов в ваш обработчик не попадут.

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

Все не так однозначно.

Во-первых, кому попало все равно можно отправить. Если вы отсылаете сообщения акторам A и B, и по ошибке отсылаете сообщение актору A вместо актора B, то вы все равно ошибаетесь с адресатом.

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

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

Информация

В рейтинге
967-й
Откуда
Гомель, Гомельская обл., Беларусь
Зарегистрирован
Активность