rcu: Introduce force_rcu notifier

The drain_rcu_call() function can be blocked as long as an RCU reader
stays in a read-side critical section. This is typically what happens
when a TCG vCPU is executing a busy loop. It can deadlock the QEMU
monitor as reported in https://gitlab.com/qemu-project/qemu/-/issues/650 .

This can be avoided by allowing drain_rcu_call() to enforce an RCU grace
period. Since each reader might need to do specific actions to end a
read-side critical section, do it with notifiers.

Prepare ground for this by adding a notifier list to the RCU reader
struct and use it in wait_for_readers() if drain_rcu_call() is in
progress. An API is added for readers to register their notifiers.

This is largely based on a draft from Paolo Bonzini.

Suggested-by: Paolo Bonzini <pbonzini@redhat.com>
Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Message-Id: <20211109183523.47726-2-groug@kaod.org>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
Greg Kurz 2021-11-09 19:35:22 +01:00 committed by Paolo Bonzini
parent a0b9c5f75c
commit ef149763a8
2 changed files with 34 additions and 0 deletions

View File

@ -27,6 +27,7 @@
#include "qemu/thread.h" #include "qemu/thread.h"
#include "qemu/queue.h" #include "qemu/queue.h"
#include "qemu/atomic.h" #include "qemu/atomic.h"
#include "qemu/notify.h"
#include "qemu/sys_membarrier.h" #include "qemu/sys_membarrier.h"
#ifdef __cplusplus #ifdef __cplusplus
@ -66,6 +67,13 @@ struct rcu_reader_data {
/* Data used for registry, protected by rcu_registry_lock */ /* Data used for registry, protected by rcu_registry_lock */
QLIST_ENTRY(rcu_reader_data) node; QLIST_ENTRY(rcu_reader_data) node;
/*
* NotifierList used to force an RCU grace period. Accessed under
* rcu_registry_lock. Note that the notifier is called _outside_
* the thread!
*/
NotifierList force_rcu;
}; };
extern __thread struct rcu_reader_data rcu_reader; extern __thread struct rcu_reader_data rcu_reader;
@ -180,6 +188,13 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(RCUReadAuto, rcu_read_auto_unlock)
#define RCU_READ_LOCK_GUARD() \ #define RCU_READ_LOCK_GUARD() \
g_autoptr(RCUReadAuto) _rcu_read_auto __attribute__((unused)) = rcu_read_auto_lock() g_autoptr(RCUReadAuto) _rcu_read_auto __attribute__((unused)) = rcu_read_auto_lock()
/*
* Force-RCU notifiers tell readers that they should exit their
* read-side critical section.
*/
void rcu_add_force_rcu_notifier(Notifier *n);
void rcu_remove_force_rcu_notifier(Notifier *n);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -46,6 +46,7 @@
unsigned long rcu_gp_ctr = RCU_GP_LOCKED; unsigned long rcu_gp_ctr = RCU_GP_LOCKED;
QemuEvent rcu_gp_event; QemuEvent rcu_gp_event;
static int in_drain_call_rcu;
static QemuMutex rcu_registry_lock; static QemuMutex rcu_registry_lock;
static QemuMutex rcu_sync_lock; static QemuMutex rcu_sync_lock;
@ -107,6 +108,8 @@ static void wait_for_readers(void)
* get some extra futex wakeups. * get some extra futex wakeups.
*/ */
qatomic_set(&index->waiting, false); qatomic_set(&index->waiting, false);
} else if (qatomic_read(&in_drain_call_rcu)) {
notifier_list_notify(&index->force_rcu, NULL);
} }
} }
@ -339,8 +342,10 @@ void drain_call_rcu(void)
* assumed. * assumed.
*/ */
qatomic_inc(&in_drain_call_rcu);
call_rcu1(&rcu_drain.rcu, drain_rcu_callback); call_rcu1(&rcu_drain.rcu, drain_rcu_callback);
qemu_event_wait(&rcu_drain.drain_complete_event); qemu_event_wait(&rcu_drain.drain_complete_event);
qatomic_dec(&in_drain_call_rcu);
if (locked) { if (locked) {
qemu_mutex_lock_iothread(); qemu_mutex_lock_iothread();
@ -363,6 +368,20 @@ void rcu_unregister_thread(void)
qemu_mutex_unlock(&rcu_registry_lock); qemu_mutex_unlock(&rcu_registry_lock);
} }
void rcu_add_force_rcu_notifier(Notifier *n)
{
qemu_mutex_lock(&rcu_registry_lock);
notifier_list_add(&rcu_reader.force_rcu, n);
qemu_mutex_unlock(&rcu_registry_lock);
}
void rcu_remove_force_rcu_notifier(Notifier *n)
{
qemu_mutex_lock(&rcu_registry_lock);
notifier_remove(n);
qemu_mutex_unlock(&rcu_registry_lock);
}
static void rcu_init_complete(void) static void rcu_init_complete(void)
{ {
QemuThread thread; QemuThread thread;