LATX, fix: bound final AOT exclusive wait

Bound final AOT exclusive entry so shutdown cannot wait forever for a
CPU blocked in native code. On timeout, cancel pending waiter state and
skip final AOT without retaining an exclusive context.

Use the timeout only for exit_group, self SIGKILL, and signals that
already selected the default fatal action. Use an explicit exit reason
so ordinary call sites cannot accidentally select the final path.

Tests:
- meson test test-exclusive-timeout --print-errorlogs

Signed-off-by: Wenqiang Wei <weiwenqiang@mail.ustc.edu.cn>
This commit is contained in:
Wenqiang Wei 2026-07-30 17:46:00 +08:00 committed by Hanlu Li
parent 25304c7589
commit 34432ed181
10 changed files with 195 additions and 20 deletions

View File

@ -116,7 +116,7 @@ done:
static void gen_aot_and_flush(CPUState *cpu, run_on_cpu_data tb_flush_count)
{
#ifdef CONFIG_LATX_AOT
aot_exit_entry(cpu, false);
aot_exit_entry(cpu, AOT_EXIT_THREAD);
#endif
do_tb_flush(cpu, tb_flush_count);
}

View File

@ -177,6 +177,35 @@ static inline void exclusive_idle(void)
}
}
static int64_t monotonic_ms(void)
{
return g_get_monotonic_time() / 1000;
}
static bool exclusive_timedwait(QemuCond *cond, int64_t deadline_ms)
{
int64_t timeout_ms = deadline_ms - monotonic_ms();
if (timeout_ms <= 0) {
return false;
}
if (timeout_ms > INT_MAX) {
timeout_ms = INT_MAX;
}
return qemu_cond_timedwait(cond, &qemu_cpu_list_lock, timeout_ms);
}
static void exclusive_cancel_waiters(void)
{
CPUState *other_cpu;
CPU_FOREACH(other_cpu) {
other_cpu->has_waiter = false;
}
qatomic_set(&pending_cpus, 0);
qemu_cond_broadcast(&exclusive_resume);
}
/* Start an exclusive operation.
Must only be called from outside cpu_exec. */
void start_exclusive(void)
@ -219,6 +248,63 @@ void start_exclusive(void)
current_cpu->exclusive_context_count = 1;
}
/*
* Try to start an exclusive operation, but do not wait forever for CPUs that
* are stuck in host/native code. Returns true with the same state as
* start_exclusive(), or false with no exclusive operation held.
*/
bool start_exclusive_timeout(int timeout_ms)
{
CPUState *other_cpu;
int64_t deadline_ms;
int running_cpus;
if (current_cpu->exclusive_context_count) {
current_cpu->exclusive_context_count++;
return true;
}
if (timeout_ms <= 0) {
return false;
}
deadline_ms = monotonic_ms() + timeout_ms;
qemu_mutex_lock(&qemu_cpu_list_lock);
while (pending_cpus) {
if (!exclusive_timedwait(&exclusive_resume, deadline_ms)) {
qemu_mutex_unlock(&qemu_cpu_list_lock);
return false;
}
}
/* Make all other cpus stop executing. */
qatomic_set(&pending_cpus, 1);
/* Write pending_cpus before reading other_cpu->running. */
smp_mb();
running_cpus = 0;
CPU_FOREACH(other_cpu) {
if (qatomic_read(&other_cpu->running)) {
other_cpu->has_waiter = true;
running_cpus++;
qemu_cpu_kick(other_cpu);
}
}
qatomic_set(&pending_cpus, running_cpus + 1);
while (pending_cpus > 1) {
if (!exclusive_timedwait(&exclusive_cond, deadline_ms)) {
exclusive_cancel_waiters();
qemu_mutex_unlock(&qemu_cpu_list_lock);
return false;
}
}
qemu_mutex_unlock(&qemu_cpu_list_lock);
current_cpu->exclusive_context_count = 1;
return true;
}
/* Finish an exclusive operation. */
void end_exclusive(void)
{

View File

@ -912,6 +912,17 @@ void cpu_exec_end(CPUState *cpu);
*/
void start_exclusive(void);
/**
* start_exclusive_timeout:
* @timeout_ms: maximum time to wait in milliseconds.
*
* Like start_exclusive(), but returns false if other CPUs do not quiesce
* within the condition-wait budget. Lock acquisition and platform condition
* variable semantics may extend the elapsed wall-clock time. On false, no
* exclusive section is held.
*/
bool start_exclusive_timeout(int timeout_ms);
/**
* end_exclusive:
*

View File

@ -1495,9 +1495,7 @@ static void handle_pending_signal(CPUArchState *cpu_env, int sig,
sig != TARGET_SIGWINCH &&
sig != TARGET_SIGCONT) {
#ifdef CONFIG_LATX_AOT
if (sig == TARGET_SIGTERM) {
aot_exit_entry(cpu, true);
}
aot_exit_entry(cpu, AOT_EXIT_FINAL);
#endif
dump_core_and_abort(sig);
}

View File

@ -9275,7 +9275,7 @@ static void QEMU_NORETURN seccomp_kill_thread(CPUArchState *env)
pthread_mutex_lock(&clone_lock);
#ifdef CONFIG_LATX_AOT
if (current_cpu->cpu_index == 0) {
aot_exit_entry(cpu, false);
aot_exit_entry(cpu, AOT_EXIT_THREAD);
}
#endif
if (CPU_NEXT(first_cpu)) {
@ -12067,7 +12067,7 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1,
pthread_mutex_lock(&clone_lock);
#ifdef CONFIG_LATX_AOT
if(current_cpu->cpu_index == 0) {
aot_exit_entry(cpu, false);
aot_exit_entry(cpu, AOT_EXIT_THREAD);
}
#endif
@ -12818,9 +12818,9 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1,
#endif
case TARGET_NR_kill:
#ifdef CONFIG_LATX_AOT
if (arg1 == getpid())
if (arg1 == getpid() && target_to_host_signal(arg2) == SIGKILL)
{
aot_exit_entry(cpu, true);
aot_exit_entry(cpu, AOT_EXIT_FINAL);
}
#endif
return get_errno(safe_kill(arg1, target_to_host_signal(arg2)));
@ -14481,7 +14481,7 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1,
preexit_cleanup(cpu_env, arg1);
/* dump basic block here. TODO */
#ifdef CONFIG_LATX_AOT
aot_exit_entry(cpu, true);
aot_exit_entry(cpu, AOT_EXIT_FINAL);
#endif
return get_errno(exit_group(arg1));
#endif
@ -16862,9 +16862,10 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1,
case TARGET_NR_tgkill:
#ifdef CONFIG_LATX_AOT
if (arg2 == syscall(SYS_gettid))
if (arg1 == getpid() && arg2 == syscall(SYS_gettid) &&
target_to_host_signal(arg3) == SIGKILL)
{
aot_exit_entry(cpu, true);
aot_exit_entry(cpu, AOT_EXIT_FINAL);
}
#endif
return get_errno(safe_tgkill((int)arg1, (int)arg2,

View File

@ -303,7 +303,12 @@ int aot_get_file_name(char *aot_file, char *buff, int index);
int add_rel_entry(aot_rel_kind kind, uint32_t **tc_offset,
uint32_t **rel_slots_num, uint32_t x86_rip_offset,
target_ulong extra_addent);
void aot_exit_entry(CPUState *cpu, int is_end);
typedef enum AOTExitReason {
AOT_EXIT_THREAD,
AOT_EXIT_FINAL,
} AOTExitReason;
void aot_exit_entry(CPUState *cpu, AOTExitReason reason);
void aot_init(void);
target_ulong aot_get_call_offset(ADDRX addr);
void aot_generate(CPUState *cpu);

View File

@ -1844,7 +1844,9 @@ static void creat_daemon(bool is_end)
umask(0);
}
void aot_exit_entry(CPUState *cpu, int is_end)
#define AOT_EXIT_EXCLUSIVE_TIMEOUT_MS 1000
void aot_exit_entry(CPUState *cpu, AOTExitReason reason)
{
if (!option_aot) {
return;
@ -1854,9 +1856,19 @@ void aot_exit_entry(CPUState *cpu, int is_end)
_exit(0);
}
bool exclusive_started = false;
bool in_exclusive_context = cpu_in_exclusive_context(cpu);
if (!in_exclusive_context) {
start_exclusive();
if (reason == AOT_EXIT_FINAL) {
if (!start_exclusive_timeout(AOT_EXIT_EXCLUSIVE_TIMEOUT_MS)) {
qemu_log_mask(LAT_LOG_AOT,
"skip final aot: exclusive wait timeout\n");
return;
}
} else {
start_exclusive();
}
exclusive_started = true;
}
sigset_t sigset;
@ -1913,13 +1925,13 @@ void aot_exit_entry(CPUState *cpu, int is_end)
}
parent_exit:
if (!in_exclusive_context) {
end_exclusive();
}
return;
if (exclusive_started) {
end_exclusive();
}
return;
}
creat_daemon(is_end);
creat_daemon(reason == AOT_EXIT_FINAL);
aot_generate(cpu);
_exit(EXIT_SUCCESS);

View File

@ -34,7 +34,8 @@ int qemu_log(const char *fmt G_GNUC_UNUSED, ...)
return 0;
}
void aot_exit_entry(CPUState *cpu G_GNUC_UNUSED, int is_end G_GNUC_UNUSED)
void aot_exit_entry(CPUState *cpu G_GNUC_UNUSED,
AOTExitReason reason G_GNUC_UNUSED)
{
}

