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:
parent
25304c7589
commit
34432ed181
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(¤t);
|
||||
cpu_list_add(&blocked);
|
||||
current_cpu = ¤t;
|
||||
|
||||
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(¤t);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
Loading…
Reference in New Issue