You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Успешный ChangeServer с живого соединения оставляет клиента подключённым и подписанным на вид, но поток при этом мёртв. Потребитель не получает ни одного события, по которому мог бы это заметить.
Что происходит
Подписки живут на стороне ноды и привязаны к конкретному соединению. ChangeServer открывает новое соединение и хоронит старое — вместе со всеми подписками. При этом:
события разрыва не приходят: старый сокет помечается как MarkSocketAsUserInitiated + SetIntentionalDisconnect, и его колбэки подавляются намеренно, чтобы поздние вызовы не путались с новым соединением;
OnConnected для нового соединения приходит, но по нему одному нельзя отличить «переподключились после обрыва» от «сменили сервер»;
статус остаётся Connected, и потребитель не видит причин что-либо предпринимать.
Итог: поток замолкает навсегда, и узнать об этом можно только по тому, что события перестали идти.
Воспроизведение
Blazor-демо из репозитория (Tests/TestsClients/Blazor-WebAssembly), которое восстанавливает подписки на событии разрыва — то есть делает ровно то, что от потребителя и ожидается:
подключиться к mainnet, нажать «Subscribe to Transactions + Ledger» — поток идёт, ~65 tx/s;
выбрать Testnet, нажать «Change Server»;
соединение поднимается, статус Connected, кнопка по-прежнему показывает «Unsubscribe from Streams»;
транзакций 0, леджеров 0. На testnet леджеры закрываются регулярно, так что это не «тихая сеть»: последняя запись потока предшествует моменту подключения к новому серверу.
Воспроизведено трижды: mainnet → testnet, testnet → devnet, mainnet → testnet. Ручная переподписка немедленно оживляет поток — то есть соединение исправно, потеряна именно подписка.
Контрольные опыты
Они отделяют дефект от совпадения и показывают, что дело именно в отсутствии сигнала:
Третья строка показательна: там переключение сработало правильно по случайности — флаг восстановления взвёлся от предыдущих неудачных попыток, а не от смены сервера.
Это не регресс
Проверено сборкой из 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 — про потерю подписок после переподключения, и он отложен на том основании, что клиенты переподписываются сами. Здесь видно ограничение этого рассуждения: демо переподписывается сама и всё равно теряет поток, потому что сигнала, на который она опирается, при ChangeServer не приходит. Переподписаться можно только если знаешь, что пора.
Что предлагается
Дать потребителю знать, что прежнее соединение (а с ним и подписки) ушло. Варианты, от меньшего к большему:
Событие. Поднимать разрыв или отдельное «сессия сменилась» при ChangeServer, чтобы существующая логика восстановления сработала без изменений на стороне потребителя. Самый дешёвый вариант и совместимый с тем, как потребители уже написаны;
Признак в OnConnected. Передавать, что это новая сессия после смены сервера, а не продолжение прежней;
Первый вариант закрывает наблюдаемую дыру, не забирая у потребителя контроль.
Проверяемость
Признак готовности: в сценарии выше поток после ChangeServer продолжает идти без ручного вмешательства — либо потому что потребитель получил сигнал и переподписался, либо потому что SDK сделал это сам. Демо в репозитории служит проверкой как есть, менять её не требуется.
Успешный
ChangeServerс живого соединения оставляет клиента подключённым и подписанным на вид, но поток при этом мёртв. Потребитель не получает ни одного события, по которому мог бы это заметить.Что происходит
Подписки живут на стороне ноды и привязаны к конкретному соединению.
ChangeServerоткрывает новое соединение и хоронит старое — вместе со всеми подписками. При этом:MarkSocketAsUserInitiated+SetIntentionalDisconnect, и его колбэки подавляются намеренно, чтобы поздние вызовы не путались с новым соединением;OnConnectedдля нового соединения приходит, но по нему одному нельзя отличить «переподключились после обрыва» от «сменили сервер»;Connected, и потребитель не видит причин что-либо предпринимать.Итог: поток замолкает навсегда, и узнать об этом можно только по тому, что события перестали идти.
Воспроизведение
Blazor-демо из репозитория (
Tests/TestsClients/Blazor-WebAssembly), которое восстанавливает подписки на событии разрыва — то есть делает ровно то, что от потребителя и ожидается:Connected, кнопка по-прежнему показывает «Unsubscribe from Streams»;Воспроизведено трижды: mainnet → testnet, testnet → devnet, mainnet → testnet. Ручная переподписка немедленно оживляет поток — то есть соединение исправно, потеряна именно подписка.
Контрольные опыты
Они отделяют дефект от совпадения и показывают, что дело именно в отсутствии сигнала:
Disconnect()→Connect()code=1000ChangeServerна мёртвый endpointConnectFailure, попытки #2, #3RestoringConnectionChangeServerпосле мёртвого endpointChangeServerс живого соединенияТретья строка показательна: там переключение сработало правильно по случайности — флаг восстановления взвёлся от предыдущих неудачных попыток, а не от смены сервера.
Это не регресс
Проверено сборкой из
origin/release(b5a2756c,Xrpl10.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не приходит. Переподписаться можно только если знаешь, что пора.Что предлагается
Дать потребителю знать, что прежнее соединение (а с ним и подписки) ушло. Варианты, от меньшего к большему:
ChangeServer, чтобы существующая логика восстановления сработала без изменений на стороне потребителя. Самый дешёвый вариант и совместимый с тем, как потребители уже написаны;OnConnected. Передавать, что это новая сессия после смены сервера, а не продолжение прежней;Первый вариант закрывает наблюдаемую дыру, не забирая у потребителя контроль.
Проверяемость
Признак готовности: в сценарии выше поток после
ChangeServerпродолжает идти без ручного вмешательства — либо потому что потребитель получил сигнал и переподписался, либо потому что SDK сделал это сам. Демо в репозитории служит проверкой как есть, менять её не требуется.