Race initial stream suppliers with relay delay of 100ms

This commit is contained in:
evgeny committed 2026-10-13 16:14:35 +02:00
1 parent e708117259
commit ad20f954c5
2 files changed
+141 -11

No files matched your search

+60 -2
View File
@@ -2,6 +2,7 @@
#include "../../lib/debug_config.h"
#include "../../lib/mem.h"
#include "../../lib/u_async.h"
#include "../utun_instance.h"
#include "../transport_layer/node_conn_direct.h"
#include "stream_optimizer.h"
@@ -12,6 +13,7 @@
#define STREAM_PROBE_CYCLE_MS 30000
#define STREAM_OFFER_TTL_MS 60000
#define STREAM_MIGRATION_TIMEOUT_MS 5000
#define STREAM_BOOTSTRAP_RELAY_MS 100
#define STREAM_PROBLEM_PENALTY_MS 1000
#define STREAM_HOP_PENALTY_MS 3
@@ -44,10 +46,18 @@ struct stream_optimizer {
struct stream_selection_settings settings;
struct stream_selection_info info;
struct stream_probe_schedule schedule;
void *startup_timer;
uint64_t started_ms, startup_peer;
uint64_t roster_ms, progress_ms, next_seq, diagnostic_ms, deficient_ms, relay_poll_ms;
int progress_valid, draining;
int progress_valid, draining, startup_complete;
};
/* Точный срок гонки не зависит от фазы общего 100-мс таймера; selector остаётся отложенным. */
static void startup_timeout(void *arg) {
struct stream_optimizer *o = arg; o->startup_timer = NULL;
stream_network_wake(stream_network_get(o->inst));
}
unsigned stream_probe_schedule_tick(struct stream_probe_schedule *s, uint64_t now, unsigned pending, unsigned active) {
if (!s) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "stream probe schedule missing"); return 0; }
unsigned budget = 1;
@@ -221,12 +231,17 @@ static struct stream_supplier *supplier_choose(struct stream_optimizer *o, unsig
for (struct stream_supplier *s = o->suppliers; s; s = s->next) {
if (!s->online || !s->compatible || s->node == exclude || !s->offers || now < s->retry_ms
|| (s->probe && s->probe_sub == sub)) continue;
if (!o->startup_complete && o->startup_peer && s->node != o->startup_peer) continue;
struct stream_offer *offer = &s->offers[sub]; struct stream_receive_info *info = &offer->info;
if (now < offer->retry_ms || !info->info_ms || now - info->info_ms > STREAM_OFFER_TTL_MS || !info->accepting || !info->free_slots
|| (seq != UINT64_MAX && info->first_available != UINT64_MAX && seq < info->first_available)) continue;
if (!pass && o->descriptor.r && usage(o, s->node, sub) >= o->descriptor.r) continue;
if (!best) { best = s; continue; }
struct stream_receive_info *previous = &best->offers[sub].info;
if (!o->startup_complete && seq == UINT64_MAX) {
if (info->info_order < previous->info_order) best = s;
continue;
}
uint64_t score = stream_supplier_score(info, &info->supplier_metrics, info->supplier_metrics_valid);
uint64_t old_score = stream_supplier_score(previous, &previous->supplier_metrics, previous->supplier_metrics_valid);
int better = score < old_score;
@@ -240,6 +255,18 @@ static struct stream_supplier *supplier_choose(struct stream_optimizer *o, unsig
return best;
}
/* Первый пригодный INFO среди всех субпотоков выигрывает гонку; порядок приёма не зависит от порядка списка пиров. */
static struct stream_supplier *startup_choose(struct stream_optimizer *o, uint64_t now, unsigned *subblock) {
struct stream_supplier *first = NULL; uint64_t order = UINT64_MAX;
for (unsigned sub = 0; sub < o->descriptor.n; sub++) {
struct stream_supplier *s = supplier_choose(o, sub, UINT64_MAX, 0, now);
if (s && s->offers[sub].info.info_order < order) {
first = s; order = s->offers[sub].info.info_order; *subblock = sub;
}
}
return first;
}
static void receive_failed(struct stream_optimizer *o, struct STREAM_RECEIVE **handle, unsigned sub, uint64_t now) {
struct stream_receive_info info; stream_receive_get_info(*handle, &info);
struct stream_supplier *s = supplier_find(o, info.peer);
@@ -273,11 +300,18 @@ int stream_optimizer_create(struct UTUN_INSTANCE *inst, const struct stream_desc
if (o) o->selected = u_calloc(d->n, sizeof(*o->selected));
if (!o || !o->selected) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "stream optimizer allocation failed"); if (o) u_free(o); return STREAM_ERR_MEMORY; }
o->inst = inst; o->descriptor = *d; o->settings = (struct stream_selection_settings){2000, 5000, 20};
o->started_ms = get_time_tb() / 10;
o->startup_timer = uasync_set_timeout(inst->ua, STREAM_BOOTSTRAP_RELAY_MS * 10, o, startup_timeout, "stream_bootstrap");
if (!o->startup_timer) {
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "stream bootstrap timer allocation failed");
u_free(o->selected); u_free(o); return STREAM_ERR_MEMORY;
}
o->info.automatic = 1; *out = o; return STREAM_OK;
}
void stream_optimizer_destroy(struct stream_optimizer *o) {
if (!o) return;
if (o->startup_timer) uasync_cancel_timeout(o->inst->ua, o->startup_timer);
for (unsigned sub = 0; sub < o->descriptor.n; sub++) {
stream_receive_close(o->selected[sub].current); stream_receive_close(o->selected[sub].replacement);
}
@@ -316,6 +350,13 @@ void stream_optimizer_refresh(struct stream_optimizer *o, uint64_t peer) {
void stream_optimizer_tick(struct stream_optimizer *o, struct STREAM_HANDLE *h, uint64_t now) {
struct stream_local local;
if (stream_local_get(o->inst, &o->descriptor, &local) != STREAM_OK) return;
if (local.decoded && !o->startup_complete) {
o->startup_complete = 1;
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "stream bootstrap complete group=%llx source=%llx epoch=%llx peer=%llx elapsed_ms=%llu start=%llu",
(unsigned long long)o->descriptor.group_id, (unsigned long long)o->descriptor.source_node_id,
(unsigned long long)o->descriptor.source_epoch, (unsigned long long)o->startup_peer,
(unsigned long long)(now - o->started_ms), (unsigned long long)local.start);
}
if (local.finished) {
/* END одного поставщика задаёт границу источника; остальные выдачи ещё могут требовать восстановления. */
if (local.last == UINT64_MAX || (local.anchored && local.next > local.last)) {
@@ -385,6 +426,21 @@ void stream_optimizer_tick(struct stream_optimizer *o, struct STREAM_HANDLE *h,
}
int migrating = 0;
for (unsigned sub = 0; sub < o->descriptor.n; sub++) if (o->selected[sub].replacement) migrating = 1;
if (!local.anchored && !busy && !o->startup_complete) {
o->startup_peer = 0;
unsigned sub = 0; struct stream_supplier *s = startup_choose(o, now, &sub);
if (s && stream_receive_open(h, s->node, (uint8_t)sub, UINT64_MAX, &o->selected[sub].current) == STREAM_OK) {
o->startup_peer = s->node; o->selected[sub].opened = 1; o->selected[sub].opened_ms = now; busy++;
if (o->startup_timer) { uasync_cancel_timeout(o->inst->ua, o->startup_timer); o->startup_timer = NULL; }
for (struct stream_supplier *other = o->suppliers; other; other = other->next)
if (other != s && other->probe) { stream_receive_close(other->probe); other->probe = NULL; }
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "stream bootstrap winner group=%llx source=%llx epoch=%llx peer=%llx sub=%u"
" order=%llu elapsed_ms=%llu relay=%d",
(unsigned long long)o->descriptor.group_id, (unsigned long long)o->descriptor.source_node_id,
(unsigned long long)o->descriptor.source_epoch, (unsigned long long)s->node, sub,
(unsigned long long)s->offers[sub].info.info_order, (unsigned long long)(now - o->started_ms), s->relay);
}
}
for (unsigned sub = 0; sub < o->descriptor.n; sub++) {
struct stream_selection *pick = &o->selected[sub]; struct stream_receive_info info = {0};
if (pick->replacement || (!local.anchored && busy)) continue;
@@ -476,9 +532,11 @@ void stream_optimizer_tick(struct stream_optimizer *o, struct STREAM_HANDLE *h,
int deficient = active < required || (viable < required && newer);
if (!deficient) o->deficient_ms = 0;
else if (!o->deficient_ms) o->deficient_ms = now;
if (now >= o->relay_poll_ms) {
if (!o->startup_complete || now >= o->relay_poll_ms) {
o->relay_poll_ms = now + STREAM_PROBE_PERIOD_MS;
int needed = deficient && now - o->deficient_ms >= STREAM_MIGRATION_TIMEOUT_MS;
if (!o->startup_complete && !o->startup_peer && now - o->started_ms >= STREAM_BOOTSTRAP_RELAY_MS) needed = 1;
if (!o->startup_complete && o->startup_peer && stream_relay_confirmed(o->inst, &o->descriptor, o->startup_peer)) needed = 1;
stream_relay_demand(o->inst, &o->descriptor, needed, now);
}
/* Пока нет первого блока, раз в секунду показываем причину ожидания каждого поставщика. */