Skip to content

Успешный ChangeServer молча убивает поток: подписки уходят со старым соединением, а события об этом нет #123

Description

@Platonenkov

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

Что происходит

Подписки живут на стороне ноды и привязаны к конкретному соединению. ChangeServer открывает новое соединение и хоронит старое — вместе со всеми подписками. При этом:

  • события разрыва не приходят: старый сокет помечается как MarkSocketAsUserInitiated + SetIntentionalDisconnect, и его колбэки подавляются намеренно, чтобы поздние вызовы не путались с новым соединением;
  • OnConnected для нового соединения приходит, но по нему одному нельзя отличить «переподключились после обрыва» от «сменили сервер»;
  • статус остаётся Connected, и потребитель не видит причин что-либо предпринимать.

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

Воспроизведение

Blazor-демо из репозитория (Tests/TestsClients/Blazor-WebAssembly), которое восстанавливает подписки на событии разрыва — то есть делает ровно то, что от потребителя и ожидается:

  1. подключиться к mainnet, нажать «Subscribe to Transactions + Ledger» — поток идёт, ~65 tx/s;
  2. выбрать Testnet, нажать «Change Server»;
  3. соединение поднимается, статус Connected, кнопка по-прежнему показывает «Unsubscribe from Streams»;
  4. транзакций 0, леджеров 0. На testnet леджеры закрываются регулярно, так что это не «тихая сеть»: последняя запись потока предшествует моменту подключения к новому серверу.

Воспроизведено трижды: mainnet → testnet, testnet → devnet, mainnet → testnet. Ручная переподписка немедленно оживляет поток — то есть соединение исправно, потеряна именно подписка.

Контрольные опыты

Они отделяют дефект от совпадения и показывают, что дело именно в отсутствии сигнала:

сценарий события разрыва подписка восстановлена поток
Disconnect()Connect() да, code=1000 да жив (+13 tx, +7 леджеров)
ChangeServer на мёртвый endpoint да, ConnectFailure, попытки #2, #3 корректный RestoringConnection
ChangeServer после мёртвого endpoint флаг остался взведён от прошлых отказов да жив (912 tx)
ChangeServer с живого соединения нет нет мёртв

Третья строка показательна: там переключение сработало правильно по случайности — флаг восстановления взвёлся от предыдущих неудачных попыток, а не от смены сервера.

Это не регресс

Проверено сборкой из origin/release (b5a2756c, Xrpl 10.12.0.0) — до всей линии работ над потоком (#102, #108, #109, #111, #114). Поведение совпадает до деталей: Connected, восстановления нет, 33 секунды тишины, последняя запись потока предшествует подключению.

Два независимых подтверждения:

  • демо между release и dev не менялось — единственная правка в Index.razor косметическая, под новую обёртку ответа; логика восстановления идентична;
  • в диффе connection.cs (release → dev, 492 добавленные строки) нет ни одного попадания в MarkSocketAsUserInitiated, SetIntentionalDisconnect, RetireOldSessionAsync, OnDisconnect.

Дефект просто был незаметен: он молчаливый, а работа над наблюдаемостью потока и сделала его видимым.

Связь с #104

#104 — про потерю подписок после переподключения, и он отложен на том основании, что клиенты переподписываются сами. Здесь видно ограничение этого рассуждения: демо переподписывается сама и всё равно теряет поток, потому что сигнала, на который она опирается, при ChangeServer не приходит. Переподписаться можно только если знаешь, что пора.

Что предлагается

Дать потребителю знать, что прежнее соединение (а с ним и подписки) ушло. Варианты, от меньшего к большему:

  1. Событие. Поднимать разрыв или отдельное «сессия сменилась» при ChangeServer, чтобы существующая логика восстановления сработала без изменений на стороне потребителя. Самый дешёвый вариант и совместимый с тем, как потребители уже написаны;
  2. Признак в OnConnected. Передавать, что это новая сессия после смены сервера, а не продолжение прежней;
  3. Восстанавливать подписки самим — это уже Подписка не восстанавливается после переподключения — поток молча прекращается #104, шире и требует хранить состояние подписок в SDK.

Первый вариант закрывает наблюдаемую дыру, не забирая у потребителя контроль.

Проверяемость

Признак готовности: в сценарии выше поток после ChangeServer продолжает идти без ручного вмешательства — либо потому что потребитель получил сигнал и переподписался, либо потому что SDK сделал это сам. Демо в репозитории служит проверкой как есть, менять её не требуется.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions