liner_broker 1.3.2

Redis based message serverless broker.
Documentation
# Использование API (жизненный цикл, потоки, ловушки)

## Жизненный цикл (типичный порядок)

1. **Создайте** клиент с параметрами хранилища и локальной идентичностью (`unique_name`, начальный `topic`, адрес привязки `localhost`, URL Redis или путь SQLite).
2. По желанию вызовите **`subscribe` / `unsubscribe`** до `run` (подписки ставятся в очередь и применяются при старте listener).
3. Вызовите **`run`** (C: `lnr_run`), чтобы запустить внутренние циклы listener и sender. До этого **`send_to` / `send_all`** возвращают неуспех («client not is running»).
4. Отправляйте и принимайте на **одном потоке или разных** только согласно правилам потокобезопасности вашей привязки (см. ниже).
5. **Уничтожьте** клиент (C: `lnr_delete_client`) по завершении, чтобы соединения и потоки корректно завершились.

## Потоки

- Rust-**`Client`** защищён внутренним **`Mutex`**. Одновременные вызовы с нескольких потоков сериализуются; избегайте взаимной блокировки, не вызывая рекурсивно тот же клиент из колбэка, если колбэк вызывается при удерживаемом lock (зависит от интеграции).
- После **`run`** фоновые задачи **listener** и **sender** владеют своими дескрипторами хранилища (`open_store_mutex`) и циклами событий; они не заменяют основной экземпляр `db` клиента.
- **C / Python / прочий FFI:** считайте использование данного **`lnr_hClient`** **однопоточным**, если не добавите свою синхронизацию. Со стороны Rust вызовы сериализуются при вызове из нескольких потоков, но языковые привязки без осторожности могут быть небезопасны между потоками.

## TCP `localhost` / адрес привязки

`localhost` должен быть строкой, которую **`ToSocketAddrs`** может разрешить (например `127.0.0.1:2255` или `0.0.0.0:2255`). Если резолв или **bind** не удались, **`run`** возвращает **`false`** (и пишет в лог).

## Топики и адреса

- Нельзя **`send_to` / `send_all` / `subscribe` / `unsubscribe`** на **свой** исходный топик; такие вызовы завершаются с сообщением об ошибке.
- Для **`send_to` / `send_all`** нужны известные адреса целевого топика. Клиент кэширует адреса из хранилища (см. **Внутренний канал** ниже).
- Если для топика нет адресов, отправка не удаётся с текстом «not found addr for topic …».
- Нельзя вызывать **`subscribe` / `unsubscribe`** на служебный топик **`__#internal_channel`** через публичный API; библиотека подписывается на него автоматически при **`run`**.

### Внутренний канал (`__#internal_channel`)

Каждый работающий клиент подписан на служебный топик **`__#internal_channel`**. Брокер использует его только для **служебных событий** (в ваш колбэк приёма они **не** попадают):

| Событие | Когда | Эффект у других клиентов |
|---------|-------|-------------------------|
| `client_connected` | после **`run`** | обновление кэша адресов для **исходного топика** пира и внутреннего канала |
| `client_disconnected` | при уничтожении клиента | то же обновление из хранилища |
| `subscribed` | после **`subscribe`** в running-состоянии | обновление кэша для **подписанного топика** |
| `unsubscribed` | после **`unsubscribe`** в running-состоянии | обновление кэша для топика (подписчиков нет ⇒ **`send_to`** не найдёт адрес после refresh) |

Полезная нагрузка — JSON, например: `{"event":"subscribed","client":"peer_name","topic":"foo"}`.

**Типичная сеть (happy path):** продюсеру **не обязательно** вызывать **`refresh_address_topic`** перед каждым **`send_to`**, если пиры делают **`run`** или **`subscribe`**, пока продюсер уже работает — маршруты подтягиваются по этим событиям.

**Нюансы** — когда `refresh_address_topic` всё ещё может понадобиться:

1. **Подписка до `run`** — `subscribe` ставит подписку в очередь и регистрируется в store, но событие `subscribed` **не** шлётся, пока клиент не в running. Другие пиры узнают маршрут при первом **`send_to`**, если кэш пуст (чтение из store), или после **`refresh_address_topic`**.
2. **Гонка** — пир только что подписался, внутреннее событие ещё не дошло. Первый **`send_to`** может не успеть; повторите через короткое время или вызовите **`refresh_address_topic`**.
3. **Устаревший кэш** — пир перерегистрировался на **новом порту** без нормальной последовательности disconnect. Вызовите **`refresh_address_topic(topic)`**, чтобы принудительно перечитать store.
4. **Продюсер не работал**, когда пир регистрировался — внутренние события не обрабатывались; нужен refresh или send (при пустом кэше адреса подгружаются из store при первом lookup).

**`refresh_address_topic`** — явное принудительное обновление кэша; при отсутствии адресов в store **удаляет** топик из кэша.

## Офлайн / флаги персистентности

C-функции **`lnr_send_to`** и **`lnr_send_all`** принимают **`at_least_once_delivery`**. При `TRUE` стек может сохранять сообщения для офлайн-доставки в зависимости от топика и состояния соединения. При `FALSE` поведение best-effort. Rust-**`Client`** и **`Liner`** экспортируют тот же флаг в **`send_to`** / **`send_all`**. Если пиры используют **разные файлы SQLite** (нет общего хранилища), для межпировых отправок передавайте **`false`** — см. [using-sqlite.md](using-sqlite.md) (*Изолированные файлы и `at_least_once_delivery`*). Правила персистентности, тайминг переподключения и дедупликация по **`number_mess`** — в [offline-delivery-and-message-numbers.md](offline-delivery-and-message-numbers.md).

## Очистка состояния

- **`clear_stored_messages`** и **`clear_addresses_of_topic`** разрешены только когда клиент **не** в состоянии running (`run` не вызывался или клиент уже разобран). При вызове во время running возвращают неуспех и пишут в лог. Какие именно ключи Redis / строки SQLite затрагиваются — в [operations-redis-sqlite.md](operations-redis-sqlite.md).

## Колбэки (путь приёма)

Колбэк приёма получает **указатели во временные буферы**, действительные только на время вызова колбэка. **Скопируйте** данные, если они нужны после возврата.

## Чеклист для интеграторов

1. Убедитесь в **доступности хранилища** до того, как полагаться на `run` (создание клиента уже один раз открывает хранилище).
2. После **`run`** ожидайте **stderr** при нефатальных проблемах с хранилищем в стационарной работе.
3. Заложите **редкую панику** при старте listener/sender по хранилищу, если оно «ломается» между созданием клиента и внутренним `open_store_mutex` (см. [store-startup-failure-semantics.md](store-startup-failure-semantics.md)).
4. Для SQLite на **общем** файле ожидайте **`SQLITE_BUSY`** при конкуренции; настройте нагрузку или таймаут на уровне SQLite/ОС при необходимости ([backends.md](backends.md)).