Наблюдение
Приёмный цикл ожидает завершения пользовательского обработчика прямо внутри себя — Xrpl/Client/connection.cs, диспетчеризация потоков:
// строка ~3049
if (OnLedgerClosed is not null)
await OnLedgerClosed.Invoke(response)!;
// строка ~3069
if (OnTransaction is not null)
await OnTransaction.Invoke(response)!;
То же и для OnValidationReceived рядом.
Почему это проблема
Обработчик подписчика становится частью критического пути соединения. Пока он выполняется, цикл не читает следующее сообщение — а по этому же соединению идут обычные запросы-ответы.
Практические следствия:
- обработчик, делающий что-либо небыстрое (запись в БД, сетевой вызов, ожидание блокировки), задерживает не только поток, но и все параллельные запросы на том же клиенте;
- при активной подписке на
transactions сообщения приходят непрерывно, поэтому даже небольшая задержка на сообщение накапливается и рано или поздно упирается в таймаут запроса;
- обработчик, который завис или упал в дедлок, полностью останавливает соединение, при этом внешне оно выглядит живым.
Отдельная сторона того же — отсутствие определённой семантики при отставании подписчика. Сейчас поведение «медленный потребитель» не описано ни в контракте, ни в доках: неизвестно, ждёт ли SDK, теряет ли сообщения, растёт ли где-то очередь.
Ожидаемое
Доставка сообщений потока развязана с приёмным циклом, и поведение при отставании подписчика определено явно. Разумные варианты (выбор за реализацией):
- очередь с ограничением (
System.Threading.Channels) и заданной политикой переполнения — ждать, отбрасывать старые или отбрасывать новые, но выбор задокументирован и по возможности настраиваем;
- либо явно оставить синхронную доставку, но задокументировать её как контракт: «обработчик обязан возвращать управление немедленно, вся работа — за его пределами».
Второе дешевле и тоже приемлемо. Неприемлемо нынешнее состояние, где семантика не описана, а цена медленного обработчика — остановка всего соединения.
Связь с другими issue
Если контракт подписки будет выведен на IXrplClient (#103), выбранная там форма доставки фактически решает и этот вопрос — их разумно рассматривать вместе. Но дефект существует и сегодня, независимо от того, через что подписываться.
Проверка
Тест с намеренно медленным обработчиком: обычные запросы на том же соединении продолжают отвечать в разумное время, поведение потока соответствует задокументированному.
Наблюдение
Приёмный цикл ожидает завершения пользовательского обработчика прямо внутри себя —
Xrpl/Client/connection.cs, диспетчеризация потоков:То же и для
OnValidationReceivedрядом.Почему это проблема
Обработчик подписчика становится частью критического пути соединения. Пока он выполняется, цикл не читает следующее сообщение — а по этому же соединению идут обычные запросы-ответы.
Практические следствия:
transactionsсообщения приходят непрерывно, поэтому даже небольшая задержка на сообщение накапливается и рано или поздно упирается в таймаут запроса;Отдельная сторона того же — отсутствие определённой семантики при отставании подписчика. Сейчас поведение «медленный потребитель» не описано ни в контракте, ни в доках: неизвестно, ждёт ли SDK, теряет ли сообщения, растёт ли где-то очередь.
Ожидаемое
Доставка сообщений потока развязана с приёмным циклом, и поведение при отставании подписчика определено явно. Разумные варианты (выбор за реализацией):
System.Threading.Channels) и заданной политикой переполнения — ждать, отбрасывать старые или отбрасывать новые, но выбор задокументирован и по возможности настраиваем;Второе дешевле и тоже приемлемо. Неприемлемо нынешнее состояние, где семантика не описана, а цена медленного обработчика — остановка всего соединения.
Связь с другими issue
Если контракт подписки будет выведен на
IXrplClient(#103), выбранная там форма доставки фактически решает и этот вопрос — их разумно рассматривать вместе. Но дефект существует и сегодня, независимо от того, через что подписываться.Проверка
Тест с намеренно медленным обработчиком: обычные запросы на том же соединении продолжают отвечать в разумное время, поведение потока соответствует задокументированному.