<feed xmlns='http://www.w3.org/2005/Atom'>
<title>ydb/library/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-19T17:53:29Z</updated>
<entry>
<title>YT-28504: Support heterogeneous lookup in caches</title>
<updated>2026-06-19T17:53:29Z</updated>
<author>
<name>babenko</name>
<email>babenko@yandex-team.com</email>
</author>
<published>2026-06-19T17:35:37Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/ydb/commit/?id=77f13a22a1d248303c4d9a6ad19c6adbae202dbb'/>
<id>urn:sha1:77f13a22a1d248303c4d9a6ad19c6adbae202dbb</id>
<content type='text'>
commit_hash:acb3e84437f5bdb125d7c1807847eb5edecbb11f
</content>
</entry>
<entry>
<title>[monlib] Fix use-after-free in TMonService2 destructor</title>
<updated>2026-06-19T14:30:21Z</updated>
<author>
<name>kgershov</name>
<email>kgershov@yandex-team.com</email>
</author>
<published>2026-06-19T13:53:22Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/ydb/commit/?id=be3766e32550798ea5fe60cb2a13d703ff818636'/>
<id>urn:sha1:be3766e32550798ea5fe60cb2a13d703ff818636</id>
<content type='text'>
TMonService2 binds `this` into the HTTP handler stored in TMtHttpServer.
Stop() was only called from TImpl::~TImpl() — after TMonService2 members
(IndexMonPage, AuthProvider_) had already been destroyed. A concurrent
request could call ServeRequest() and crash on IndexMonPage-&gt;Output().

Fix: call Stop() in ~TMonService2() before member destruction.
commit_hash:f5b6aff5d9f85ae76d121a036bfd30094377c5ca
</content>
</entry>
<entry>
<title>Add lock-free per-CPU primitives to library/cpp/yt/rseq</title>
<updated>2026-06-19T12:12:00Z</updated>
<author>
<name>babenko</name>
<email>babenko@yandex-team.com</email>
</author>
<published>2026-06-19T11:27:43Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/ydb/commit/?id=89c0e29c8f9ba29ecdc736fefda87286482ac213'/>
<id>urn:sha1:89c0e29c8f9ba29ecdc736fefda87286482ac213</id>
<content type='text'>
Introduce AddPerCpu and StorePerCpu over an rseq-sharded per-CPU array.

On the x86-64 Linux fast path the update is committed by a hand-rolled
rseq critical section (non-atomic, migration-safe): addq for the 8-byte
accumulate, movq / movdqu for the 8- or 16-byte store. The kernel
restarts the sequence on preemption or migration, and only one thread
runs on a CPU at a time, so no atomic or lock is needed. Off the fast
path (other arches, no kernel rseq) the operation falls back to an
atomic on the slot indexed by sched_getcpu().

A naturally-aligned 8-byte store is single-copy atomic on x86-64, so it
is never observed torn; the 16-byte store may be, which is acceptable for
a last-writer-wins gauge.
commit_hash:6250f6e9e35cf3895ebafe0b534ec12cca50b03b
</content>
</entry>
<entry>
<title>New version of the tld SKIP_CHECK SKIP_REVIEW</title>
<updated>2026-06-18T23:25:47Z</updated>
<author>
<name>robot-ratatosk</name>
<email>robot-ratatosk@yandex-team.com</email>
</author>
<published>2026-06-18T22:54:29Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/ydb/commit/?id=f3549c17df64c644f5be3f0aac7ac22a7dddc6d7'/>
<id>urn:sha1:f3549c17df64c644f5be3f0aac7ac22a7dddc6d7</id>
<content type='text'>
commit_hash:700366443410f399c1582f64da0f5efac01b725d
</content>
</entry>
<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>Add TTscp::GetApproximate</title>
<updated>2026-06-18T10:04:31Z</updated>
<author>
<name>babenko</name>
<email>babenko@yandex-team.com</email>
</author>
<published>2026-06-18T09:20:33Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/ydb/commit/?id=50fd836ab1e51d127495bb37dab3888b27e0ce09'/>
<id>urn:sha1:50fd836ab1e51d127495bb37dab3888b27e0ce09</id>
<content type='text'>
TTscp::GetApproximate takes the processor id from the rseq fast path
(GetCurrentCpuId) and the instant from a non-serializing rdtsc, instead of the
single serializing rdtscp of TTscp::Get.

TPerCpuGauge::Update switches to it: the per-shard timestamp only orders writes
across shards to pick the freshest value, so the lower precision is fine. Update
is virtual and now YT_PREVENT_TLS_CACHING -- the fiber-TLS boundary the inlined
rseq read needs.

