<feed xmlns='http://www.w3.org/2005/Atom'>
<title>ydb/library/cpp/unified_agent_client/grpc_io.cpp, branch CLI_2.32.0</title>
<subtitle>Mirror of YDB github repos</subtitle>
<id>https://code.mastervirt.ru/ydb/atom?h=CLI_2.32.0</id>
<link rel='self' href='https://code.mastervirt.ru/ydb/atom?h=CLI_2.32.0'/>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/ydb/'/>
<updated>2026-06-18T14:18:49Z</updated>
<entry>
<title>Serialize TGrpcTimer on completion queue thread</title>
<updated>2026-06-18T14:18:49Z</updated>
<author>
<name>andybg</name>
<email>andybg@yandex-team.com</email>
</author>
<published>2026-06-18T13:15:00Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/ydb/commit/?id=7873460179706278f09ef7c45150039a5d9433f5'/>
<id>urn:sha1:7873460179706278f09ef7c45150039a5d9433f5</id>
<content type='text'>
## 1. В чём была проблема

У сессии gRPC-клиента (`TClientSession`) несколько таймеров на базе **`TGrpcTimer`** используют один и тот же механизм: внутри лежит **`grpc::Alarm`**, привязанный к **одному** `CompletionQueue`, события с которого обрабатывает **отдельный поток** poller (в логах и стеках — `ua_grpc_cq`).

**Публичные методы** `TGrpcTimer::Set` и `Cancel` вызывались **и с этого потока** (после срабатывания alarm, из колбэков завершения gRPC-операций, из `Poll` и т.д.), **и с других потоков** — например, при старте сессии из потока пула (`DoStart` → первый `MakeGrpcCallTimer-&gt;Set`). В результате **один и тот же** `grpc::Alarm` и связанные с ним поля таймера менялись **конкурентно без синхронизации**. ThreadSanitizer фиксировал **data race** внутри реализации alarm в gRPC; с точки зрения контракта это **недопустимое параллельное использование** обёртки над alarm.

Типичный конфликт: на потоке приложения выполняется **`Set`** (первый запуск таймера переподключения), параллельно на **`ua_grpc_cq`** обрабатывается завершение вызова и снова вызывается **`Set`** для отложенного переподключения — оба попадают в **`Alarm::Set`** для одного объекта.

---

## 2. Изменение архитектуры (как починили)

Инвариант: **любое изменение состояния `TGrpcTimer`, которое трогает `grpc::Alarm` и служебные поля таймера, выполняется только на потоке, который крутит `CompletionQueue::Next` для этого клиента** (тот же поток, что обрабатывает срабатывания alarm).

Для этого:

- Введены внутренние **`ApplySet` / `ApplyCancel`** — в них перенесена прежняя логика работы с alarm; вызывать их разрешено **только** в контексте poller.
- Публичные **`Set` / `Cancel`**: если вызов уже идёт **из** poller (определяется **thread-local** флагом на время обработки события из CQ), сразу вызываются **`Apply*`**; иначе работа **ставится в ту же** `CompletionQueue` через **искусственное завершение** (`grpc_cq_begin_op` / `grpc_cq_end_op`), а колбэк на стороне poller выполняет **`Apply*`**.
- Чтобы отложенная операция не обращалась к уже уничтоженной сессии, перед постановкой в очередь делается **`TryRef`** на **`TAsyncJoiner`** сессии; после выполнения **`Apply*`** — **`UnRef`**. Если сессия уже уходит в закрытие и joiner недоступен, отложенный **`Set`/`Cancel`** тихо отбрасывается.

С точки зрения **`client_impl`**, вызовы **`MakeGrpcCallTimer-&gt;Set`**, **`ForceCloseTimer-&gt;Set`**, **`PollTimer-&gt;Set`**, **`-&gt;Cancel`** **не менялись** — меняется только реализация внутри **`TGrpcTimer`** и конструктор (передаётся ссылка на **`AsyncJoiner`** сессии).

---

## 3. Новая архитектура: sequence diagram и пример

{% cut "Таблица потоков, примеры A/B и диаграммы Mermaid" %}

Ниже — **два типичных сценария** для одного таймера, например **`MakeGrpcCallTimer`** (переподключение после завершения gRPC-вызова).

### Где именно теперь «живёт» работа с таймером

