proc: more signal determinism, proper process exit in mt mode

This commit is contained in:
Mark Poliakov 2024-11-19 12:15:34 +02:00
parent d7111e8d99
commit cbd823e17b
7 changed files with 70 additions and 32 deletions

View File

@ -324,13 +324,32 @@ impl Process {
}
/// Raises a signal for the specified process
pub fn raise_signal(self: &Arc<Self>, signal: Signal) {
let thread = self.inner.read().threads[0].clone();
thread.raise_signal(signal);
///
/// If sender is Some(id) - signal was sent by some thread in some (possibly this) process.
/// If sender is None - signal was caused by some fault or some other non-deliberate cause.
pub fn raise_signal(self: &Arc<Self>, sender: Option<ThreadId>, signal: Signal) {
// If signal was raised by a thread within the process, consider the signal a
// "synchronous-like" and deliver it to the same thread that issued it.
let destination = {
let inner = self.inner.read();
if let Some(thread) = sender.and_then(|id| inner.threads.iter().find(|t| t.id == id)) {
// Behave like a "synchronous" signal, because a thread within a process asked to
// do something with the process itself. Make the requesting thread handle the
// signal.
thread.clone()
} else {
// Asynchronous signal not caused by any thread within the process, basically,
// any thread can handle it, but for determinism's sake, let it be the main one
inner.threads[0].clone()
}
};
destination.raise_signal(signal);
}
/// Raises a signal for the specified process group
pub fn signal_group(group_id: ProcessGroupId, signal: Signal) {
pub fn signal_group(sender: Option<ThreadId>, group_id: ProcessGroupId, signal: Signal) {
MANAGER.for_each(|_, proc| {
let inner = proc.inner.read();
if !proc.has_exited() && inner.group_id == group_id {
@ -342,7 +361,7 @@ impl Process {
signal
);
drop(inner);
proc.raise_signal(signal);
proc.raise_signal(sender, signal);
}
});
}
@ -398,6 +417,9 @@ impl Process {
Ok(())
}
/// Terminates all threads with the exclusion of `except`. This may be useful when a thread
/// wants the whole process to terminate: the asking thread itself must be exempt from this
/// termination, as it's the one doing the cleanup.
pub async fn terminate_others(&self, except: ThreadId) {
let mut inner = self.inner.write();
@ -407,7 +429,9 @@ impl Process {
}
log::info!("Terminate thread {}", thread.id);
thread.terminate().await;
thread.terminate();
thread.exit.wait().await;
log::debug!("{} died", thread.id);
}
inner.retain_thread(except);

View File

@ -82,6 +82,14 @@ impl CpuQueue {
assert!(sched.in_queue);
assert!(core::ptr::eq(self, sched.queue.unwrap()));
if thread.kill.is_signalled() {
sched.state = ThreadState::Terminated;
sched.in_queue = false;
sched.queue = None;
thread.set_terminated();
continue;
}
match sched.state {
ThreadState::Ready => {
sched.state = ThreadState::Running;

View File

@ -81,6 +81,7 @@ pub struct Thread {
signal_queue: SegQueue<Signal>,
pub exit: Arc<BoolEvent>,
pub kill: BoolEvent,
/// CPU scheduling affinity mask
pub affinity: ThreadAffinity,
@ -135,6 +136,7 @@ impl Thread {
inner: IrqSafeSpinlock::new(ThreadInner { signal_entry: None }),
signal_queue: SegQueue::new(),
exit: Arc::new(BoolEvent::new()),
kill: BoolEvent::new(),
affinity: ThreadAffinity::any_cpu(),
});
@ -403,11 +405,16 @@ impl Thread {
self.enqueue_to(queue);
}
/// Requests thread termination and blocks until said thread finishes fully
pub async fn terminate(self: &Arc<Self>) {
// Will not abort the execution: called from another thread
self.dequeue(ThreadState::Terminated);
self.exit.wait().await;
/// Requests thread termination. This function only asks the thread to terminate, which will
/// happen the next time it's seen by the scheduler.
pub fn terminate(self: &Arc<Self>) {
// Will not abort the execution: called from another thread.
//
// Instead of dequeueing the thread, like done with a synchronous exit (when the thread
// exits on its own), instead, the thread needs to be **enqueued**, because we need to wake
// it up in order to make it die itself.
self.kill.signal_saturating();
self.enqueue();
}
/// Returns the current thread on the CPU.
@ -496,7 +503,6 @@ impl CurrentThread {
unreachable!()
}
// TODO: test multithreaded process exit
/// Terminate the parent process of the thread, including all other threads and the current
/// thread itself
pub fn exit_process(&self, code: ExitCode) -> ! {

View File

@ -156,7 +156,7 @@ impl<O: TerminalOutput> Terminal<O> {
self.output.notify_readers();
if let Some(group_id) = *self.input.signal_pgroup.read() {
Process::signal_group(group_id, Signal::Interrupted);
Process::signal_group(None, group_id, Signal::Interrupted);
self.input.ready_ring.notify_all();
return;
}

View File

@ -205,8 +205,9 @@ pub(crate) fn set_signal_entry(ip: usize, sp: usize) {
}
pub(crate) fn send_signal(pid: ProcessId, signal: Signal) -> Result<(), Error> {
let thread = Thread::current();
let target = Process::get(pid).ok_or(Error::DoesNotExist)?;
target.raise_signal(signal);
target.raise_signal(Some(thread.id), signal);
Ok(())
}
@ -275,9 +276,11 @@ pub(crate) fn wait_thread(id: u32) -> Result<(), Error> {
let this_thread = Thread::current();
let process = this_thread.process();
block!(process.wait_for_thread(tid).await)??;
log::debug!("wait_thread({id})");
let result = block!(process.wait_for_thread(tid).await)?;
log::debug!("www -> {result:?}");
Ok(())
result
}
pub(crate) fn get_thread_option(option: &mut ThreadOption) -> Result<(), Error> {

View File

@ -166,3 +166,13 @@ impl ProgramArgumentInner {
}
}
}
impl Signal {
pub fn is_synchronous(&self) -> bool {
matches!(self, Self::MemoryAccessViolation)
}
pub fn is_handleable(&self) -> bool {
!matches!(self, Self::Killed)
}
}

19
test.c
View File

@ -2,31 +2,18 @@
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/yggdrasil.h>
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static void *function(void *arg) {
pthread_barrier_t *barrier = (pthread_barrier_t *) arg;
printf("[child] waiting for parent\n");
pthread_barrier_wait(barrier);
printf("[child] barrier signalled!!\n");
abort();
return NULL;
}
int main(int argc, const char **argv) {
pthread_t thread;
pthread_barrier_t barrier;
pthread_barrier_init(&barrier, NULL, 2);
printf("[main] will make the child wait on a barrier\n");
assert(pthread_create(&thread, NULL, function, (void *) &barrier) == 0);
sleep(3);
pthread_barrier_wait(&barrier);
printf("[main] barrier signalled!!\n");
assert(pthread_create(&thread, NULL, function, (void *) NULL) == 0);
pthread_join(thread, NULL);
return 0;