Browse Source

Restart stream receives and expose confirmed delivery gaps

master
evgeny 2 days ago
parent
commit
fc80186043
  1. 36
      src/stream/stream.c
  2. 30
      src/stream/stream_network.c
  3. 3
      src/stream/stream_network.h
  4. 36
      tests/test_stream_service.c

36
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)) {

30
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;

3
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);

36
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-дерево. */

Loading…
Cancel
Save