| Действие | Поток |
| :--- | :--- |
| Публичный **`Set` / `Cancel`** с **стороны приложения** (не poller) | Постановка задачи в **CQ**; реальное **`Apply*`** — на **`ua_grpc_cq`**. |
| Публичный **`Set` / `Cancel`** уже **внутри** обработчика события CQ (вложенный вызов) | Сразу **`Apply*`** на том же потоке (**без** повторной постановки). |
| Срабатывание **`grpc::Alarm`** | Доставка в poller → **`TGrpcTimer::OnIOCompleted`** → при необходимости снова **`Alarm.Set`** / вызов пользовательского колбэка — **всё на `ua_grpc_cq`**. |

### Пример A: первый `Set` при старте сессии (поток приложения)

Сессия стартует в **`DoStart`** на **рабочем** потоке; **`MakeGrpcCallTimer-&gt;Set(Now())`** не трогает alarm напрямую — **ставит** в CQ задачу «выполнить **`ApplySet`**»; poller **выполняет** её и выставляет alarm.

```mermaid
sequenceDiagram
    participant App as AppThread
    participant Timer as TGrpcTimer
    participant CQ as CompletionQueue
    participant Poller as ua_grpc_cq

    App-&gt;&gt;Timer: Set(now)
    Timer-&gt;&gt;Timer: not poller thread
    Timer-&gt;&gt;Timer: TryRef(AsyncJoiner)
    Timer-&gt;&gt;CQ: enqueue synthetic op
    CQ--&gt;&gt;Poller: Next delivers op
    Poller-&gt;&gt;Timer: deferred callback
    Timer-&gt;&gt;Timer: ApplySet(now)
    Timer-&gt;&gt;Timer: Alarm.Set + schedule
    Timer-&gt;&gt;Timer: UnRef(AsyncJoiner)
```

### Пример B: перепланирование после завершения вызова (уже на poller)

**`OnGrpcCallFinished`** вызывается с **CQ** после обработки тега gRPC; **`MakeGrpcCallTimer-&gt;Set(reconnectTime)`** попадает в **fast path** и сразу вызывает **`ApplySet`** на **`ua_grpc_cq`** — без очереди.

```mermaid
sequenceDiagram
    participant Poller as ua_grpc_cq
    participant Session as TClientSession
    participant Timer as TGrpcTimer

    Poller-&gt;&gt;Session: OnGrpcCallFinished
    Session-&gt;&gt;Timer: Set(reconnectTime)
    Timer-&gt;&gt;Timer: poller thread (TLS)
    Timer-&gt;&gt;Timer: ApplySet(reconnectTime)
```

### Срабатывание alarm (напоминание)

Когда срабатывает **внутренний** alarm gRPC, poller получает тег **`TGrpcTimer`**, вызывает **`OnIOCompleted`**: там снова возможны **`Alarm.Set`** (перенос по **`NextTriggerTime`**) или переход к **пользовательскому** колбэку (**`MakeGrpcCall`**, **`Poll`**, **`BeginClose`** и т.д.) — **всё на том же потоке `ua_grpc_cq`**.

{% endcut %}

---

{% cut "Технические детали (файлы, API gRPC, TSAN, lifetime)" %}

### Файлы в Arcadia

| Файл | Роль |
| :--- | :--- |
| `library/cpp/unified_agent_client/grpc_io.h`, `grpc_io.cpp` | `TGrpcTimer`, `TPostedCompletion` / `TPostedBridge`, `PostIIOCallbackToCompletionQueue`, `TlsInUaGrpcCompletionQueuePoller` в цикле poller. |
| `library/cpp/unified_agent_client/client_impl.cpp` | Создание трёх `TGrpcTimer` с передачей `AsyncJoiner` сессии. |

### Низкоуровневая постановка в CQ

Паттерн тот же, что у **`TGrpcNotification::Trigger`**: **`grpc_core::ApplicationCallbackExecCtx`**, **`grpc_core::ExecCtx`**, **`grpc_cq_begin_op`**, **`grpc_cq_end_op`**. Тег **`CompletionQueueTag`** — отдельный объект; в **`FinalizeResult`** в poller передаётся **`IIOCallback`** (мост), который выполняет отложенную лямбду и освобождает себя после **`OnIOCompleted`**.

### TSAN ()

В отчёте TSAN конкурирующие записи шли в **`grpc::internal::AlarmImpl::Set`** из потока **`ua_grpc_cq`** (цепочка **`OnGrpcCallFinished` → `MakeGrpcCallTimer-&gt;Set`**) и из потока приложения (**`DoStart` → `MakeGrpcCallTimer-&gt;Set(Now())`**). После фикса оба пути сериализуют **`ApplySet`** на poller.

