loop
{
tokio::select!
{
// 1 wait for a shutdown signal, break loop when received
// 2 listen for connections on TCP as a server and spawn each handling of a client as a task (client tasks)
}
} // How can I break the loop upon shutdown, but not have the client tasks aborted/canceled (select! goes through and does that recursively i guess)
if you are talking about the spawned tasks handling client connections, no, select! doesn't cancel them, it only cancels the other alternative futures (not spawned tasks) of the select!, in this case the TCP listener.
if your loop is in the async fn main() {} task, it is the return from the "main" task that tears down the tokio runtime, which forces all unfinished tasks to cancel.
your "main" task must not return before you gracefully shutdown the client handler tasks. you have multiple ways to do this, examples includes:
keep track how many client handlers are alive using some synchronized counter, the main task will only return when the counter reaches zero.
one trick is to (ab-)use the internal counter of a mpsc channel:
the main task create a pair of Sender and Receiver
each spawned task get a clone of the Sender end, which will be dropped when they finishes
when the main task finishes, it drops its Sender and await the Receiver
once the count of the Senders reaches zero, the Receiver will be waken
don't discard the join handles after spawning the tasks, await them before returning from "main".
JoinSet can be used, instead of manually keeping track of all the join handles
this is under the assumption that you want the client handler to finish. if you don't want to wait for all of them to finish because it might takes long, you must implement some from of cooperative cancellation mechanism, e.g. using tokio_util::CancellationToken.