diff --git a/src/stream/stream.c b/src/stream/stream.c index f31d590b..48402b0e 100644 --- a/src/stream/stream.c +++ b/src/stream/stream.c @@ -21,7 +21,7 @@ struct stream_session { uint8_t *output; void *delivery; uint64_t next_seq; - uint64_t start_seq, last_seq; + uint64_t start_seq, last_seq, passed_before; uint8_t end[STREAM_END_SIZE]; int end_valid; unsigned owners, holds; @@ -162,6 +162,14 @@ static void session_deliver(void *arg) { } else if (result == STREAM_EXPIRED) { event.type = STREAM_EVENT_GAP; event.first_seq = s->next_seq; event.last_seq = stats.expired_before - 1; s->next_seq = stats.expired_before; emit = 1; again = 1; + } else if ((result == STREAM_WAIT || result == STREAM_GAP) && s->next_seq < s->passed_before) { + uint64_t next = s->passed_before, decoded; + if (stream_buffer_next_decodable(s->buffer, s->next_seq + 1, &decoded) == STREAM_OK && decoded < next) next = decoded; + event.type = STREAM_EVENT_GAP; event.first_seq = s->next_seq; event.last_seq = next - 1; + s->next_seq = next; emit = 1; again = 1; + DEBUG_INFO(DEBUG_CATEGORY_STREAM, "stream consumer GAP source=%llx first=%llu last=%llu next=%llu", + (unsigned long long)s->descriptor.source_node_id, (unsigned long long)event.first_seq, + (unsigned long long)event.last_seq, (unsigned long long)next); } else if (result < 0) { session_close(s, STREAM_CLOSE_FAILED, 1, 1); } else if (s->finished && s->end_valid && s->next_seq > s->last_seq && !s->end_delivered) { @@ -523,7 +531,7 @@ int stream_ingest_path(struct UTUN_INSTANCE *inst, const uint8_t *wire, size_t l } struct stream_path retained = *path; retained.nodes[retained.count++] = inst->node_id; int result = stream_buffer_put_path(s->buffer, wire, length, &retained); - if (result == STREAM_OK) { + if (result == STREAM_OK || (result == STREAM_DUPLICATE && !s->anchored)) { if (!s->anchored) { s->anchored = 1; s->start_seq = s->next_seq = stream_read_be(wire + STREAM_BLOCK_SEQ_OFF, 8); } session_schedule(s); stream_network_changed(s->service->network); @@ -545,7 +553,7 @@ int stream_local_get(struct UTUN_INSTANCE *inst, const struct stream_descriptor /* Выданный локальный handle проверяется до создания сетевых ресурсов. */ int stream_handle_context(struct STREAM_HANDLE *h, struct UTUN_INSTANCE **inst, struct stream_descriptor *d) { - if (!inst || !d || !stream_handle_active(h) || h->publisher) { + if (!inst || !d || !stream_handle_active(h) || h->session->source) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "network receive requires active reader handle"); return STREAM_ERR_STATE; } *inst = h->service->inst; *d = h->session->descriptor; return STREAM_OK; @@ -567,6 +575,28 @@ int stream_ingest_end(struct UTUN_INSTANCE *inst, const struct stream_descriptor session_schedule(s); stream_network_changed(inst->stream->network); return STREAM_OK; } +/* Все выбранные upstream прошли эту границу; дальше старую неполную пачку они уже не выдадут. */ +void stream_local_progress(struct UTUN_INSTANCE *inst, const struct stream_descriptor *key, uint64_t before) { + struct stream_local local; + if (stream_local_get(inst, key, &local) != STREAM_OK || local.publisher) return; + struct stream_session *s = session_find(inst->stream, key->group_id, key->source_node_id); + if (before > s->passed_before) { s->passed_before = before; session_schedule(s); } +} + +/* Явный новый старт не смешивается с DATA старых upstream handles; кольцо остаётся пригодным для relay. */ +int stream_local_restart(struct UTUN_INSTANCE *inst, const struct stream_descriptor *key) { + struct stream_local local; + if (stream_local_get(inst, key, &local) != STREAM_OK || local.publisher) { + DEBUG_WARN(DEBUG_CATEGORY_STREAM, "stream restart requires local receiver"); return STREAM_ERR_STATE; + } + struct stream_session *s = session_find(inst->stream, key->group_id, key->source_node_id); + if (s->delivery) { uasync_call_soon_cancel(inst->ua, s->delivery); s->delivery = NULL; } + s->anchored = s->end_delivered = 0; s->passed_before = s->next_seq = s->start_seq = 0; + DEBUG_INFO(DEBUG_CATEGORY_STREAM, "stream receiver restart from end group=%llx source=%llx epoch=%llx retained=%u", + (unsigned long long)key->group_id, (unsigned long long)key->source_node_id, + (unsigned long long)key->source_epoch, local.buffer != NULL); return STREAM_OK; +} + /* Выборка идёт из live BGP-путей группы, не из nodes.online или глобальных соединений. */ int stream_get_candidates(const struct STREAM_HANDLE *h, struct stream_candidate **out, size_t *count) { if (!out || *out || !count || !stream_handle_active(h)) { diff --git a/src/stream/stream_network.c b/src/stream/stream_network.c index 0a533aa4..576190d2 100644 --- a/src/stream/stream_network.c +++ b/src/stream/stream_network.c @@ -312,6 +312,20 @@ static int raw_send(struct stream_peer *p, struct stream_message m, const uint8_ return STREAM_OK; } +/* Нижняя граница прогресса всех выбранных выдач. INFO/REQUESTED ещё могут принести старую пачку и запрещают скачок. */ +static void receive_progress(struct stream_network *net, const struct stream_descriptor *d) { + uint8_t seen[RS_MAX_BLOCKS] = {0}; unsigned distinct = 0; uint64_t before = UINT64_MAX; + for (struct stream_peer *p = net->peers; p; p = p->next) { + for (struct STREAM_RECEIVE *f = p->flows; f; f = f->next) { + if (!f->outgoing || f->info.state == STREAM_RECEIVE_CLOSED || !same_stream(&f->descriptor, d)) continue; + if (f->info.state < STREAM_RECEIVE_ACTIVE) return; + if (!seen[f->subblock]) { seen[f->subblock] = 1; distinct++; } + if (f->info.next < before) before = f->info.next; + } + } + if (distinct >= (unsigned)(d->n - d->r)) stream_local_progress(net->inst, d, before); +} + /* Производитель не хранит готовые DATA: cursor проверяется непосредственно перед копированием в ETCP. */ static int downstream_send(struct STREAM_RECEIVE *f) { struct stream_peer *p = f->peer; struct stream_local local; @@ -564,6 +578,7 @@ static void dispatch(struct stream_network *net, uint64_t sender, const uint8_t flow_close(f, m.payload[0], 0); else DEBUG_DEBUG(DEBUG_CATEGORY_STREAM, "stream unknown/inactive issuance peer=%llx command=%u handle=%llu sub=%u bytes=%zu", (unsigned long long)sender, m.command, (unsigned long long)m.handle, m.subblock, m.length); + receive_progress(net, &local.descriptor); p->busy--; peer_sweep(p); } @@ -733,6 +748,21 @@ void stream_receive_close(struct STREAM_RECEIVE *f) { flow_close(f, STREAM_REASON_STOPPED, 1); peer_sweep(p); } +/* Смена стартовой попытки инвалидирует только upstream этой трансляции; номера новых SUBSCRIBE продолжают возрастать. */ +int stream_receive_restart(struct STREAM_HANDLE *subscription) { + struct UTUN_INSTANCE *inst; struct stream_descriptor d; + if (stream_handle_context(subscription, &inst, &d) != STREAM_OK) return STREAM_ERR_STATE; + struct stream_network *net = stream_network_get(inst); + if (!net) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "stream restart without network"); return STREAM_ERR_STATE; } + for (struct stream_peer *p = net->peers; p; p = p->next) { + p->busy++; + for (struct STREAM_RECEIVE *f = p->flows; f; f = f->next) + if (f->outgoing && same_stream(&f->descriptor, &d)) flow_close(f, STREAM_REASON_STOPPED, 1); + p->busy--; peer_sweep(p); + } + return stream_local_restart(inst, &d); +} + int stream_receive_get_info(const struct STREAM_RECEIVE *f, struct stream_receive_info *info) { if (!f || !info) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "stream receive info invalid arguments"); return STREAM_ERR_ARGUMENT; } *info = f->info; return STREAM_OK; diff --git a/src/stream/stream_network.h b/src/stream/stream_network.h index 2d7b4a5f..e45c17e2 100644 --- a/src/stream/stream_network.h +++ b/src/stream/stream_network.h @@ -21,6 +21,9 @@ struct stream_receive_info { * peer должен быть онлайн-кандидатом группы. Повтор peer+stream+sub не создаёт вторую выдачу. */ int stream_receive_open(struct STREAM_HANDLE *subscription, uint64_t peer, uint8_t subblock, uint64_t seq, struct STREAM_RECEIVE **out); void stream_receive_close(struct STREAM_RECEIVE *receive); +/* Отменить текущие upstream данного локального экземпляра и начать новую попытку с конца. + * Буфер/подписка/downstream сохраняются; старые receive handles становятся CLOSED и всё равно требуют close. */ +int stream_receive_restart(struct STREAM_HANDLE *subscription); int stream_receive_get_info(const struct STREAM_RECEIVE *receive, struct stream_receive_info *info); int stream_network_init(struct UTUN_INSTANCE *inst, struct stream_network **out); diff --git a/tests/test_stream_service.c b/tests/test_stream_service.c index 624c3f34..84f2c8eb 100644 --- a/tests/test_stream_service.c +++ b/tests/test_stream_service.c @@ -33,9 +33,11 @@ static struct STREAM_HANDLE *reader1, *reader2; static unsigned data_events[4], end_events[4], closed_events[4]; static int close_readers_in_callback; static unsigned network_data[4], network_end[4], accepts, reverse_requests, dropped_renew; +static unsigned network_gaps[4]; +static uint64_t network_next[4] = {0, 1, 1, 0}; static uint8_t first_request[STREAM_WIRE_HEADER + 8]; static etcp_recv_fn saved_raw[4], saved_route; -static int drop_renew; +static int drop_renew, gap_batch; /* Протокол проходит настоящие ETCP/router обработчики; перехват только считает команды и имитирует потерю RENEW. */ static void network_raw(struct ETCP_CONN *conn, struct ll_entry *entry) { @@ -46,6 +48,17 @@ static void network_raw(struct ETCP_CONN *conn, struct ll_entry *entry) { CHECK(entry->len == sizeof(first_request)); memcpy(first_request, entry->dgram, sizeof(first_request)); } if (node == 1 && command == STREAM_CMD_ACCEPT) accepts++; + /* Дыра поставщика внутри его окна: заменить DATA пачки 2 на честный GAP 2->3, сохранив сессию/handle. */ + if (node == 1 && command == STREAM_CMD_DATA && gap_batch) { + struct stream_message message; struct stream_path path; size_t used; + CHECK(stream_wire_parse(entry->dgram, entry->len, &message) == STREAM_OK); + CHECK(stream_path_parse(message.payload, message.length, &path, &used) == STREAM_OK); + if (stream_read_be(message.payload + used + STREAM_BLOCK_SEQ_OFF, 8) == 2) { + entry->dgram[2] = STREAM_CMD_GAP; entry->len = STREAM_WIRE_HEADER + 16; + stream_write_be(entry->dgram + STREAM_WIRE_HEADER, 2, 8); + stream_write_be(entry->dgram + STREAM_WIRE_HEADER + 8, 3, 8); + } + } if (node == 3 && command == STREAM_CMD_RENEW && drop_renew) { dropped_renew++; queue_dgram_free(entry); queue_entry_free(entry); return; } @@ -64,8 +77,12 @@ static void on_network(struct UTUN_INSTANCE *inst, const struct stream_event *ev CHECK(event->descriptor.group_id == GROUP2 && event->descriptor.source_node_id == nodes[3]->node_id); if (event->type == STREAM_EVENT_DATA) { CHECK(event->length == sizeof(expected) && !memcmp(event->data, expected, sizeof(expected))); - CHECK(event->first_seq == 1 + network_data[node]); network_data[node]++; + CHECK(event->first_seq == network_next[node]++); network_data[node]++; } else if (event->type == STREAM_EVENT_END) network_end[node]++; + else if (event->type == STREAM_EVENT_GAP) { + CHECK(event->first_seq == network_next[node] && event->last_seq == 2); + network_next[node] = event->last_seq + 1; network_gaps[node]++; + } else CHECK(event->type == STREAM_EVENT_CLOSED); } @@ -203,6 +220,15 @@ static void test_network(void) { } CHECK(info.blocks == 1 && info.start == 1 && info.next == 2 && info.handle); } + /* Новый bootstrap может получить уже сохранённый блок: DUPLICATE обязан установить общий старт. */ + CHECK(stream_receive_restart(subscribers[1]) == STREAM_OK); + network_data[1] = 0; network_next[1] = 1; first_request[0] = 0; + for (unsigned i = 0; i < 8; i++) { + struct stream_receive_info info; CHECK(stream_receive_get_info(up[i], &info) == STREAM_OK && info.state == STREAM_RECEIVE_CLOSED); + stream_receive_close(up[i]); up[i] = NULL; + CHECK(stream_receive_open(subscribers[1], nodes[3]->node_id, i, UINT64_MAX, &up[i]) == STREAM_OK); + } + run_until(&network_data[1], 1); /* Node2 видит подписку relay через обычный Merkle-обмен, а не копирование из его SQLite. */ deadline = get_time_tb() + 150000; int ready = 0; @@ -243,10 +269,10 @@ static void test_network(void) { CHECK(etcp_route_send(nodes[1], GROUP2, nodes[3]->node_id, entry, 0, ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT) == 0); } run_until(&reverse_requests, 2); - network_write(publisher, 2); run_until(&network_data[1], 2); run_until(&network_data[2], 2); + gap_batch = 1; network_write(publisher, 2); run_until(&network_gaps[1], 1); gap_batch = 0; /* Снять два лишних проверочных субпотока: шесть оставшихся восстанавливают новые пачки. */ for (unsigned i = 6; i < 8; i++) { stream_receive_close(up[i]); up[i] = NULL; stream_receive_close(down[i]); down[i] = NULL; } - network_write(publisher, 3); run_until(&network_data[1], 3); run_until(&network_data[2], 3); + network_write(publisher, 3); run_until(&network_data[1], 2); run_until(&network_data[2], 2); run_until(&network_gaps[2], 1); CHECK(stream_publish_finish(publisher) == STREAM_OK); run_until(&network_end[1], 1); run_until(&network_end[2], 1); /* RENEW каждые 2с удерживает даже окончившуюся выдачу; затем имитируем отсутствие продлений. */ @@ -271,7 +297,7 @@ static void test_network(void) { test_clock_run_for(ua, 1000); stream_set_event_callback(nodes[1], on_stream, NULL); stream_set_event_callback(nodes[2], NULL, NULL); for (unsigned i = 0; i < 2; i++) topo_group_peer_close(connections[i]); - DEBUG_INFO(DEBUG_CATEGORY_STREAM, "[PASS] live raw ETCP chain: common index, FEC, retained paths/LOOP, duplicate/reverse, END, renew/lease, REINIT"); + DEBUG_INFO(DEBUG_CATEGORY_STREAM, "[PASS] live ETCP chain: common index/restart, FEC, paths/LOOP, GAP, duplicate/reverse, END, leases, REINIT"); } /* Ошибка размера/SQLite не меняет подпись, userinfo, версию и производное Merkle-дерево. */