#### Benchmark

sas2-2769 (glibc 2.31 + tcmalloc, rseq fast path), median of 5:

| primitive | time |
|---|---|
| TTscp::Get() (rdtscp) | 14.1 ns |
| TTscp::GetApproximate() (rseq + rdtsc) | 10.6 ns (-25%) |
commit_hash:b277b6551accd6d0b879f8ffb168bcbe8d9fbb74
</content>
</entry>
<entry>
<title>Speed up working with timezones</title>
<updated>2026-06-16T20:54:42Z</updated>
<author>
<name>shamteev</name>
<email>shamteev@yandex-team.com</email>
</author>
<published>2026-06-16T20:27:30Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/ydb/commit/?id=9c0feecb216b98e6e483257cf2b2afafcd7d7c77'/>
<id>urn:sha1:9c0feecb216b98e6e483257cf2b2afafcd7d7c77</id>
<content type='text'>
Optimize timezone-aware query functions (timestamp_floor\_\*\_tz, format_timestamp_tz)

Problem: queries using timezone functions on a non-Moscow/non-UTC zone (e.g.
Europe/Bucharest) ran 10-20x slower than their non-timezone equivalents. For
Moscow/UTC a lookup table handles the conversion; for any other zone the code
falls back to live cctz calls.

Cause, confirmed by flamegraph and code:

1. NDatetime::ToCivilTime performed two independent timezone lookups per call.
   cctz::convert(tp, tz) is defined as tz.lookup(tp).cs, and the function then
   called tz.lookup(tp) again for the offset/is_dst fields. Both pieces of data
   are already present in a single absolute_lookup result.
2. TimestampFloorDayTZInternal (and Month/Quarter/Year variants) located the
   start of the period via a binary search that called ToCivilTime on every
   iteration — \~18 iterations for the day case. Combined with (1) that is \~38
   cctz lookups per call, and timestamp_floor_day_tz appears twice per row in a
   typical WHERE clause.

* Changelog entry

Type: fix

1. ToCivilTime now does a single tz.lookup(tp) and reuses both cs and the
   offset/is_dst fields. Behavior is identical; affects all callers.
2. TimestampFloor\{Day,Month,Quarter,Year\}TZ compute the period start directly
   (subtract the civil time-of-day, or build the first second of the period and
   convert it back), then verify the result. The binary search is kept as a
   fallback for the rare days that do not start at midnight (DST transitions at
   00:00, historical offset shifts), so results are unchanged.
commit_hash:295ee82832ab2a4a35920067e7c063d6992bb083
</content>
</entry>
<entry>
<title>New version of the tld SKIP_CHECK SKIP_REVIEW</title>
<updated>2026-06-16T01:21:04Z</updated>
<author>
<name>robot-ratatosk</name>
<email>robot-ratatosk@yandex-team.com</email>
</author>
<published>2026-06-16T00:34:08Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/ydb/commit/?id=93226a4077f279023119906aff38f38374ddf5c2'/>
<id>urn:sha1:93226a4077f279023119906aff38f38374ddf5c2</id>
<content type='text'>
commit_hash:12a46325d5cf858c816e573782759a1cf0580070
</content>
</entry>
<entry>
<title>Fix some typos in library/cpp/getopt/small</title>
<updated>2026-06-15T16:56:48Z</updated>
<author>
<name>ponasenko-rs</name>
<email>ponasenko-rs@yandex-team.com</email>
</author>
<published>2026-06-15T15:56:06Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/ydb/commit/?id=20745c2653a72235e34f3cf5aecb414918c8ce6f'/>
<id>urn:sha1:20745c2653a72235e34f3cf5aecb414918c8ce6f</id>
<content type='text'>
commit_hash:35d60580ca855d25f47b96ec20edccdae29d3bf5
</content>
</entry>
<entry>
<title>Make library/cpp/yt/rseq a Linux-only dependency of library/cpp/yt/system</title>
<updated>2026-06-15T08:32:51Z</updated>
<author>
<name>babenko</name>
<email>babenko@yandex-team.com</email>
</author>
<published>2026-06-15T08:13:32Z</published>
<link rel='alternate' type='text/html' href='https://code.mastervirt.ru/ydb/commit/?id=12be02b42fc24cf7bd990d56da8cf8908a35db2d'/>
<id>urn:sha1:12be02b42fc24cf7bd990d56da8cf8908a35db2d</id>
<content type='text'>
Make library/cpp/yt/rseq a Linux-only dependency of library/cpp/yt/system
commit_hash:7d6f5e738658447529440425b55b2891f6664d81
</content>
</entry>
</feed>
