lib: serialize pthread startup

Add a new condition var and mutex to serialize pthread startup.
When a new pthread is started, it will wait very early on for the
parent pthread to permit it to run. This ensures that that the
ordering between parent and child is predictable.

Signed-off-by: Mark Stapp <mjs@cisco.com>
This commit is contained in:
Mark Stapp 2024-04-09 08:51:20 -04:00
parent e19fa07c2c
commit 44eb133b52
2 changed files with 46 additions and 0 deletions

View File

@ -92,9 +92,14 @@ struct frr_pthread *frr_pthread_new(const struct frr_pthread_attr *attr,
MTYPE_PTHREAD_PRIM, sizeof(pthread_mutex_t));
fpt->running_cond = XCALLOC(MTYPE_PTHREAD_PRIM,
sizeof(pthread_cond_t));
pthread_mutex_init(fpt->running_cond_mtx, NULL);
pthread_cond_init(fpt->running_cond, NULL);
pthread_mutex_init(&fpt->startup_cond_mtx, NULL);
pthread_cond_init(&fpt->startup_cond, NULL);
fpt->started = false;
frr_with_mutex (&frr_pthread_list_mtx) {
listnode_add(frr_pthread_list, fpt);
}
@ -108,6 +113,8 @@ static void frr_pthread_destroy_nolock(struct frr_pthread *fpt)
pthread_mutex_destroy(&fpt->mtx);
pthread_mutex_destroy(fpt->running_cond_mtx);
pthread_cond_destroy(fpt->running_cond);
pthread_mutex_destroy(&fpt->startup_cond_mtx);
pthread_cond_destroy(&fpt->startup_cond);
XFREE(MTYPE_FRR_PTHREAD, fpt->name);
XFREE(MTYPE_PTHREAD_PRIM, fpt->running_cond_mtx);
XFREE(MTYPE_PTHREAD_PRIM, fpt->running_cond);
@ -140,11 +147,34 @@ int frr_pthread_set_name(struct frr_pthread *fpt)
return ret;
}
/* New pthread waits before running */
static void frr_pthread_wait_startup(struct frr_pthread *fpt)
{
frr_with_mutex (&fpt->startup_cond_mtx) {
while (!fpt->started)
pthread_cond_wait(&fpt->startup_cond,
&fpt->startup_cond_mtx);
}
}
/* Parent pthread allows new pthread to start running */
static void frr_pthread_notify_startup(struct frr_pthread *fpt)
{
frr_with_mutex (&fpt->startup_cond_mtx) {
fpt->started = true;
pthread_cond_signal(&fpt->startup_cond);
}
}
static void *frr_pthread_inner(void *arg)
{
struct frr_pthread *fpt = arg;
/* The new pthead waits until the parent allows it to continue. */
frr_pthread_wait_startup(fpt);
rcu_thread_start(fpt->rcu_thread);
return fpt->attr.start(fpt);
}
@ -169,6 +199,9 @@ int frr_pthread_run(struct frr_pthread *fpt, const pthread_attr_t *attr)
/* Restore caller's signals */
pthread_sigmask(SIG_SETMASK, &oldsigs, NULL);
/* Allow new child pthread to start */
frr_pthread_notify_startup(fpt);
/*
* Per pthread_create(3), the contents of fpt->thread are undefined if
* pthread_create() did not succeed. Reset this value to zero.
@ -250,6 +283,8 @@ int frr_pthread_non_controlled_startup(pthread_t thread, const char *name,
fpt->thread = thread;
fpt->rcu_thread = rcu_thread;
fpt->started = true;
frr_pthread_inner(fpt);
return 0;

View File

@ -46,6 +46,17 @@ struct frr_pthread {
/* caller-specified data; start & stop funcs, name, id */
struct frr_pthread_attr attr;
/*
* Startup serialization: newly-started pthreads wait at a point
* very early in life so that there isn't a race with the
* starting pthread. The OS 'start' apis don't make any guarantees
* about which pthread runs first - the existing pthread that has
* called the 'start' api, or the new pthread that is just starting.
*/
pthread_cond_t startup_cond;
pthread_mutex_t startup_cond_mtx;
atomic_bool started;
/*
* Notification mechanism for allowing pthreads to notify their parents
* when they are ready to do work. This mechanism has two associated