diff --git a/tools/chatgui-android/doc/ARCHITECTURE.md b/tools/chatgui-android/doc/ARCHITECTURE.md index 8cea0699..e920d527 100644 --- a/tools/chatgui-android/doc/ARCHITECTURE.md +++ b/tools/chatgui-android/doc/ARCHITECTURE.md @@ -84,10 +84,11 @@ Payload копируется в byte array; его формат зависит Перед stop/restart Kotlin сбрасывает controllers и останавливает внешние media workers. Worker снимает media-привязки, уничтожает instance, снимает standby/UDP-таймеры и уничтожает uasync. -`instance_lite_stop()` ждёт до двух секунд: при timeout отсоединяет поток, поэтому возврат -не гарантирует завершённый cleanup. Без опубликованного instance он возвращается сразу. -Restart пересоздаёт instance и uasync. Дополнительный опрос is_running в bridge до 5с -может увидеть ещё старое работающее ядро; подтверждение нового запуска — SERVICE_STARTED. +`instance_lite_stop()` ждёт полный cleanup и pthread_join, включая startup до публикации instance; +каждые две секунды ожидания пишет предупреждение, поток не отсоединяет. Повторный stop безопасен. +Restart пересоздаёт instance и uasync. Возврат restart/updateSockets подтверждает принятие копии INI, +ошибка очереди возвращается Kotlin; готовность нового запуска подтверждается SERVICE_STARTED. +Fatal signal handlers принадлежат платформе: worker не заменяет их обработчиком с небезопасными locks/JNI/backtrace. Общие правила освобождения: `../../../doc/service_lifecycle.md`. ## Данные и конфигурация diff --git a/tools/chatgui-android/jni_bridge/android_jni_bridge.c b/tools/chatgui-android/jni_bridge/android_jni_bridge.c index 6eb7b4b1..0ba5e660 100644 --- a/tools/chatgui-android/jni_bridge/android_jni_bridge.c +++ b/tools/chatgui-android/jni_bridge/android_jni_bridge.c @@ -259,13 +259,11 @@ int utun_bridge_send_message(const char* channel_id, const char* text, const uin void utun_bridge_create_channel(const char* name, const char* channel_id) { bridge_log(BLEV_INFO, "create_channel name=%s id=%s", name ? name : "", channel_id ? channel_id : ""); if (!name || !name[0]) { bridge_log(BLEV_ERROR, "create_channel: empty name"); return; } - struct UASYNC* ua = instance_lite_get_uasync(); - if (!uasync_is_running(ua)) { bridge_log(BLEV_ERROR, "create_channel: uasync not running"); return; } + if (!instance_lite_is_running()) { bridge_log(BLEV_ERROR, "create_channel: uasync not running"); return; } struct chat_create_auto_arg* req = u_calloc(1, sizeof(*req)); if (!req) { bridge_log(BLEV_ERROR, "create_channel: OOM"); return; } - req->inst = instance_lite_get_instance(); snprintf(req->name, sizeof(req->name), "%s", name); - uasync_post(ua, chat_core_create_channel_auto_trampoline, req); + bridge_post_request(chat_core_create_channel_auto_trampoline, req, &req->inst); } void utun_bridge_connect_node(const char* address, int port, const char* pubkey_hex) { @@ -283,14 +281,12 @@ void utun_bridge_request_invite_candidates(const char* channel_id) { bridge_log(BLEV_ERROR, "request_invite_candidates: null ch_id"); return; } - struct UASYNC* ua = instance_lite_get_uasync(); - if (!uasync_is_running(ua)) { bridge_log(BLEV_ERROR, "request_invite_candidates: uasync not running"); return; } + if (!instance_lite_is_running()) { bridge_log(BLEV_ERROR, "request_invite_candidates: uasync not running"); return; } struct chat_invite_cand_req* req = u_calloc(1, sizeof(*req)); if (!req) { bridge_log(BLEV_ERROR, "request_invite_candidates: OOM"); return; } - req->inst = instance_lite_get_instance(); snprintf(req->ch_id, sizeof(req->ch_id), "%s", channel_id); bridge_log(BLEV_INFO, "request_invite_candidates ch=%s", channel_id); - uasync_post(ua, chat_invite_candidates_trampoline, req); + bridge_post_request(chat_invite_candidates_trampoline, req, &req->inst); } void utun_bridge_request_invite_link(const char* channel_id, uint64_t target_node_id, uint64_t request_id) { @@ -298,17 +294,15 @@ void utun_bridge_request_invite_link(const char* channel_id, uint64_t target_nod bridge_log(BLEV_ERROR, "request_invite_link: null ch_id"); return; } - struct UASYNC* ua = instance_lite_get_uasync(); - if (!uasync_is_running(ua)) { bridge_log(BLEV_ERROR, "request_invite_link: uasync not running"); return; } + if (!instance_lite_is_running()) { bridge_log(BLEV_ERROR, "request_invite_link: uasync not running"); return; } struct chat_invite_build_req* req = u_calloc(1, sizeof(*req)); if (!req) { bridge_log(BLEV_ERROR, "request_invite_link: OOM"); return; } - req->inst = instance_lite_get_instance(); strncpy(req->ch_id, channel_id, sizeof(req->ch_id) - 1); req->request_id = request_id; req->target_node_id = target_node_id; bridge_log(BLEV_INFO, "request_invite_link ch=%s target=0x%016llx", channel_id, (unsigned long long)target_node_id); - uasync_post(ua, chat_invite_build_link_trampoline, req); + bridge_post_request(chat_invite_build_link_trampoline, req, &req->inst); } void utun_bridge_connect_channel(const char* channel_id) { @@ -316,14 +310,12 @@ void utun_bridge_connect_channel(const char* channel_id) { bridge_log(BLEV_ERROR, "connect_channel: null ch_id"); return; } - struct UASYNC* ua = instance_lite_get_uasync(); - if (!uasync_is_running(ua)) return; + if (!instance_lite_is_running()) return; struct chat_create_auto_arg* req = u_calloc(1, sizeof(*req)); if (!req) return; - req->inst = instance_lite_get_instance(); snprintf(req->name, sizeof(req->name), "%s", channel_id); bridge_log(BLEV_INFO, "connect_channel ch=%s", channel_id); - uasync_post(ua, chat_core_connect_channel_trampoline, req); + bridge_post_request(chat_core_connect_channel_trampoline, req, &req->inst); } void utun_bridge_connect_member_node(const char* channel_id, uint64_t node_id) { @@ -332,15 +324,13 @@ void utun_bridge_connect_member_node(const char* channel_id, uint64_t node_id) { channel_id ? channel_id : "(null)", (unsigned long long)node_id); return; } - struct UASYNC* ua = instance_lite_get_uasync(); - if (!uasync_is_running(ua)) return; + if (!instance_lite_is_running()) return; struct chat_connect_node_req* req = u_calloc(1, sizeof(*req)); if (!req) { bridge_log(BLEV_ERROR, "connect_member_node: OOM"); return; } - req->inst = instance_lite_get_instance(); strncpy(req->ch_id, channel_id, sizeof(req->ch_id) - 1); req->node_id = node_id; bridge_log(BLEV_INFO, "connect_member_node ch=%s node=0x%016llx", channel_id, (unsigned long long)node_id); - uasync_post(ua, chat_core_connect_node_trampoline, req); + bridge_post_request(chat_core_connect_node_trampoline, req, &req->inst); } static char* json_escape_alloc(const char* str) { @@ -712,25 +702,21 @@ int utun_bridge_dm_send(const char* conv_id, const uint8_t* text, size_t length, } void utun_bridge_dm_request_conversations(void) { - struct UASYNC* ua = instance_lite_get_uasync(); - if (!uasync_is_running(ua)) { bridge_log(BLEV_WARN, "DM conversations: core stopped"); return; } + if (!instance_lite_is_running()) { bridge_log(BLEV_WARN, "DM conversations: core stopped"); return; } struct dm_conversations_req* req = u_calloc(1, sizeof(*req)); if (!req) { bridge_log(BLEV_ERROR, "DM conversations: OOM"); return; } - req->inst = instance_lite_get_instance(); - uasync_post(ua, dm_conversations_trampoline, req); + bridge_post_request(dm_conversations_trampoline, req, &req->inst); } void utun_bridge_dm_request_messages(const char* conv_id, uint64_t generation) { - struct UASYNC* ua = instance_lite_get_uasync(); - if (!conv_id || !conv_id[0] || !generation || !uasync_is_running(ua)) { + if (!conv_id || !conv_id[0] || !generation || !instance_lite_is_running()) { bridge_log(BLEV_WARN, "DM history: invalid request/core stopped"); return; } struct dm_messages_req* req = u_calloc(1, sizeof(*req)); if (!req) { bridge_log(BLEV_ERROR, "DM history: OOM"); return; } - req->inst = instance_lite_get_instance(); req->generation = generation; snprintf(req->conv_id, sizeof(req->conv_id), "%s", conv_id); - uasync_post(ua, dm_messages_trampoline, req); + bridge_post_request(dm_messages_trampoline, req, &req->inst); } int utun_bridge_dm_send_file(const char* conv_id, const char* path, const char* name, uint64_t generation, uint64_t request_id) { @@ -1018,19 +1004,15 @@ char* utun_bridge_get_member_detail_json(const char* channel_id, uint64_t node_i } int utun_bridge_toggle_member_radio_mute(const char* channel_id, uint64_t node_id) { - struct UTUN_INSTANCE* inst = instance_lite_get_instance(); - struct UASYNC* ua = instance_lite_get_uasync(); - if (!inst || !uasync_is_running(ua) || !channel_id || !channel_id[0] || strlen(channel_id) >= 64 || !node_id) { + if (!instance_lite_is_running() || !channel_id || !channel_id[0] || strlen(channel_id) >= 64 || !node_id) { DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: Android mute request rejected: core unavailable or invalid member"); return -1; } struct chat_member_radio_mute_req* req = u_calloc(1, sizeof(*req)); - struct posted_task* post = u_calloc(1, sizeof(*post)); - if (!req || !post) { - DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: Android mute request allocation failed"); u_free(req); u_free(post); return -1; + if (!req) { + DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: Android mute request allocation failed"); u_free(req); return -1; } - req->inst = inst; req->node_id = node_id; strcpy(req->ch_id, channel_id); - post->callback = chat_core_toggle_member_radio_mute_trampoline; post->arg = req; - uasync_post_reserved(ua, post); + req->node_id = node_id; strcpy(req->ch_id, channel_id); + if (bridge_post_request(chat_core_toggle_member_radio_mute_trampoline, req, &req->inst)) return -1; return 0; } @@ -1099,16 +1081,14 @@ void utun_bridge_set_member_flags(const char* channel_id, uint64_t node_id, int admin, int supernode, int moder, int storage_flag, int verified, int deleted) { if (!channel_id || node_id == 0) { bridge_log(BLEV_ERROR, "set_member_flags: invalid args"); return; } - struct UASYNC* ua = instance_lite_get_uasync(); - if (!uasync_is_running(ua)) { bridge_log(BLEV_ERROR, "set_member_flags: uasync not running"); return; } + if (!instance_lite_is_running()) { bridge_log(BLEV_ERROR, "set_member_flags: uasync not running"); return; } struct member_tags_edit_req* req = u_calloc(1, sizeof(*req)); if (!req) { bridge_log(BLEV_ERROR, "set_member_flags: OOM"); return; } - req->inst = instance_lite_get_instance(); strncpy(req->ch_id, channel_id, sizeof(req->ch_id) - 1); req->node_id = node_id; req->admin = admin; req->supernode = supernode; req->moder = moder; req->storage = storage_flag; req->verified = verified; req->deleted = deleted; - uasync_post(ua, member_tags_edit_trampoline, req); + bridge_post_request(member_tags_edit_trampoline, req, &req->inst); bridge_log(BLEV_DEBUG, "set_member_flags queued ch=%s nid=0x%016llx admin=%d super=%d moder=%d storage=%d verified=%d deleted=%d", channel_id, (unsigned long long)node_id, admin, supernode, moder, storage_flag, verified, deleted); } @@ -1175,14 +1155,12 @@ char* utun_bridge_get_member_links_json(uint64_t node_id) { void utun_bridge_transfer_admin(const char* channel_id, uint64_t node_id) { if (!channel_id || node_id == 0) { bridge_log(BLEV_ERROR, "transfer_admin: invalid args"); return; } - struct UASYNC* ua = instance_lite_get_uasync(); - if (!uasync_is_running(ua)) { bridge_log(BLEV_ERROR, "transfer_admin: uasync not running"); return; } + if (!instance_lite_is_running()) { bridge_log(BLEV_ERROR, "transfer_admin: uasync not running"); return; } struct chat_transfer_admin_req* req = u_calloc(1, sizeof(*req)); if (!req) { bridge_log(BLEV_ERROR, "transfer_admin: OOM"); return; } - req->inst = instance_lite_get_instance(); strncpy(req->ch_id, channel_id, sizeof(req->ch_id) - 1); req->target_node_id = node_id; - uasync_post(ua, chat_core_transfer_admin_trampoline, req); + bridge_post_request(chat_core_transfer_admin_trampoline, req, &req->inst); bridge_log(BLEV_INFO, "transfer_admin queued ch=%s → 0x%016llx", channel_id, (unsigned long long)node_id); } @@ -1192,13 +1170,11 @@ void utun_bridge_transfer_admin(const char* channel_id, uint64_t node_id) { void utun_bridge_delete_channel(const char* channel_id) { if (!channel_id || !channel_id[0]) { bridge_log(BLEV_ERROR, "delete_channel: empty ch_id"); return; } - struct UASYNC* ua = instance_lite_get_uasync(); - if (!uasync_is_running(ua)) { bridge_log(BLEV_ERROR, "delete_channel: uasync not running"); return; } + if (!instance_lite_is_running()) { bridge_log(BLEV_ERROR, "delete_channel: uasync not running"); return; } struct chat_delete_channel_req* req = u_calloc(1, sizeof(*req)); if (!req) { bridge_log(BLEV_ERROR, "delete_channel: OOM"); return; } - req->inst = instance_lite_get_instance(); strncpy(req->ch_id, channel_id, sizeof(req->ch_id) - 1); - uasync_post(ua, chat_core_delete_channel_trampoline, req); + bridge_post_request(chat_core_delete_channel_trampoline, req, &req->inst); bridge_log(BLEV_INFO, "delete_channel queued ch=%s", channel_id); } @@ -1226,14 +1202,12 @@ int utun_bridge_get_sound_on_message(const char* channel_id) { void utun_bridge_set_sound_on_message(const char* channel_id, int on) { if (!channel_id || !channel_id[0]) { bridge_log(BLEV_ERROR, "set_sound: empty ch_id"); return; } - struct UASYNC* ua = instance_lite_get_uasync(); - if (!uasync_is_running(ua)) { bridge_log(BLEV_ERROR, "set_sound: uasync not running"); return; } + if (!instance_lite_is_running()) { bridge_log(BLEV_ERROR, "set_sound: uasync not running"); return; } struct sound_setting_req* req = u_calloc(1, sizeof(*req)); if (!req) { bridge_log(BLEV_ERROR, "set_sound: OOM"); return; } - req->inst = instance_lite_get_instance(); strncpy(req->ch_id, channel_id, sizeof(req->ch_id) - 1); req->on = on ? 1 : 0; - uasync_post(ua, sound_setting_trampoline, req); + bridge_post_request(sound_setting_trampoline, req, &req->inst); bridge_log(BLEV_INFO, "set_sound queued ch=%s on=%d", channel_id, on ? 1 : 0); } @@ -1407,7 +1381,7 @@ void utun_bridge_collect_conn_metrics(uint64_t peer_node_id) { instance_lite_collect_conn_metrics(peer_node_id); } -void utun_bridge_restart(const char* config_text) { +int utun_bridge_restart(const char* config_text) { bridge_log(BLEV_INFO, "bridge restart: posting to instance"); #ifdef __ANDROID__ { @@ -1415,20 +1389,12 @@ void utun_bridge_restart(const char* config_text) { instance_lite_set_event_handler(instance_event_handler); } #endif - instance_lite_restart(config_text); - - /* Wait for instance to be ready before returning to Kotlin. - Normal restart (live): is_running() true immediately → no delay. - Dead restart (after stop): polls until g_running=1 → blocks until init done. */ - for (int i = 0; i < 500 && !instance_lite_is_running(); i++) - usleep(10000); - if (!instance_lite_is_running()) - bridge_log(BLEV_ERROR, "bridge restart: instance not ready after 5s"); + return instance_lite_restart(config_text); } -void utun_bridge_update_sockets(const char* config_text) { +int utun_bridge_update_sockets(const char* config_text) { bridge_log(BLEV_INFO, "bridge update_sockets: posting to instance"); - instance_lite_update_sockets(config_text); + return instance_lite_update_sockets(config_text); } void utun_bridge_ping(void) { @@ -1496,15 +1462,13 @@ int utun_bridge_attachment_send(const char* channel_id, const char* file_path, c int utun_bridge_attachment_download(const char* channel_id, int64_t msg_id) { if (!channel_id || msg_id <= 0) { bridge_log(BLEV_ERROR, "attachment_download: invalid args"); return -1; } - struct UASYNC* ua = instance_lite_get_uasync(); - if (!uasync_is_running(ua)) return -1; + if (!instance_lite_is_running()) return -1; struct attachment_dl_req* req = u_calloc(1, sizeof(*req)); if (!req) return -1; - req->inst = instance_lite_get_instance(); snprintf(req->channel_id, sizeof(req->channel_id), "%s", channel_id); req->msg_id = msg_id; bridge_log(BLEV_INFO, "attachment_download: ch=%s msg_id=%lld", channel_id, (long long)msg_id); - uasync_post(ua, chat_core_attachment_download_trampoline, req); + bridge_post_request(chat_core_attachment_download_trampoline, req, &req->inst); return 0; } @@ -1648,37 +1612,27 @@ int utun_bridge_call_start(const char* ch_id, uint64_t peer_node_id, uint64_t ca (unsigned long long)peer_node_id, (unsigned long long)call_id); return -1; } - struct UASYNC* ua = instance_lite_get_uasync(); - struct UTUN_INSTANCE* inst = instance_lite_get_instance(); - if (!inst || !uasync_is_running(ua)) { bridge_log(BLEV_ERROR, "call_start: core unavailable"); return -1; } + if (!instance_lite_is_running()) { bridge_log(BLEV_ERROR, "call_start: core unavailable"); return -1; } struct call_start_arg* a = (struct call_start_arg*)u_calloc(1, sizeof(*a)); - struct posted_task* post = u_calloc(1, sizeof(*post)); - if (!a || !post) { bridge_log(BLEV_ERROR, "call_start: allocation failed"); u_free(a); u_free(post); return -1; } - a->inst = inst; - snprintf(a->ch_id, sizeof(a->ch_id), "%s", ch_id); + if (!a) { bridge_log(BLEV_ERROR, "call_start: allocation failed"); u_free(a); return -1; } + snprintf(a->ch_id, sizeof(a->ch_id), "%s", ch_id); a->peer_node_id = peer_node_id; a->call_id = call_id; bridge_log(BLEV_INFO, "call_start ch=%s peer=0x%llx id=0x%llx", ch_id, (unsigned long long)peer_node_id, (unsigned long long)call_id); - post->callback = call_start_trampoline; post->arg = a; - uasync_post_reserved(ua, post); + if (bridge_post_request(call_start_trampoline, a, &a->inst)) return -1; return 0; } static int bridge_call_id_action(uint64_t call_id, const char* name, void (*trampoline)(void*)) { if (call_id == 0) { bridge_log(BLEV_ERROR, "call_%s: bad call_id=0", name); return -1; } - struct UASYNC* ua = instance_lite_get_uasync(); - struct UTUN_INSTANCE* inst = instance_lite_get_instance(); - if (!inst || !uasync_is_running(ua)) { bridge_log(BLEV_ERROR, "call_%s: core unavailable", name); return -1; } + if (!instance_lite_is_running()) { bridge_log(BLEV_ERROR, "call_%s: core unavailable", name); return -1; } struct call_id_arg* a = (struct call_id_arg*)u_calloc(1, sizeof(*a)); - struct posted_task* post = u_calloc(1, sizeof(*post)); - if (!a || !post) { bridge_log(BLEV_ERROR, "call_%s: allocation failed", name); u_free(a); u_free(post); return -1; } - a->inst = inst; - a->call_id = call_id; + if (!a) { bridge_log(BLEV_ERROR, "call_%s: allocation failed", name); u_free(a); return -1; } + a->call_id = call_id; bridge_log(BLEV_INFO, "call_%s id=0x%llx", name, (unsigned long long)call_id); - post->callback = trampoline; post->arg = a; - uasync_post_reserved(ua, post); + if (bridge_post_request(trampoline, a, &a->inst)) return -1; return 0; } @@ -1720,15 +1674,12 @@ void utun_bridge_call_audio_set_aec_delay(int frames) { * ────────────────────────────────────────────────────────────────── */ void utun_bridge_radio_set_active(uint64_t group_id, int on) { - struct UTUN_INSTANCE* inst = instance_lite_get_instance(); - struct UASYNC* ua = instance_lite_get_uasync(); - if (!inst || !ua) { bridge_log(BLEV_ERROR, "radio_set_active: no instance/ua"); return; } + if (!instance_lite_is_running()) { bridge_log(BLEV_ERROR, "radio_set_active: no instance/ua"); return; } struct radio_set_active_arg* a = (struct radio_set_active_arg*)u_calloc(1, sizeof(*a)); if (!a) return; - a->inst = inst; - a->group_id = group_id; + a->group_id = group_id; a->on = on; - uasync_post(ua, radio_set_active_trampoline, a); + bridge_post_request(radio_set_active_trampoline, a, &a->inst); } int utun_bridge_radio_subscribers(uint64_t group_id) { @@ -2473,9 +2424,9 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeRestart( if (!configText) return JNI_FALSE; const char* cfg = (*env)->GetStringUTFChars(env, configText, NULL); if (!cfg) return JNI_FALSE; - utun_bridge_restart(cfg); + int result = utun_bridge_restart(cfg); (*env)->ReleaseStringUTFChars(env, configText, cfg); - return JNI_TRUE; + return result == 0 ? JNI_TRUE : JNI_FALSE; } JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeUpdateSockets( @@ -2484,9 +2435,9 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeUpdateSockets if (!configText) return JNI_FALSE; const char* cfg = (*env)->GetStringUTFChars(env, configText, NULL); if (!cfg) return JNI_FALSE; - utun_bridge_update_sockets(cfg); + int result = utun_bridge_update_sockets(cfg); (*env)->ReleaseStringUTFChars(env, configText, cfg); - return JNI_TRUE; + return result == 0 ? JNI_TRUE : JNI_FALSE; } JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeIsResponsive( diff --git a/tools/chatgui-android/jni_bridge/android_jni_bridge.h b/tools/chatgui-android/jni_bridge/android_jni_bridge.h index 2748322c..c7541511 100644 --- a/tools/chatgui-android/jni_bridge/android_jni_bridge.h +++ b/tools/chatgui-android/jni_bridge/android_jni_bridge.h @@ -95,15 +95,14 @@ char* utun_bridge_get_messages_json(const char* channel_id, int limit, const uin /* Текущий node_id либо 0, если чат не инициализирован. */ uint64_t utun_bridge_get_my_node_id(void); -/* Передать INI в instance_lite_restart, затем до 5с опрашивать is_running. - * У живого ядра старое is_running может сразу дать 1: возврат не гарантирует завершение рестарта. +/* Передать INI в instance_lite_restart: 0 — запрос принят, -1 — отказ с логом. Ожидания готовности здесь нет. * Событие CHAT_EVT_SERVICE_STARTED подтверждает запуск нового ядра; перед вызовом остановить media workers/DSP. */ -void utun_bridge_restart(const char* config_text); +int utun_bridge_restart(const char* config_text); -/* Копирует INI, ставит reload [server:*] в uasync (auto_sockets=android); без готового ядра запрос отбрасывается. */ +/* Копирует INI, ставит reload [server:*] в uasync (auto_sockets=android); 0 — принято, -1 — отказ с логом. */ -void utun_bridge_update_sockets(const char* config_text); +int utun_bridge_update_sockets(const char* config_text); /* Локальный ping uasync; is_responsive=1 после обработки последнего ping, 0 до первого/после stop. * Ожидания и таймаута здесь нет, доступность пиров не проверяется. */