О построении многопоточных серверов с использованием libMary на примере
MomentVideo.

Основные вопросы:
* Синхронизация обработки событий;
* Синхронизация при исполнении побочных эффектов (отправка данных в другие
  соединения);
* Порядок деинициализации и сборка мусора.

1. Синхронизация.

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

Чтобы упростить реализацию классов, обслуживающих соединения и разбирающих
сетевые протоколы (RTMP, HTTP), гарантируем, что все вызовы обработчиков событий
ввода/вывода для одного и того же соединения синхронизированы. То есть в самих
обработчиках никакой дополнительной синхронизации не требуется.

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

Рассмотрим подробно цепочки обработки протоколов RTMP и RTMPT.

=== Обслуживание RTMP ===

	EpollPollGroup
	       |
	       v       OutputFrontend
	 TcpConnection----------------------+
	       | InputFrontend              |
	       v                            v
       ConnectionReceiver    +--->DeferredConnectionSender
	       |             |
	       v             |
         RtmpConnection------+  <=== ТОЧКА СИНХРОНИЗАЦИИ
               |  ^  ^
	       |  |  |
	       |  |  +----------------+
	       |  |                   |
               |  +-----RtmpServer<---+
	       |         |    ^       |
	       v         v    |       |
	   (User's frontend)--+       | Отправка сообщений
                                      |
                     (Побочные эффекты из других потоков)

Из других потоков можно:
* Оправлять сообщения в RTMP-соединение;
* Разрушать цепочку обработки (удалять соединение);

Оптравка сообщений - это, по сути, прямое обращение к DeferredConnectionSender.
НО при этом важно выяснить, осуществляет ли DeferredConnectionSender прямое
обращение к TcpConnection при отправке сообщения. В простом варианте - да,
осуществляет.

В RtmpConnection при отправке данных обновляется счётчик total_sent, который
нужен для оптравки ack'ов - то есть тоже нужна синхронизация.

? Сделать RtmpServer проксирующим объектом? MT-safe оболочка для RtmpConnection.
(сомнительное предложение)

Ещё вариант - откладывать сообщения в очередь сообщений для данного потока.
Но тогда всё равно имеем 1 мьютекс - с тем же успехом можно лочить мьютекс
в RtmpConnection.

Очевидно, что точкой синхронизации по отправке является RtmpConnection.
Точка синхронизации по приёму находится в пользовательском фронтэнде.

== Как правильно рарушать цепочку? ==

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

Если для цепочки в данный момент обрабатывается событие, то разрушение
цепочки, либо её части, откладывается до завершения обработки события.
В PollGroup появится ещё один вид callback'ов: pollEventBegin/pollEventEnd.
Для разрушаемого объекта можно установить флаг "is_closed" или аналогичный,
при условии, что объект асинхронный. Разрушение цепочек происходит на
максимально высоком уровне (в пользовательском фронтэнде).

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

Асинхронное разрушение цепочки - аналогичное действие, но с синхронизацией
в пользовательском фронтэнде.

=== Реальная MT-модель MomentVideo ===

N потоков, в каждом потоке - цикл обработки событий. Все соединения разделены
по потокам, за синхронизацию вызовов обработчиков событий отвечает Poller.

Точка синхронизации при работе с RTMP-соединениями - RtmpConnection. За
RTMPT-соединения отвечает RtmptServer.

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

Выйти из положения можно было бы подсчётом ссылок: при каджом обращении к
объекту из цикла событий увеличиваем refcount. Но счётчик ссылок при этом должен
быть атомарным. Возможны ли другие варианты?

Грубая, но интересная альтернатива - deletion queue. При разрушении объекта
помещаем его в deletion queue, которая отрабатывает, когда известно, что ни одно
событие не находится в обработке. Заведём thread-local очереди. НО для того,
чтобы поместить в очередь на удаление объект, обслуживаемый другим потоком,
снова нужна синхронизация. То есть в этом случае нужно поддерживать mt-safe
deletion queue (mt-safe в смысле постановки в очередь) - это всё же лучше,
чем выполнять синхронизированные операции при каждом событии ввода/вывода.

НО нужно проследить, не нивелируется ли получаемый выигрыш в точке синхронизации
(RtmpConnection). В videochatd закрытие соединения с клиентом означает
разрушение сеанса связи с ним ("сессии"). При этом разрушаются асинхронные
объекты, обслуживающие сессию (ClientSession, RtmpConnection,
RtmptServer::RtmptSession). Для их корректного удаления нужно использовать
блокировки на структуры данных, в которых хранятся сессионные объекты.

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

    S { A, B, C }
	S - сессия
	A, B, C - синхронные объекты, обслуживаемые разными потоками

Во-первых, возможно устроить обобщённый механизм для постановк одновременно в
несколько различных очередей удаления (DeletionSet).

Во-вторых, можно связать ref() на A, B и C с ref'ом на S. ...в этом случае
удаление S усложняется не меньше.

Ещё одна проблема с удалением связана с тем, что временем жизни S могут
управлять не только синхронные объекты с чётким циклом использования, но и
любые другие объекты:

    S { A, B, C; M, N, O }
	M, N, O - произвольные объекты

В общем случае качественное управление временем жизни обеспечивает только
подсчёт ссылок.

Q: почему в MyCpp я отказался от "reference masters"?
A: Скорее всего, из-за неявных внутренних ref'ов "на себя", т е  из-за замыкания
   ссылок через setRefMaster() (пример ревизии MyNC с setRefMaster() - #306).
   - Да, reference masters - это неудачный подход.

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

Это очень сильно отличается от подсчёта ссылок. Явное удаление vs неявное
удаление? ...лучше, конечно, выполнять удаление явно. Я сам управляю временем
жизни всех объектов в программе. И классический reference counting - это тоже
разновидность явного удаления (явно заданная политика).

Ещё один вариант - делать dispose() по истечению refcount'а и отдельно считать
ссылки на единицу хранения - usageReference и memoryReference. Для mt-safe кода
это двойная нагрузка. setMemoryMaster(), dispose()... Вряд ли я смогу
пользоваться таким механизмом без постоянных ошибок.

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

--- Появилась идея: Stack References ---

На деле для защиты от удаления объектов во время использования интересуют только
ссылки с автоматическим временем жизни, условно удерживаемые на стеке на время
выполнения некоторого участка кода. Такие ссылки являются гарантированно
временными. Назову их "Code References" (CR) - ссылки, удерживаемые программным
кодом на ограниченное время. CR захватываются подобно слабым ссылкам (WeakRef)
и в реализации для зависимых объектов могут делегироваться внешнему контейнеру.

Самостоятельные объекты (Object) сами для себя выступают в роли контейнера.
То есть для CodeReferenced существует 3 варианта работы:
    * Делегирование другому CodeReferenced;
    * Делегирование Object;
    * Самому быть Object.
Поскольку Object - это CodeReferenced, второй вариант эквивалентен первому.
Имеем 2 реализации CodeReferenced: DependentCodeReferenced и Object.

Кода счётчик ссылок Object становится равным 0, нужно не просто делать
"delete this". Но это касается, скорее, темы выделения памяти под объекты и
хранимых пользовательских деструкторов. Пока будет просто происходить delete.

