runq: runq_not_empty() to support racy lookups again

The 4BSD assumes that its sched_runnable(), which then calls
runq_not_empty(), can be called racily (another CPU may be grabbing
a thread from the global queue at the same time).

This makes runq_not_empty() trip on an assertion in runq_find()
detecting a mismatch between some bit in the status word and the state
of the corresponding queue (empty/not empty).

Re-implement runq_not_empty() in an independent and simpler way as an
alternative to removing the assertion, which remains useful to detect
races when removing or adding a thread in some runqueue.

Fixes:          79f68322c892 ("runq: runq_check(): Re-implement on top of runq_findq()")
MFC after:      1 month
Event:          Kitchener-Waterloo Hackathon 202506
Sponsored by:   The FreeBSD Foundation
This commit is contained in:
Olivier Certner 2025-06-19 22:36:09 -04:00
parent 3e5aeb2064
commit be8e84c2e9
No known key found for this signature in database
GPG key ID: 8CA13040971E2627

View file

@ -493,7 +493,7 @@ int
runq_findq(struct runq *const rq, const int lvl_min, const int lvl_max,
runq_pred_t *const pred, void *const pred_data)
{
rqsw_t const (*const rqsw)[RQSW_NB] = &rq->rq_status.rq_sw;
const rqsw_t (*const rqsw)[RQSW_NB] = &rq->rq_status.rq_sw;
rqsw_t w;
int i, last, idx;
@ -568,20 +568,27 @@ runq_first_thread(struct runq *const rq)
/*
* Return true if there are some processes of any priority on the run queue,
* false otherwise. Has no side effects.
* false otherwise. Has no side effects. Supports racy lookups (required by
* 4BSD).
*/
bool
runq_not_empty(struct runq *rq)
{
struct thread *const td = runq_first_thread(rq);
const rqsw_t (*const rqsw)[RQSW_NB] = &rq->rq_status.rq_sw;
int sw_idx;
if (td != NULL) {
CTR2(KTR_RUNQ, "runq_not_empty: idx=%d, td=%p",
td->td_rqindex, td);
return (true);
for (sw_idx = 0; sw_idx < RQSW_NB; ++sw_idx) {
const rqsw_t w = (*rqsw)[sw_idx];
if (w != 0) {
CTR3(KTR_RUNQ, "runq_not_empty: not empty; "
"rq=%p, sw_idx=%d, bits=" RQSW_PRI,
rq, sw_idx, w);
return (true);
}
}
CTR0(KTR_RUNQ, "runq_not_empty: empty");
CTR1(KTR_RUNQ, "runq_not_empty: empty; rq=%p", rq);
return (false);
}