### Поведение при закрытии сессии

Если **`TryRef(AsyncJoiner)`** не удался, отложенный **`Set`/`Cancel`** не ставится — сессия уже в фазе **`Join`**. Уже стоящие в CQ задачи удерживают ref до выполнения **`Apply*`** и **`UnRef`**.

### Заглушка счётчика для `MakeIOCallback`

Для отложенной лямбды используется **`TNoOpRefStub`**: **`Ref`/`UnRef`** пустые; удержание сессии обеспечивается парой **`TryRef`/`UnRef`** на **`TAsyncJoiner`**, а не счётчиком **`TIOCallback`**.

{% endcut %}
commit_hash:ba05e9c98e41bcf748270a48a818cc7d233a161b
</content>
</entry>
<entry>
<title>Drop backward compat with grpc-prev</title>
<updated>2026-03-20T10:18:33Z</updated>
<author>
<name>thegeorg</name>
<email>thegeorg@yandex-team.com</email>
</author>
<published>2026-03-20T09:02:21Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/ydb/commit/?id=fc9fe39f913bd803446f53a85054aa45181fc1f0'/>
<id>urn:sha1:fc9fe39f913bd803446f53a85054aa45181fc1f0</id>
<content type='text'>
commit_hash:e5545cade7cc946c943e85c680db7c276edc48b5
</content>
</entry>
<entry>
<title>Fix gRPC retry parser initialization race condition</title>
<updated>2025-11-22T22:48:12Z</updated>
<author>
<name>andybg</name>
<email>andybg@yandex-team.com</email>
</author>
<published>2025-11-22T22:30:13Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/ydb/commit/?id=99383234453575439d497e697d4f6b6edbdc384a'/>
<id>urn:sha1:99383234453575439d497e697d4f6b6edbdc384a</id>
<content type='text'>
Проблема:
Сегментация через ~42 секунды после запуска UA в ServiceConfigParser::GetParserIndex("retry").

Причина:
Static Initialization Order Fiasco + Lazy Initialization в gRPC CoreConfiguration.
RetryFilter пытается получить парсер до того, как CoreConfiguration зарегистрировала его.

Решение:
1. Устанавливаем лимиты потоков ДО grpc_init() (статические параметры)
2. Явно вызываем grpc_init() для гарантии выполнения всех статических инициализаторов
3. Принудительно инициализируем CoreConfiguration через CoreConfiguration::Get()

Это гарантирует порядок:
- Настройка лимитов потоков
- Инициализация библиотеки gRPC
- Построение CoreConfiguration с регистрацией всех парсеров
- Только после этого фильтры могут безопасно использовать retry парсер

Изменения:
- library/cpp/unified_agent_client/grpc_io.cpp: Исправлена EnsureGrpcConfigured()
- tests/ut/grpc_init_ut.cpp: Добавлены unit тесты
- tests/grpc_init_check/: Standalone тестовая программа
- tests/ut/ya.make: Интеграция тестов в систему сборки

Все тесты прошли: 640/640 OK
commit_hash:ed4601dfe21f6dfac653dec6e9c3e535e5a0a09c
</content>
</entry>
<entry>
<title>Limit the number of gRPC threads</title>
<updated>2024-12-10T18:35:28Z</updated>
<author>
<name>andybg</name>
<email>andybg@yandex-team.com</email>
</author>
<published>2024-12-10T17:49:27Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/ydb/commit/?id=ebceebb7b22e971e5eb52b5323bfc1a4de855145'/>
<id>urn:sha1:ebceebb7b22e971e5eb52b5323bfc1a4de855145</id>
<content type='text'>
Add test to verify the threads limit

