|
|
|
@ -1444,6 +1444,100 @@ static void test_delayed_media_reader(void) { |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "[PASS] delayed media readers: 6s pause, live progress without restart, exact remote/local FIN drain"); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "[PASS] delayed media readers: 6s pause, live progress without restart, exact remote/local FIN drain"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static struct { |
|
|
|
|
|
|
|
etcp_recv_fn viewer_receive, source_receive; |
|
|
|
|
|
|
|
struct ll_entry *held_info; |
|
|
|
|
|
|
|
struct ETCP_CONN *held_conn; |
|
|
|
|
|
|
|
unsigned data, ends, renews; |
|
|
|
|
|
|
|
int hold_source; |
|
|
|
|
|
|
|
} lifecycle; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Первый INFO источника задержан до DATA неполного ретранслятора; остальные пакеты не теряются. */ |
|
|
|
|
|
|
|
static void lifecycle_view_raw(struct ETCP_CONN *conn, struct ll_entry *entry) { |
|
|
|
|
|
|
|
if (lifecycle.hold_source && conn->peer_node_id == nodes[0]->node_id |
|
|
|
|
|
|
|
&& entry->len >= STREAM_WIRE_HEADER && entry->dgram[2] == STREAM_CMD_INFO_RESP) { |
|
|
|
|
|
|
|
CHECK(!lifecycle.held_info); |
|
|
|
|
|
|
|
lifecycle.held_info = entry; lifecycle.held_conn = conn; return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
lifecycle.viewer_receive(conn, entry); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void lifecycle_source_raw(struct ETCP_CONN *conn, struct ll_entry *entry) { |
|
|
|
|
|
|
|
if (conn->peer_node_id == nodes[2]->node_id && entry->len >= STREAM_WIRE_HEADER |
|
|
|
|
|
|
|
&& entry->dgram[2] == STREAM_CMD_RENEW) lifecycle.renews++; |
|
|
|
|
|
|
|
lifecycle.source_receive(conn, entry); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void lifecycle_event(struct UTUN_INSTANCE *inst, const struct stream_event *event, void *arg) { |
|
|
|
|
|
|
|
(void)inst; (void)arg; |
|
|
|
|
|
|
|
if (event->type == STREAM_EVENT_DATA) { |
|
|
|
|
|
|
|
CHECK(event->first_seq == 0 && event->length == sizeof(expected)); |
|
|
|
|
|
|
|
CHECK(!memcmp(event->data, expected, sizeof(expected))); lifecycle.data++; |
|
|
|
|
|
|
|
} else if (event->type == STREAM_EVENT_END) lifecycle.ends++; |
|
|
|
|
|
|
|
else CHECK(event->type == STREAM_EVENT_CLOSED); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Первый поставщик имеет лишь 3 из 6 нужных частей; пауза источника не оправдывает зависший bootstrap. */ |
|
|
|
|
|
|
|
static void test_partial_bootstrap_and_end(void) { |
|
|
|
|
|
|
|
struct STREAM_HANDLE *publisher = NULL, *relay = NULL, *viewer = NULL; |
|
|
|
|
|
|
|
CHECK(stream_publish_open(nodes[0], GROUP1, NULL, &publisher) == STREAM_OK); |
|
|
|
|
|
|
|
struct stream_info origin; CHECK(stream_get_info(publisher, &origin) == STREAM_OK); |
|
|
|
|
|
|
|
wait_state(1, 0, GROUP1, origin.descriptor.source_epoch, 0); |
|
|
|
|
|
|
|
wait_state(2, 0, GROUP1, origin.descriptor.source_epoch, 0); |
|
|
|
|
|
|
|
CHECK(stream_subscribe_manual_open(nodes[1], GROUP1, nodes[0]->node_id, &relay) == STREAM_OK); |
|
|
|
|
|
|
|
wait_state(2, 1, GROUP1, origin.descriptor.source_epoch, 1); |
|
|
|
|
|
|
|
memset(expected, 0x37, sizeof(expected)); |
|
|
|
|
|
|
|
CHECK(stream_publish_block(publisher, expected, sizeof(expected)) == STREAM_OK); |
|
|
|
|
|
|
|
struct stream_local source, received; |
|
|
|
|
|
|
|
CHECK(stream_local_get(nodes[0], &origin.descriptor, &source) == STREAM_OK); |
|
|
|
|
|
|
|
for (unsigned sub = 0; sub < 3; sub++) { |
|
|
|
|
|
|
|
const uint8_t *part; size_t length; uint64_t actual; |
|
|
|
|
|
|
|
CHECK(stream_buffer_read(source.buffer, 0, sub, &part, &length, &actual) == STREAM_OK); |
|
|
|
|
|
|
|
CHECK(stream_ingest_block(nodes[1], part, length) == STREAM_OK); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
lifecycle.viewer_receive = nodes[2]->api_bindings.callbacks[ETCP_ID_STREAM]; |
|
|
|
|
|
|
|
lifecycle.source_receive = nodes[0]->api_bindings.callbacks[ETCP_ID_STREAM]; |
|
|
|
|
|
|
|
CHECK(etcp_bind(nodes[2], ETCP_ID_STREAM, lifecycle_view_raw) == 0); |
|
|
|
|
|
|
|
CHECK(etcp_bind(nodes[0], ETCP_ID_STREAM, lifecycle_source_raw) == 0); |
|
|
|
|
|
|
|
stream_set_event_callback(nodes[2], lifecycle_event, NULL); lifecycle.hold_source = 1; |
|
|
|
|
|
|
|
CHECK(stream_subscribe_open(nodes[2], GROUP1, nodes[0]->node_id, &viewer) == STREAM_OK); |
|
|
|
|
|
|
|
uint64_t deadline = get_time_tb() + 40000; |
|
|
|
|
|
|
|
do { |
|
|
|
|
|
|
|
test_clock_poll(ua, 100); |
|
|
|
|
|
|
|
CHECK(stream_local_get(nodes[2], &origin.descriptor, &received) == STREAM_OK); |
|
|
|
|
|
|
|
} while (!received.anchored && get_time_tb() < deadline); |
|
|
|
|
|
|
|
CHECK(received.anchored && !received.decoded && lifecycle.held_info); |
|
|
|
|
|
|
|
lifecycle.hold_source = 0; |
|
|
|
|
|
|
|
struct ll_entry *held = lifecycle.held_info; lifecycle.held_info = NULL; |
|
|
|
|
|
|
|
lifecycle.viewer_receive(lifecycle.held_conn, held); |
|
|
|
|
|
|
|
run_until(&lifecycle.data, 1); |
|
|
|
|
|
|
|
CHECK(stream_local_get(nodes[2], &origin.descriptor, &received) == STREAM_OK); |
|
|
|
|
|
|
|
CHECK(received.start == 0 && received.next == 1 && received.decoded); |
|
|
|
|
|
|
|
struct stream_selection_info selected; CHECK(stream_selection_get_info(viewer, &selected) == STREAM_OK); |
|
|
|
|
|
|
|
CHECK(!selected.restarts && selected.providers == 2); |
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "[PASS] partial bootstrap: complementary source parts decoded original batch without restart"); |
|
|
|
|
|
|
|
/* END не зависит от существования relay; оставляем только прямую выдачу, чтобы измерять её RENEW. */ |
|
|
|
|
|
|
|
stream_close(viewer); stream_close(relay); viewer = NULL; |
|
|
|
|
|
|
|
test_clock_run_for(ua, 10000); |
|
|
|
|
|
|
|
CHECK(stream_subscribe_open(nodes[2], GROUP1, nodes[0]->node_id, &viewer) == STREAM_OK); |
|
|
|
|
|
|
|
run_until(&lifecycle.data, 2); test_clock_run_for(ua, 20000); |
|
|
|
|
|
|
|
CHECK(stream_publish_finish(publisher) == STREAM_OK); run_until(&lifecycle.ends, 1); |
|
|
|
|
|
|
|
test_clock_run_for(ua, 2000); lifecycle.renews = 0; |
|
|
|
|
|
|
|
test_clock_run_for(ua, 100000); |
|
|
|
|
|
|
|
CHECK(stream_selection_get_info(viewer, &selected) == STREAM_OK); |
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "finished reader retained: renews=%u flows=%u overlaps=%u probes=%u providers=%u", |
|
|
|
|
|
|
|
lifecycle.renews, selected.subflows, selected.overlaps, selected.probes_active, selected.providers); |
|
|
|
|
|
|
|
CHECK(!lifecycle.renews && !selected.subflows && !selected.overlaps && !selected.probes_active && !selected.providers); |
|
|
|
|
|
|
|
CHECK(stream_handle_active(viewer)); |
|
|
|
|
|
|
|
CHECK(stream_local_get(nodes[2], &origin.descriptor, &received) == STREAM_OK && received.next == 1 && received.finished); |
|
|
|
|
|
|
|
struct ETCP_CONN *shared = instance_find_conn(nodes[2], nodes[0]->node_id); CHECK(shared && shared->links_up); |
|
|
|
|
|
|
|
stream_close(viewer); stream_close(publisher); test_clock_run_for(ua, 1000); |
|
|
|
|
|
|
|
CHECK(etcp_bind(nodes[2], ETCP_ID_STREAM, lifecycle.viewer_receive) == 0); |
|
|
|
|
|
|
|
CHECK(etcp_bind(nodes[0], ETCP_ID_STREAM, lifecycle.source_receive) == 0); |
|
|
|
|
|
|
|
stream_set_event_callback(nodes[2], NULL, NULL); |
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "[PASS] completed receive releases upstream while retaining reader and shared transport"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
int main(int argc, char **argv) { |
|
|
|
int main(int argc, char **argv) { |
|
|
|
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); |
|
|
|
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); |
|
|
|
debug_set_category_level(DEBUG_CATEGORY_STREAM, DEBUG_LEVEL_DEBUG); |
|
|
|
debug_set_category_level(DEBUG_CATEGORY_STREAM, DEBUG_LEVEL_DEBUG); |
|
|
|
@ -1508,8 +1602,9 @@ int main(int argc, char **argv) { |
|
|
|
CHECK(member_sync_start(nodes[0], nodes[i]->node_id, "710001", NULL, NULL) == 0); |
|
|
|
CHECK(member_sync_start(nodes[0], nodes[i]->node_id, "710001", NULL, NULL) == 0); |
|
|
|
CHECK(member_sync_start(nodes[i], nodes[0]->node_id, "710001", NULL, NULL) == 0); |
|
|
|
CHECK(member_sync_start(nodes[i], nodes[0]->node_id, "710001", NULL, NULL) == 0); |
|
|
|
} |
|
|
|
} |
|
|
|
if (argc == 2 && !strcmp(argv[1], "--relay-only")) { |
|
|
|
if (argc == 2 && (!strcmp(argv[1], "--relay-only") || !strcmp(argv[1], "--lifecycle-only"))) { |
|
|
|
test_supernode_relay(); |
|
|
|
if (!strcmp(argv[1], "--relay-only")) test_supernode_relay(); |
|
|
|
|
|
|
|
else test_partial_bootstrap_and_end(); |
|
|
|
for (unsigned i = 0; i < 3; i++) topo_group_peer_close(connections[i]); |
|
|
|
for (unsigned i = 0; i < 3; i++) topo_group_peer_close(connections[i]); |
|
|
|
for (unsigned i = 0; i < 4; i++) { stream_set_event_callback(nodes[i], NULL, NULL); utun_instance_destroy(nodes[i]); } |
|
|
|
for (unsigned i = 0; i < 4; i++) { stream_set_event_callback(nodes[i], NULL, NULL); utun_instance_destroy(nodes[i]); } |
|
|
|
uasync_destroy(ua, 1); test_clock_finish(); |
|
|
|
uasync_destroy(ua, 1); test_clock_finish(); |
|
|
|
@ -1644,6 +1739,7 @@ int main(int argc, char **argv) { |
|
|
|
CHECK(data_events[0] == 1); /* Pending 100ms batch не выполнилась после stop. */ |
|
|
|
CHECK(data_events[0] == 1); /* Pending 100ms batch не выполнилась после stop. */ |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "[PASS] CHAT stop keeps UTUN/core; source timer cancelled; stale signed options cleared on restart"); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "[PASS] CHAT stop keeps UTUN/core; source timer cancelled; stale signed options cleared on restart"); |
|
|
|
for (unsigned i = 0; i < 4; i++) stream_set_event_callback(nodes[i], NULL, NULL); |
|
|
|
for (unsigned i = 0; i < 4; i++) stream_set_event_callback(nodes[i], NULL, NULL); |
|
|
|
|
|
|
|
test_partial_bootstrap_and_end(); |
|
|
|
test_delayed_media_reader(); |
|
|
|
test_delayed_media_reader(); |
|
|
|
for (unsigned i = 0; i < 3; i++) topo_group_peer_close(connections[i]); |
|
|
|
for (unsigned i = 0; i < 3; i++) topo_group_peer_close(connections[i]); |
|
|
|
for (unsigned i = 0; i < 4; i++) utun_instance_destroy(nodes[i]); |
|
|
|
for (unsigned i = 0; i < 4; i++) utun_instance_destroy(nodes[i]); |
|
|
|
|