From c755a6372cf3ec88cc63159c7599c211de649ff2 Mon Sep 17 00:00:00 2001 From: Katerina Kubecova Date: Tue, 15 Oct 2024 11:31:32 +0200 Subject: [PATCH] Proto: adding lockless state table and journal To allow reading of protocol states from other protocols or completely different routines, we have to export these states to data structures not requiring to lock the protocol loops. On one hand, this doesn't give the reader the actual state "right now", on the other hand, getting that is impossible in a properly multithreaded environment and you will always get the information with some (little but noteworthy) delay. This implementation handles only the basic state information of the protocols, common for all the protocols. Adding protocol-specific state information should be done by implementing the protocol hook init_state(). Channel information is stored but not announced, as we don't need the announcements for now. --- lib/route.h | 4 + nest/proto.c | 224 ++++++++++++++++++++++++++++++++++++++++++++++++ nest/protocol.h | 51 +++++++++++ nest/rt-attr.c | 77 +++++++++++++++++ 4 files changed, 356 insertions(+) diff --git a/lib/route.h b/lib/route.h index de15cfb2..66cf20dc 100644 --- a/lib/route.h +++ b/lib/route.h @@ -466,6 +466,10 @@ extern struct ea_class ea_gen_from; extern struct ea_class ea_gen_mpls_label, ea_gen_mpls_policy, ea_gen_mpls_class; +/* protocol journal attributes */ +extern struct ea_class ea_name, ea_protocol_name, ea_protocol_type, ea_main_table_id, + ea_state, ea_last_modified, ea_info, ea_proto_id, ea_channel_id, ea_rtable, + ea_in_keep, ea_proto_channel_list; /* Source: An old method to devise the route source protocol and kind. * To be superseded in a near future by something more informative. */ diff --git a/nest/proto.c b/nest/proto.c index 2bb0766e..6f9923f0 100644 --- a/nest/proto.c +++ b/nest/proto.c @@ -44,10 +44,13 @@ static u32 graceful_restart_locks; static char *p_states[] = { "DOWN", "START", "UP", "STOP" }; static char *c_states[] = { "DOWN", "START", "UP", "STOP", "RESTART" }; +proto_state_table proto_state_table_pub; + extern struct protocol proto_unix_iface; static void proto_rethink_goal(struct proto *p); static char *proto_state_name(struct proto *p); +void proto_journal_item_cleanup(struct lfjour * journal UNUSED, struct lfjour_item *i); static void channel_init_limit(struct channel *c, struct limit *l, int dir, struct channel_limit *cf); static void channel_update_limit(struct channel *c, struct limit *l, int dir, struct channel_limit *cf); static void channel_reset_limit(struct channel *c, struct limit *l, int dir); @@ -239,6 +242,48 @@ proto_add_channel(struct proto *p, struct channel_config *cf) init_list(&c->roa_subscriptions); + /* Announcing existence of the channel */ + PST_LOCKED(ts) + { + /* Allocating channel ID */ + c->id = hmap_first_zero(&ts->channel_id_map); + hmap_set(&ts->channel_id_map, c->id); + + /* The current channel state table may be too small */ + if (c->id >= ts->length_channels) + { + ea_list **l = mb_allocz(ts->pool, sizeof(ea_list*) * ts->length_channels * 2); + memcpy(l, ts->channels, sizeof(ea_list*) * ts->length_channels); + mb_free(ts->channels); + + ts->channels = l; + ts->length_channels = ts->length_channels * 2; + } + + /* Create the actual channel information */ + struct ea_list *ca = NULL; + + ea_set_attr(&ca, EA_LITERAL_STORE_STRING(&ea_name, 0, c->name)); + ea_set_attr(&ca, EA_LITERAL_EMBEDDED(&ea_proto_id, 0, c->proto->id)); + ea_set_attr(&ca, EA_LITERAL_EMBEDDED(&ea_channel_id, 0, c->id)); + ea_set_attr(&ca, EA_LITERAL_EMBEDDED(&ea_in_keep, 0, c->in_keep)); + ea_set_attr(&ca, EA_LITERAL_STORE_PTR(&ea_rtable, 0, c->table)); + + ASSERT_DIE(c->id < ts->length_channels); + ASSERT_DIE(ts->channels[c->id] == NULL); + ts->channels[c->id] = ea_lookup_slow(ca, 0, EALS_IN_TABLE); + + /* Update channel list in protocol state */ + ASSERT_DIE(c->proto->id < ts->length_states); + ea_list *eal = ts->states[c->proto->id]; + + ea_set_attr(&eal, + EA_LITERAL_DIRECT_ADATA(&ea_proto_channel_list, 0, int_set_add( + tmp_linpool, ea_get_adata(ts->states[c->proto->id], &ea_proto_channel_list), c->id))); + + proto_announce_state_locked(ts, c->proto, eal); + } + CALL(c->class->init, c, cf); add_tail(&p->channels, &c->n); @@ -255,6 +300,22 @@ proto_remove_channel(struct proto *p UNUSED, struct channel *c) CD(c, "Removed", c->name); + ea_list *eal = p->ea_state; + + ea_set_attr(&eal, + EA_LITERAL_DIRECT_ADATA(&ea_proto_channel_list, 0, int_set_del( + tmp_linpool, ea_get_adata(eal, &ea_proto_channel_list), c->id))); + + proto_announce_state(c->proto, eal); + + PST_LOCKED(ts) + { + ASSERT_DIE(c->id < ts->length_channels); + ea_free_later(ts->channels[c->id]); + ts->channels[c->id] = NULL; + hmap_clear(&ts->channel_id_map, c->id); + } + rt_unlock_table(c->table); rem_node(&c->n); mb_free(c); @@ -1275,8 +1336,38 @@ proto_new(struct proto_config *cf) p->hash_key = random_u32(); cf->proto = p; + PST_LOCKED(tp) + { + p->id = hmap_first_zero(&tp->proto_id_map); + hmap_set(&tp->proto_id_map, p->id); + + if (p->id >= tp->length_states) + { + /* Grow the states array */ + ea_list **new_states = mb_allocz(tp->pool, sizeof *new_states * tp->length_states * 2); + memcpy(new_states, tp->states, tp->length_states * sizeof *new_states); + + mb_free(tp->states); + tp->states = new_states; + } + } + init_list(&p->channels); + /* + Making first version of proto eatters. + */ + struct ea_list *state = NULL; + + ea_set_attr(&state, EA_LITERAL_STORE_STRING(&ea_name, 0, p->name)); + ea_set_attr(&state, EA_LITERAL_STORE_PTR(&ea_protocol_type, 0, &p->proto)); + ea_set_attr(&state, EA_LITERAL_EMBEDDED(&ea_state, 0, p->proto_state)); + ea_set_attr(&state, EA_LITERAL_STORE_ADATA(&ea_last_modified, 0, &p->last_state_change, sizeof(btime))); + ea_set_attr(&state, EA_LITERAL_EMBEDDED(&ea_proto_id, 0, p->id)); + ea_set_attr(&state, EA_LITERAL_STORE_ADATA(&ea_proto_channel_list, 0, NULL, 0)); + + proto_announce_state(p, state); + return p; } @@ -1718,6 +1809,8 @@ proto_rethink_goal(struct proto *p) struct proto_config *nc = p->cf_new; struct proto *after = p->n.prev; + proto_announce_state(p, NULL); + DBG("%s has shut down for reconfiguration\n", p->name); p->cf->proto = NULL; OBSREF_CLEAR(p->global_config); @@ -2011,6 +2104,33 @@ protos_build(void) { proto_pool = rp_new(&root_pool, the_bird_domain.the_bird, "Protocols"); + proto_state_table_pub.lock = DOMAIN_NEW(rtable); + + /* Init proto_state_table */ + pool *p = rp_new(&root_pool, the_bird_domain.the_bird, "Proto state table"); + + PST_LOCKED(ts) + { + ts->length_channels = 64; + ts->length_states = 32; + + hmap_init(&ts->proto_id_map, p, ts->length_states); /* for proto ids. Value of proto id is the same as index of that proto in ptoto_state_table->attrs */ + hmap_init(&ts->channel_id_map, p, ts->length_channels); + + ts->pool = p; + ts->states = mb_allocz(p, sizeof(ea_list *) * ts->length_states); + ts->channels = mb_allocz(p, sizeof(ea_list *) * ts->length_channels * 2); + } + + /* Init proto state journal */ + struct settle_config cf = {.min = 0, .max = 0}; + proto_state_table_pub.journal.item_done = proto_journal_item_cleanup; + proto_state_table_pub.journal.item_size = sizeof(struct proto_pending_update); + proto_state_table_pub.journal.loop = birdloop_new(&root_pool, DOMAIN_ORDER(service), 1, "proto journal loop"); + proto_state_table_pub.journal.domain = proto_state_table_pub.lock.rtable; + + lfjour_init(&proto_state_table_pub.journal, &cf); + protos_build_gen(); } @@ -2308,6 +2428,10 @@ proto_notify_state(struct proto *p, uint state) p->proto_state = state; p->last_state_change = current_time(); + ea_list *eal = p->ea_state; + ea_set_attr(&eal, EA_LITERAL_EMBEDDED(&ea_state, 0, p->proto_state)); + proto_announce_state(p, eal); + switch (state) { case PS_START: @@ -2779,3 +2903,103 @@ proto_iterate_named(struct symbol *sym, struct protocol *proto, struct proto *ol return NULL; } } + +static void +proto_journal_item_cleanup_(ea_list *proto_attr, ea_list *old_attr) +{ + ea_free_later(old_attr); + + if (!proto_attr) + { + PST_LOCKED(tp) + { + int p_id = ea_get_int(old_attr, &ea_proto_id, 0); + hmap_clear(&tp->proto_id_map, p_id); + tp->states[p_id] = NULL; + } + } +} + +void +proto_journal_item_cleanup(struct lfjour * journal UNUSED, struct lfjour_item *i) +{ + /* Called after a journal update was has been read. */ + struct proto_pending_update *pupdate = SKIP_BACK(struct proto_pending_update, li, i); + proto_journal_item_cleanup_(pupdate->proto_attr, pupdate->old_proto_attr); +} + +void +proto_announce_state_locked(struct proto_state_table_private* ts, struct proto *p, ea_list *attr) +{ + /* + Should be called each time one (or more) variables tracked in proto eattrs changes. + Changes proto eattrs and activates journal. + */ + ea_set_attr(&attr, EA_LITERAL_STORE_ADATA(&ea_last_modified, 0, &p->last_state_change, sizeof(btime))); + + attr = ea_lookup(attr, 0, EALS_CUSTOM); + + ASSERT_DIE(p->id < ts->length_states); + ea_list *old_attr = ts->states[p->id]; + ts->states[p->id] = attr; + + p->ea_state = attr; + + struct proto_pending_update *pupdate = SKIP_BACK(struct proto_pending_update, li, lfjour_push_prepare(&proto_state_table_pub.journal)); + + if (!pupdate) + { + proto_journal_item_cleanup_(attr, old_attr); + return; + } + + *pupdate = (struct proto_pending_update) { + .li = pupdate->li, /* Keep the item's internal state */ + .proto_attr = attr, + .old_proto_attr = old_attr, + .protocol = p + }; + + lfjour_push_commit(&proto_state_table_pub.journal); +} + +void +proto_announce_state(struct proto *p, ea_list *attr) +{ + PST_LOCKED(ts) + proto_announce_state_locked(ts, p, attr); +} + + +ea_list * +channel_get_state(int id) +{ + PST_LOCKED(ts) + { + ASSERT_DIE((u32) id < ts->length_channels); + if (ts->channels[id]) + return ea_ref_tmp(ts->channels[id]); + } + return NULL; +} + +ea_list * +proto_get_state(int id) +{ + ea_list *eal; + PST_LOCKED(ts) + { + ASSERT_DIE((u32)id < ts->length_states); + eal = ts->states[id]; + } + if (eal) + return ea_ref_tmp(eal); + return NULL; +} + +void +proto_states_subscribe(struct lfjour_recipient *r) +{ + PST_LOCKED(ts) + lfjour_register(&proto_state_table_pub.journal, r); +} diff --git a/nest/protocol.h b/nest/protocol.h index bbb76a8a..a77c28db 100644 --- a/nest/protocol.h +++ b/nest/protocol.h @@ -173,6 +173,8 @@ struct proto { btime last_state_change; /* Time of last state transition */ char *last_state_name_announced; /* Last state name we've announced to the user */ char *message; /* State-change message, allocated from proto_pool */ + u32 id; /* Id of the protocol indexing its position in proto_state_table */ + ea_list *ea_state; /* Last stored protocol state in proto_state_table */ /* * General protocol hooks: @@ -392,6 +394,54 @@ static inline int proto_is_inactive(struct proto *p) ; } +#define PROTO_STATE_TABLE_PUBLIC \ + DOMAIN(rtable) lock; /* Lock needed to access global protocol state table */ \ + struct lfjour journal; /* Subscribe here to get new content! */ \ + + +struct proto_state_table_private { + struct { + PROTO_STATE_TABLE_PUBLIC; + }; + struct proto_state_table_private **locked_at; + ea_list ** states; + ea_list ** channels; + u32 length_states; + u32 length_channels; + struct hmap proto_id_map; + struct hmap channel_id_map; + pool *pool; +}; + +typedef union proto_state_table { + struct { + PROTO_STATE_TABLE_PUBLIC; + }; + struct proto_state_table_private priv; +} proto_state_table; + +extern proto_state_table proto_state_table_pub; + +/* Define the lock cleanup function */ +LOBJ_UNLOCK_CLEANUP(proto_state_table, rtable); + +#define PST_IS_LOCKED LOBJ_IS_LOCKED(&proto_state_table_pub, rtable) +#define PST_LOCKED(tp) LOBJ_LOCKED(&proto_state_table_pub, tp, proto_state_table, rtable) +#define PST_LOCK(tp) LOBJ_LOCK(&proto_state_table_pub, tp, proto_state_table, rtable) + +struct proto_pending_update { + LFJOUR_ITEM_INHERIT(li); + ea_list *proto_attr; + ea_list *old_proto_attr; + struct proto *protocol; +}; + +void proto_announce_state_locked(struct proto_state_table_private *ts, struct proto *p, ea_list *attr); +void proto_announce_state(struct proto *p, ea_list *attr); +ea_list *channel_get_state(int id); +ea_list *proto_get_state(int id); +void proto_states_subscribe(struct lfjour_recipient *r); + /* * Debugging flags @@ -526,6 +576,7 @@ struct channel { node n; /* Node in proto->channels */ const char *name; /* Channel name (may be NULL) */ + u32 id; const struct channel_class *class; struct proto *proto; diff --git a/nest/rt-attr.c b/nest/rt-attr.c index 4ea5f76e..2e7a11c0 100644 --- a/nest/rt-attr.c +++ b/nest/rt-attr.c @@ -1836,6 +1836,69 @@ ea_show_list(struct cli *c, ea_list *eal) ea_show(c, &n->attrs[i]); } + +struct ea_class ea_name = { + .name = "proto_name", + .type = T_STRING, +}; + +struct ea_class ea_protocol_name = { + .name = "proto_protocol_name", + .type = T_STRING, +}; + +struct ea_class ea_protocol_type = { + .name = "proto_protocol_type", + .type = T_PTR, +}; + +struct ea_class ea_main_table_id = { + .name = "proto_main_table_id", + .type = T_INT, +}; + +struct ea_class ea_state = { + .name = "proto_state", + .type = T_ENUM_STATE, +}; + +struct ea_class ea_last_modified = { + .name = "proto_last_modified", + .type = T_BTIME, +}; + +struct ea_class ea_info = { + .name = "proto_info", + .type = T_STRING, +}; + +struct ea_class ea_proto_id = { + .name = "proto_proto_id", + .type = T_INT, +}; + +struct ea_class ea_proto_channel_list = { + .name = "ea_proto_channel_list", + .type = T_CLIST, +}; + +struct ea_class ea_channel_id = { + .name = "proto_channel_id", + .type = T_INT, +}; + +struct ea_class ea_in_keep = { + .name = "channel_in_keep", + .type = T_INT, +}; + + +struct ea_class ea_rtable = { + .name = "rtable", + .type = T_PTR, +}; + + /** * rta_init - initialize route attribute cache * @@ -1876,6 +1939,20 @@ rta_init(void) ea_register_init(&ea_gen_mpls_policy); ea_register_init(&ea_gen_mpls_class); ea_register_init(&ea_gen_mpls_label); + + /* Protocol attributes */ + ea_register_init(&ea_name); + ea_register_init(&ea_protocol_name); + ea_register_init(&ea_protocol_type); + ea_register_init(&ea_main_table_id); + ea_register_init(&ea_state); + ea_register_init(&ea_last_modified); + ea_register_init(&ea_info); + ea_register_init(&ea_proto_id); + ea_register_init(&ea_channel_id); + ea_register_init(&ea_in_keep); + ea_register_init(&ea_proto_channel_list); + ea_register_init(&ea_rtable); } /*