воскресенье, 9 апреля 2017 г.

[prog] На тему сложности проектирования: внезапная проблема в попытке подружить unique-сообщения и message chains

Продолжение темы добавления в SO-5 такой штуки, как unique-сообщения. Unique-сообщение -- это сообщение, которое:

  • идет строго одному получателю (т.е. используется исключительно при взаимодействии 1:1);
  • дает получателю право менять экземпляр сообщения как ему вздумается (это нужно для ситуаций, когда сообщение большое и его нужно править "по месту", либо же когда в сообщении перемещаются move-only данные, которые из сообщения нужно забрать, например, в сообщении передается экземпляр типа File или unique_ptr).

Для того, чтобы unique-сообщения можно было отсылать агентам, в SO-5.5.19 добавляется новый тип почтового ящика -- unique_mbox. Только при подписке на unique_mbox можно повесить обработчик для unique-сообщения. Таким образом еще в компайл-тайм обеспечивается гарантия того, что нельзя взять shared-сообщение из обычного mbox-а, изменить это сообщение и отправить измененный экземпляр другому агенту.

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

Мне показалось разумным, что периодическое сообщение не может быть unique-сообщением. Поскольку:

  • получатель сообщения не является единоличным владельцем указателя на экземпляр сообщения. Еще один указатель есть у таймера. Соответственно, может получиться ситуация, когда получатель в момент времени T обрабатывает экземпляр периодического сообщения M (которое ему было отослано таймером некоторое время назад, но долго простояло в очереди), а в этот же момент таймер ставит в очередь получателя этот же самый экземпляр сообщения еще раз. Получится, что получаетль будет править объект, который находится у него в очереди. Что, может и не страшно, но как-то странно, по меньшей мере;
  • получатель сообщения не имеет препятствий к тому, чтобы переслать этот экземпляр unique-сообщения кому-то другому. Но, в этом случае сообщение в прямом смысле перестает быть unique-сообщением, т.к. оно в буквально начинает адресоваться сразу двум получателям. Первый -- это исходный получатель, которому периодическую доставку сообщения осуществляет таймер. Второй -- это тот, кому первый получатель переадресовал сообщение.

Поэтому на уровне API в SO-5.5.19 отсылка периодических сообщений в unique_mbox сейчас просто запрещена. И это правильно, как мне думается.

Теперь пришло время подружить unique-сообщения с message chains (mchains в терминологии SO-5). Mchains -- это реализация концепции каналов из модели CSP. При этом сообщения из mchain-ов всегда доставляются только одному получателю, этим mchain-ы принципиально отличаются от multi-producer/multi-consumer mbox-ов.

Казалось бы, раз mchain -- это всегда multi-producer/single-consumer канал, то нет смысла создавать дихотомию shared_mchain (или обычный mchain) и unique_mchain. Можно просто разрешить пользователю отсылать и получать unique-сообщения в обычный mchain. Т.е. можно просто выполнить send<Msg>(mchain,params) и получить этот Msg через unique_mhood_t<Msg> (т.е. получить сообщение как unique-сообщение).

Однако, здесь встречаются грабли, связанные с периодическими сообщениями. Сейчас можно отослать периодическое сообщение в mchain. Но, если любое сообщение из mchain можно получить через unique_mhood_t (т.е. как unique-сообщение), то значит и периодическое сообщение так же может быть извлечено как unique-сообщение. Что не есть хорошо (см. две вышеобозначенные причины).

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

Но тогда возникает вполне резонный вопрос: а как объяснять пользователю, зачем нужны mchain и unique_mchain, если по сути это одно и то же? И есть подозрение, что объяснить будет не так-то просто. А раз так, то пользователю будет сложнее использовать SO-5, а это не есть хорошо.


Возможно, подходить к проблеме unique-сообщений нужно было по-другому. Сейчас описанные выше сложности возникают из-за того, что исходная отсылка shared-сообщения и unique-сообщения ничем не отличается. Соответственно, когда в очередь агента или в буфер mchain-а стал объект типа Msg, то при вызове обработчика этого сообщения есть только объект Msg, но нет информации о том, отсылался ли этот объект как shared-сообщение или как unique-сообщение. Отсюда и возникающие сложности.

Можно было бы пойти другим путем. Например, заставить пользователя для отсылки unique-сообщений использовать специальную шаблонную обертку, вроде unique_msg<Msg>. Соответственно, не потребовалось бы делить mbox-ы на обычные mbox-ы и unique_mbox-ы. Не пришлось бы думать над тем, нужно ли делить mchain-ы на обычные mchain-ы и unique_mchain-ы. Запрет отсылки периодических unique-сообщений решался бы не по типу получателя (mbox/unique_mbox или mchain/unique_mchain), а по типу сообщения -- если отсылается unique_msg<Msg>, значит запрет.

Однако, такой подход так же имеет свои слабые стороны:

  • попытку отослать unique-сообщение в multi-producer/multi-consumer mbox можно было бы обнаружить только в run-time. А ошибка в run-time гораздо хуже, чем ошибка в compile-time;
  • усложнилось бы написание шаблонного кода. Допустим, сейчас можно написать шаблонную функцию make_and_send_msg, которая будет конструировать какое-то прикладное сообщение и отсылать его в указанный канал или mbox. При наличии шаблона-обертки unique_msg<Msg> мы уже не сможем просто так использовать эту функцию для отсылки как shared-, так и unique-сообщений.

Поэтому-то сейчас и был использован подход на базе дихотомии mbox/unique_mbox, mchain/unique_mchain.


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

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

Комментариев нет: