The switch now reaches the running server
The join: the policy decides, features owns the listener objects, ListenerControl owns the running sockets, and only Server has both. Server::set_protocol_policy is what a click performs. It applies the locks to what was asked before storing anything (LP-21), so what is recorded is what the server allows. Closing removes each listener object and then stops its socket; opening puts the object back and then spawns it. The order is the point in both directions -- a socket stopped while its object remains returns on the next restart, and a socket spawned before its object exists has nothing to come back to. saved_listeners is carried over from the stored policy rather than taken from the request. A client never sets it, and a /set that omitted it would otherwise lose the listeners still waiting to come back. Putting a listener back has to bind a fresh socket, so it re-parses from the registry -- the objects are already back by then -- rather than trying to revive the saved one. Only main knows which session manager a protocol wants, so it leaves a spawner behind at startup and spawn_listener is now shared between that and the initial spawn. Without a spawner a restored listener is reported as pending a restart rather than promised, which is what the test servers will see. A listener that cannot be put back does not stop the others and stays saved for another try (LP-5). Still nothing an operator can reach: no JMAP method calls this yet, and no sign-in is refused. What it does do is close and reopen a port on a running server, which is the part that did not exist this morning.
This commit is contained in:
@@ -23,11 +23,18 @@
|
||||
//! process stops answering; anything that still routes the port is the
|
||||
//! operator's to reconcile, and is deliberately left alone.
|
||||
|
||||
use crate::config::server::ServerProtocol;
|
||||
use crate::config::server::{Listener, ServerProtocol};
|
||||
use crate::network::TcpAcceptor;
|
||||
use ahash::AHashMap;
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::OnceLock;
|
||||
use tokio::sync::watch;
|
||||
|
||||
/// How a listener is spawned. Only `main` knows how to build the session
|
||||
/// manager for a protocol, so it leaves this behind at startup and the policy
|
||||
/// uses it to put a listener back without a restart (LP-5).
|
||||
pub type SpawnListener = Box<dyn Fn(Listener, TcpAcceptor, watch::Receiver<bool>) + Send + Sync>;
|
||||
|
||||
/// A listener that is currently accepting, and the switch that stops it.
|
||||
struct Running {
|
||||
protocol: ServerProtocol,
|
||||
@@ -47,6 +54,7 @@ pub struct ListenerInfo {
|
||||
#[derive(Default)]
|
||||
pub struct ListenerControl {
|
||||
running: RwLock<AHashMap<String, Running>>,
|
||||
spawner: OnceLock<SpawnListener>,
|
||||
}
|
||||
|
||||
impl ListenerControl {
|
||||
@@ -70,6 +78,33 @@ impl ListenerControl {
|
||||
shutdown_rx
|
||||
}
|
||||
|
||||
/// Remembers how to spawn a listener, once, at startup. Later calls are
|
||||
/// ignored, so nothing can swap the spawner out from under a running
|
||||
/// server.
|
||||
pub fn set_spawner(&self, spawner: SpawnListener) {
|
||||
let _ = self.spawner.set(spawner);
|
||||
}
|
||||
|
||||
/// Whether a spawner has been left behind. Without one, a listener can be
|
||||
/// stopped but not started, and the caller has to say so rather than
|
||||
/// promise a port that will not open until a restart.
|
||||
pub fn can_spawn(&self) -> bool {
|
||||
self.spawner.get().is_some()
|
||||
}
|
||||
|
||||
/// Starts a listener and registers it, so it can be stopped again.
|
||||
/// Returns false when no spawner was left behind.
|
||||
pub fn spawn(&self, listener: Listener, acceptor: TcpAcceptor) -> bool {
|
||||
let Some(spawner) = self.spawner.get() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let ports = listener.listeners.iter().map(|l| l.addr.port()).collect();
|
||||
let shutdown_rx = self.register(listener.id.clone(), listener.protocol, ports);
|
||||
spawner(listener, acceptor, shutdown_rx);
|
||||
true
|
||||
}
|
||||
|
||||
/// Stops one listener by id. Returns what was stopped, or `None` when no
|
||||
/// listener of that id is running.
|
||||
pub fn stop(&self, id: &str) -> Option<ListenerInfo> {
|
||||
|
||||
Reference in New Issue
Block a user