Regarding "synchronous interrupts of userspace", instead of a POSIX-style signal that pauses all of userspace until it finishes executing, I was thinking my RPCs would be like "fat functions" in that the same thread in which the call was made changes address space into the callee, and this thread doesn't pause any other threads running on the callee (so it will get preemptively interrupted.)
Being an RPC, there's no guarantee that child process will return (it could terminate, for example.) Imagine the following call stack:
Process A -> Process B -> Process C
(e.g. a process calls the VFS which calls the device driver.)
At any point, A, B, or C could terminate. So I'm thinking, we should return a status code along with the Flat Buffer Response message - the message only being populated if the status == OK. They we can handle any of these processes terminating:
- Process A -> Process B -> X
The thread returns to the last caller in Process B, with the RPC returning the status CALLER_TERMINATED. Process B can choose if they want to propogate the error back up to Process A, return a different status, or gracefully handle it and still return OK along with some response back to Process A. (e.g. if Process A asks the VFS to get the contents of a directory, and the device driver fails, the VFS could gracefully return an empty directory.)
- Process A -> X -> Process C
It wouldn't be wise to stop the thread mid-execution inside of Process C, since Process C could have locked resources or be in the middle of mutating a data structure. Process C should finish executing it's handler (even if this effort is ultimately wasted), and when Process C returns, the kernel sees that Process B was terminated, and jumps back to the caller inside Process A returning status CALLER_TERMINATED.
- X -> Process B -> Process C
The thread finishes executing the handler inside of Process C and Process B, and upon returning to Process A, the kernel sees that Process A (where the thread originated) was terminated, discards the response, and kills the thread.
So, if you did want to call an RPC asynchronosly, it would be up to the caller to wrap it in it's own thread. The C++ code would look something like:
Code:
std::future<status_or<ResponseType>> future_status_or_response = std::async(std::launch::async, []{ return CallRpc(process_id, message_id, request_message); });
// Do other processing, make other RPCs, etc.
// Get the future, blocks until finish running:
status_or<ResponseType> status_or_response = future_status_or_response.get();
if (!status_or_response.ok()) {
// Some error happened, e.g. the process died.
return;
}
ResponseType response = status_or_response.value();
// Have the response.