191 lines
21 KiB
Markdown
191 lines
21 KiB
Markdown
# Жизненный цикл ядра и сервисов
|
||
|
||
CLI выполняет daemon fork до создания `UASYNC`, SQLite, сокетов и фоновых потоков.
|
||
Сервисы и их workers создаются уже в дочернем процессе. Родитель ждёт подтверждения
|
||
успешной инициализации и записи PID-файла; ошибка старта возвращается вызывающему.
|
||
`test_daemon_worker` проверяет настоящий запуск без `-f`, наличие дискового потока,
|
||
штатный SIGTERM и ненулевой статус launcher при ошибке PID-файла.
|
||
|
||
Все операции выполняются в потоке единственного `UASYNC` экземпляра, вне callbacks
|
||
останавливаемых модулей. GUI передаёт команды в этот поток.
|
||
|
||
```c
|
||
struct UTUN_INSTANCE* inst = utun_instance_create_from_str(ua, config);
|
||
if (!inst || utun_core_start(inst) < 0) /* обработать ошибку */;
|
||
if (chat_service_start(inst) < 0) /* обработать ошибку */;
|
||
if (utun_service_start(inst) < 0) /* обработать ошибку */;
|
||
|
||
chat_service_stop(inst); /* UTUN продолжает работать */
|
||
chat_service_start(inst); /* загружает сохранённые каналы */
|
||
utun_service_stop(inst); /* чат продолжает работать */
|
||
utun_instance_destroy(inst);
|
||
```
|
||
|
||
`utun_instance_init()` — сокращение для запуска ядра, UTUN и чата, если чат включён
|
||
в конфигурации. Desktop GUI и Android запускают ядро и чат явно, без UTUN.
|
||
Повторный start работающего сервиса и повторный stop допустимы. Ошибка старта
|
||
сервиса откатывает созданные им ресурсы. `utun_instance_stop()` останавливает цикл
|
||
приложения; освобождение ядра выполняет `utun_instance_destroy()`.
|
||
|
||
Успех запуска требует успешной инициализации всех обязательных ресурсов: заданных
|
||
listen-сокетов, мониторинга, TUN и всех `route_subnet` при включённом TUN. Частичный
|
||
набор сокетов или маршрутов — ошибка запуска. `utun_started` устанавливается только
|
||
после завершения всех шагов. Системные маршруты хранятся отдельным списком успешно
|
||
созданных записей; stop и откат удаляют только этот список, в обратном порядке.
|
||
Маршрут, на добавлении которого ядро вернуло ошибку, не становится собственностью
|
||
UTUN и при откате не удаляется. Ошибка одного удаления не прекращает очистку остальных.
|
||
|
||
`auto_sockets=android` выбирает внешнее управление сетью: frontend регистрирует
|
||
ConnectivityManager до запроса запуска и передаёт конфигурацию сокетов через JNI.
|
||
Оба нативных монитора ОС в этом режиме не создаются. Рабочие listen-сокеты по-прежнему
|
||
должны открыться полностью. Android подтверждает запуск по `SERVICE_STARTED`, а не
|
||
по успешному созданию потока; после события передаётся свежий снимок адресов.
|
||
|
||
| Владелец | Ресурсы |
|
||
|---|---|
|
||
| Ядро | Идентичность узла, реестр узлов и групп, SQLite, транспортные сокеты, NCD, маршрутизатор, общая репликация, время, мониторинг |
|
||
| UTUN | Группа UTUN, запросы соединений из конфигурации, DATA binding, TUN, системные маршруты, NAT transport |
|
||
| Чат | CHAT-группы, запросы подключения и join, экземпляры синхронизации каналов, chat API, медиа, личные сообщения, звонки и headless API |
|
||
| Группа | Сессии пиров, её маршруты и логические каналы, планировщик подключения и recovery |
|
||
| Сессия пира группы | NCD handle с момента CONNECTING, согласованные эпохи, состояние обмена таблицей, очередь отправки |
|
||
|
||
SQLite открывается ядром один раз: `db_path/chats.db`, либо `:memory:`, если путь
|
||
не задан. Остановка чата сохраняет каналы и сообщения в этой БД. В варианте
|
||
`:memory:` данные сохраняются только до уничтожения ядра.
|
||
|
||
Работающий общий транспорт сохраняется, пока он нужен хотя бы одному из пиров. Закрытие запроса одной группы
|
||
не разрывает соединение, нужное другой группе. При остановке группы отменяются
|
||
её планировщики, запросы, таймеры и ожидания очередей; отправляется LEAVE,
|
||
закрываются логические каналы маршрутизатора и удаляется её ожидающий транзит
|
||
на общих соединениях. Транзит других групп сохраняется. Отложенный LEAVE может временно
|
||
удерживать транспорт самостоятельно, не обращаясь к уже удалённой группе.
|
||
|
||
ETCP удаляет транспорт после 30 секунд непрерывного DOWN только при отсутствии локальных NCD handles.
|
||
Первоначальное ожидание UP тоже учитывает владение. Повторный DOWN не продлевает
|
||
таймаут; восстановление UP отменяет его. Если срок истёк при наличии handles, транспорт
|
||
сохраняется, а исходный deadline остаётся. Освобождение последнего handle во время DOWN
|
||
оставляет транспорт до этого deadline либо запускает удаление в следующем callback,
|
||
если срок уже истёк. Внешние ETCP refs задерживают только освобождение памяти.
|
||
Явный force-close и отмена неудачной попытки последнего владельца закрывают сразу.
|
||
|
||
Входящий UP без локальных handles сохраняется: он может быть нужен пиру. CLOSE/KEEP_ALIVE
|
||
привязан при запуске ядра, поэтому мягкое закрытие работает и на чисто входящем узле.
|
||
На CLOSE при наличии handles отвечаем KEEP_ALIVE; без них закрываем транспорт.
|
||
KEEP_ALIVE полностью отменяет FIN_WAIT. Ожидание CLOSE обычно занимает 5 секунд,
|
||
для спящего пира увеличивается до его RX deadline. Сам факт DOWN во время FIN_WAIT
|
||
завершает ранее запрошенное мягкое закрытие.
|
||
|
||
Контроль RX независим от отправки keepalive. Объявленный сон не отключает проверку
|
||
жизни: срок от последнего RX равен максимуму согласованного keepalive timeout и
|
||
объявленного остатка ACTIVE+SLEEP с запасом normal timeout. Фазовые таймеры и наши
|
||
отправки срок жизни пира не продлевают. Пробуждение сразу сокращает deadline до normal timeout.
|
||
После истечения последнего линка транспорт
|
||
становится DOWN, затем может удаляться через 30 секунд, когда локальных NCD handles нет. `test_etcp_server_lifecycle`
|
||
проверяет сроки, несколько путей, восстановление, владение, сон и CLOSE/KEEP_ALIVE
|
||
с управляемыми часами и контролем освобождения памяти/таймеров.
|
||
|
||
Остановка чата сначала прекращает внешние команды и фоновые задания, затем
|
||
закрывает медиа и сервисы, синхронизацию и группы, после чего освобождает chat core.
|
||
`media_async_destroy()` дожидается завершения native workers и вызывает каждый
|
||
ожидающий callback с `MEDIA_ASYNC_CANCELLED`, пока его контекст ещё существует.
|
||
Worker резервирует completion до запуска и передаёт его через `uasync_post_reserved`;
|
||
периодического polling нет. Destroy выполняется вне callbacks uasync, после join
|
||
отменяет pending post и завершает done ровно один раз. Uasync живёт до конца этой очистки.
|
||
Результаты отменённых задач не записываются в БД. Это синхронное ожидание: длительное
|
||
транскодирование или распознавание речи может задержать stop; принудительного
|
||
прерывания этих библиотек нет.
|
||
|
||
Live media хранится в RS-кольце с общим индексом PTS; каждый локальный handle владеет
|
||
отдельным cursor. Пауза player не останавливает сетевой ingest и не закрепляет старые
|
||
части против вытеснения. После FIN publisher может снять своё владение, сохранив
|
||
завершённый источник для оставшихся локальных readers. Последний close освобождает
|
||
источник, индекс и кольцо. Новая публикация отсоединяет readers прежней эпохи.
|
||
Android preview собственного эфира не создаёт reader: Camera2 пишет прямо в TextureView
|
||
и encoder Surface. UI attach/detach меняет лишь camera session; audio/encoder/origin сохраняются.
|
||
UI, ожидающие команды camera handler и каждая camera session удерживают preview target отдельно.
|
||
TextureView возвращает false при destroy и снимает ссылку UI; Surface/SurfaceTexture освобождаются
|
||
после закрытия последней session. CameraDevice.onClosed снимает также оставшиеся ссылки своих
|
||
sessions, если Android не прислал отдельные session.onClosed. Повторные callbacks безопасны.
|
||
Capture stop запрещает attach и снимает своё владение target; camera HandlerThread завершается
|
||
после camera close и освобождения outputs всех созданных sessions.
|
||
Отказ preview output возвращает encoder-only session и не завершает сам эфир.
|
||
При stop/destroy отменяются delivery и media-index callbacks до освобождения store;
|
||
destroy отсоединённого cursor не обращается к store. Frontend прекращает audio callback
|
||
и worker до освобождения своего DSP; borrowed handle/RS pointers между потоками не передаются.
|
||
|
||
Чтение video cursor запрашивает следующий кадр по готовности decoder, независимо от
|
||
PTS показа; audio cursor сохраняет временной lookahead. Bridges готовят один ответ
|
||
на незавершённый запрос и не читают следующий кадр после получения ответа автоматически.
|
||
Android worker держит один рабочий input и один готовый output MediaCodec; свободный
|
||
input buffer получает лишь для уже готовых данных и сразу возвращает codec.
|
||
Первый decoded frame разрешает движение clock/audio. Будущий output ждёт PTS и
|
||
приостанавливает новые video requests, сохраняя независимую обработку audio/control.
|
||
PTS поданных кадров служат диагностике/проверке запаздывания, а не лимиту заполнения codec.
|
||
Смена revision заменяет только video decoder и его измеренный защитный guard;
|
||
GAP/EXPIRED начинают новую точку KEY. Нехватка сети сохраняет decoder references.
|
||
Desktop FFmpeg держит один рабочий AVPacket и два готовых изображения; EAGAIN оставляет
|
||
packet до следующего прохода. FIN подаёт EOS и дренирует выход до decoder EOF;
|
||
конец GUI требует также показа оставшихся изображений и завершения audio tail.
|
||
Отсутствие прогресса decoder при наличии работы имеет отдельную диагностику и срок 10s.
|
||
|
||
ACK-история принадлежит ядру и обновляется только при первом найденном подтверждении
|
||
пакета. REINIT/close соединения не стирает уже подтверждённые за последнюю секунду байты.
|
||
Общий `stream_publisher` Android/desktop владеет отдельным 100ms таймером TX-снимков;
|
||
close/detach отменяют его в uasync до уничтожения instance. Bridges копируют снимок
|
||
под mutex, frontend не обращается к живым очередям транспорта. Android экран подписан
|
||
на отдельный StateFlow статистики; desktop обновляет статистику отдельно от сырого превью.
|
||
Publisher не регистрирует stream event callback и не подменяет callback viewer.
|
||
Bridge удерживает внешний mutex на время producer/open/stop и destroy общего publisher;
|
||
actor не вызывает frontend callbacks под своим mutex. Reserved post, media timer,
|
||
stats timer и stream handle освобождаются до контекста publisher.
|
||
|
||
Desktop `MainWindow` владеет `StreamPublishEngine`, а окно управления лишь отображает
|
||
его состояние. Закрытие окна сохраняет эфир, новый запуск запрещён до конца FIN drain.
|
||
Qt capture callback хранит только последний кадр, video/audio workers выполняют encode/DSP
|
||
вне GUI и hardware callbacks. Отдельные поколения источника и encoder позволяют менять
|
||
видеокодек/bitrate/rotation без повторного выбора экрана в портале. Ошибки Qt capture
|
||
обрабатываются через queued callback, чтобы не уничтожать backend из собственного callback.
|
||
Сначала stop запрещает producers, закрывает Qt capture и audio device, затем join workers,
|
||
освобождает microphone lease и завершает публикацию через FIN с drain 2s.
|
||
При завершении приложения engine уничтожается до SoundManager и ядра; detach в uasync
|
||
закрывает оставшийся publisher. Workers никогда не держат borrowed instance/handle.
|
||
Microphone lease публикации эксклюзивен; прежние call/radio/recorder leases совместные.
|
||
Восстановление устройства сохраняет владение и не разрешает другой capture до stop/failure.
|
||
Fatal ошибка audio worker передаётся в GUI с session ID и завершает всю публикацию;
|
||
откат видеоконфигурации применяется только к video/capture. Поздний отказ старого publisher
|
||
не завершает новую сессию. Причина ошибки сохраняется в окне после окончания drain.
|
||
|
||
Статус транспорта UP подтверждает восстановление физического пути. READY сессии
|
||
группы означает согласованный JOIN/ACCEPT и завершённый обмен её таблицей; именно
|
||
его ждёт recovery. REINIT меняет эпохи обмена, сохраняя рабочие маршруты. Эпохи
|
||
защищают от сообщений старой сессии и не задают срок жизни маршрута.
|
||
|
||
Проверки: `test_services` проверяет независимый stop/start, общий NCD в CONNECTING,
|
||
сохранение идентичности/сокетов/БД, повторную загрузку каналов, отмену workers,
|
||
заблокированного медиа-потока, транзита и ожиданий заменённой очереди. `test_chat_join_e2e`
|
||
проверяет join и распространение membership между узлами без UTUN-сервиса.
|
||
|
||
В Android звонковой сессией владеет `CallController` уровня приложения; события
|
||
ядра и команды Telecom обрабатываются один раз в main thread, без повторного
|
||
проигрывания событий при создании ViewModel. UI получает `StateFlow` и передаёт
|
||
пользовательские команды. `CallService` владеет `CallAudioEngine` и частичным
|
||
wake lock: перед исходящим вызовом или принятием входящего запускается foreground
|
||
режим `phoneCall|microphone`, затем сигналинг и аудио. Завершение сначала
|
||
останавливает I/O и дожидается аудиопотоков, затем освобождает native-аудио,
|
||
wake lock и сервис. Перед `NativeLib.stop/restart/destroy` выполняется тот же
|
||
порядок; новый звонок разрешается после `SERVICE_STARTED` нового ядра.
|
||
|
||
Активность ядра задаётся явно: видимая Activity или незавершённый звонок удерживают
|
||
ACTIVE и выключают Android standby. Состояние не истекает по таймеру. После
|
||
завершения звонка в фоне возвращается standby; экран может гаснуть во время
|
||
разговора независимо от частичного wake lock сервиса.
|
||
|
||
`utun_set_client_activity()` в сборке с `UTUN_HAVE_STANDBY` применяет фактический
|
||
режим duty-cycle до рассылки событий активности; JNI не переключает его отдельно.
|
||
Повторная команда не перезапускает фазу и анонсы, но первый фоновый запуск включает
|
||
standby даже при начальном `client_activity=STANDBY`. Keepalive подписан на режим
|
||
и фазы самого standby; новые UDP/TCP-линки сразу анонсируют текущую фазу после
|
||
handshake. Пробуждение по таймеру и внешней сети сначала сообщает ACTIVE-окно
|
||
пирам, затем запускает ожидающие задачи. При выходе сначала восстанавливаются
|
||
линки; deinit отменяет ожидающие задачи и очищает подписки старого ядра.
|