Try to fix; Limit number of gRPC threads.
commit_hash:d525e94e0ef397bf05d367ec9904d674c17d0d76
</content>
</entry>
<entry>
<title>Use lower case for TString methods Data, Size and Empty in extsearch, fintech, games, geobase, infra, ipreg, juggler, kernel, keyboard, laas, library, logbroker, logos, mail</title>
<updated>2024-10-10T13:05:56Z</updated>
<author>
<name>mikhnenko</name>
<email>mikhnenko@yandex-team.com</email>
</author>
<published>2024-10-10T12:49:45Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/ydb/commit/?id=8afa19d6579e8d3351a28f691f8298d03fcf3948'/>
<id>urn:sha1:8afa19d6579e8d3351a28f691f8298d03fcf3948</id>
<content type='text'>
If you think that this pr has broken something for you, roll it back
commit_hash:df8e48b2a4ecbbc74a504aa3ff62ebb8f464218d
</content>
</entry>
<entry>
<title>Y_FAIL-&gt;Y_ABORT at '^li'</title>
<updated>2023-10-17T06:00:07Z</updated>
<author>
<name>ilnurkh</name>
<email>ilnurkh@yandex-team.com</email>
</author>
<published>2023-10-17T05:15:44Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/ydb/commit/?id=784925324fd115c37bc98c5bbfe64c15f9966d74'/>
<id>urn:sha1:784925324fd115c37bc98c5bbfe64c15f9966d74</id>
<content type='text'>
https://clubs.at.yandex-team.ru/arcadia/29404
</content>
</entry>
<entry>
<title>Y_VERIFY-&gt;Y_ABORT_UNLESS at ^l</title>
<updated>2023-10-09T20:57:14Z</updated>
<author>
<name>ilnurkh</name>
<email>ilnurkh@yandex-team.com</email>
</author>
<published>2023-10-09T20:39:40Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/ydb/commit/?id=e601ca03f859335d57ecff2e5aa6af234b6052ed'/>
<id>urn:sha1:e601ca03f859335d57ecff2e5aa6af234b6052ed</id>
<content type='text'>
https://clubs.at.yandex-team.ru/arcadia/29404
</content>
</entry>
<entry>
<title>feat grpc: update to grpc 1.53.1</title>
<updated>2023-07-17T16:35:29Z</updated>
<author>
<name>leonidlazarev</name>
<email>leonidlazarev@yandex-team.com</email>
</author>
<published>2023-07-17T16:35:29Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/ydb/commit/?id=cb8e9a6330e4e5d9a0e2f8506e7469bbd641ec63'/>
<id>urn:sha1:cb8e9a6330e4e5d9a0e2f8506e7469bbd641ec63</id>
<content type='text'>
update grpc to 1.53.1
update grpcio/py3 to 1.53.1

Added patches:
    22-grpc-code-output.patch - allow translation of grpc code to internal string type.
    23-max-thread-limitation.patch - to provide interface for settings of thread number limit, as
    grpc::DynamicThreadPool doesn't provide interface to limit thread number anymore.
    24-support_for-non-abort-grpc.patch - generate exception instead of application crash
    25-forkable-destruction-order.patch - correct forkable logic for TimerManager
    27-skip-child-post-fork-operations.patch - allow to skip child post fork operations to exclude UB (used for unified agent only)
    pr33495_fox_nested_fork.patch - fix issues with nested forks
    pr33582_fork_handler.patch - disable fork handler support  if it is not requested intentionally</content>
</entry>
<entry>
<title>feat grpc: update to grpc 1.50.2</title>
<updated>2023-06-02T12:07:38Z</updated>
<author>
<name>leonidlazarev</name>
<email>leonidlazarev@yandex-team.com</email>
</author>
<published>2023-06-02T12:07:38Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/ydb/commit/?id=59e0045a61e61c2ac38878f2adc7ec91ca914cc1'/>
<id>urn:sha1:59e0045a61e61c2ac38878f2adc7ec91ca914cc1</id>
<content type='text'>
update grpc to 1.50.2
update grpcio to 1.50.0

Удаленные патчи:
06-flow_control.patch - логика в upstream удалена
10-fix-crash-on-fork.patch - логика в upstream удалена
12-coverity-fix.patch - логика в upstream удалена
20-P2166-string-nullptr.patch - в upstream временный объект вместо nullptr
PR29209-fix-heap-use-after-free.patch - решение есть в upstream

Добавленные патчи:
pr33085_fix_epoll1_engine_reinit.patch
21-windows_build.patch</content>
</entry>
<entry>
<title>Log backend move</title>
<updated>2023-02-09T09:40:11Z</updated>
<author>
<name>hor911</name>
<email>hor911@ydb.tech</email>
</author>
<published>2023-02-09T09:40:11Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/ydb/commit/?id=24689527cd888aa8a640ecb5077e656b3520d373'/>
<id>urn:sha1:24689527cd888aa8a640ecb5077e656b3520d373</id>
<content type='text'>
</content>
</entry>
</feed>