View File

@ -6,3 +6,13 @@ test_elfload_pagesize = executable(
)
test('test-elfload-pagesize', test_elfload_pagesize)
test_exclusive_timeout = executable(
'test-exclusive-timeout',
files('test-exclusive-timeout.c'),
'../../cpus-common.c',
include_directories: include_directories('../..'),
dependencies: [glib, qemuutil, qom],
)
test('test-exclusive-timeout', test_exclusive_timeout)

View File

@ -0,0 +1,51 @@
#include "qemu/osdep.h"
#include <glib.h>
#include "exec/cpu-common.h"
#include "hw/core/cpu.h"
void qemu_cpu_kick(CPUState *cpu)
{
(void)cpu;
}
bool qemu_cpu_is_self(CPUState *cpu)
{
return cpu == current_cpu;
}
static void test_timeout_cancellation_allows_a_retry(void)
{
CPUState current = { .cpu_index = 0 };
CPUState blocked = { .cpu_index = 1, .running = true };
qemu_init_cpu_list();
cpu_list_add(&current);
cpu_list_add(&blocked);
current_cpu = &current;
g_assert_false(start_exclusive_timeout(0));
g_assert_cmpuint(current.exclusive_context_count, ==, 0);
g_assert_false(blocked.has_waiter);
g_assert_false(start_exclusive_timeout(1));
g_assert_cmpuint(current.exclusive_context_count, ==, 0);
g_assert_false(blocked.has_waiter);
blocked.running = false;
g_assert_true(start_exclusive_timeout(100));
g_assert_cmpuint(current.exclusive_context_count, ==, 1);
end_exclusive();
g_assert_cmpuint(current.exclusive_context_count, ==, 0);
cpu_list_remove(&blocked);
cpu_list_remove(&current);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/cpu/exclusive-timeout/cancel-and-retry",
test_timeout_cancellation_allows_a_retry);
return g_test_run();
}