1use std::num::NonZero;
3use std::rc::Rc;
4use std::sync::Arc;
5
6use actix_web::web;
7use anyhow::{Context as _, Result, bail};
8use core::convert::Infallible;
9use tokio::sync::mpsc;
10
11use crate::api;
12use crate::misc::defer;
13use crate::servers::{
14 App, AppBase, AppCreator, Command, Constellation, DiscoverVerdict, Name, Server,
15 for_all_servers, server::RunningState,
16};
17
18pub struct Set {
20 wait_jh: tokio::task::JoinHandle<usize>,
22}
23
24#[derive(Default)]
31pub struct SetOpts {
32 pub phc_listener: Option<std::net::TcpListener>,
35
36 pub transcryptor_listener: Option<std::net::TcpListener>,
38
39 pub auths_listener: Option<std::net::TcpListener>,
41}
42
43impl SetOpts {
44 fn take_listener(&mut self, name: Name) -> Option<std::net::TcpListener> {
46 match name {
47 Name::PubhubsCentral => self.phc_listener.take(),
48 Name::Transcryptor => self.transcryptor_listener.take(),
49 Name::AuthenticationServer => self.auths_listener.take(),
50 }
51 }
52}
53
54impl Set {
55 pub fn new(
58 config: &crate::servers::Config,
59 ) -> Result<(Self, tokio::sync::oneshot::Sender<Infallible>)> {
60 Self::new_opts(config, SetOpts::default())
61 }
62
63 pub fn new_opts(
65 config: &crate::servers::Config,
66 opts: SetOpts,
67 ) -> Result<(Self, tokio::sync::oneshot::Sender<Infallible>)> {
68 let (inner, shutdown_sender) = SetInner::new(config, opts)?;
69
70 let wait_jh = tokio::task::spawn(inner.wait());
71
72 Ok((Self { wait_jh }, shutdown_sender))
73 }
74
75 pub async fn wait(self) -> usize {
82 match self.wait_jh.await {
83 Ok(nr) => nr,
84 Err(join_error) => {
85 panic!("task waiting on servers to exit was cancelled or panicked: {join_error}");
86 }
87 }
88 }
89}
90
91struct SetInner {
93 joinset: tokio::task::JoinSet<Result<()>>,
95
96 shutdown_sender: Option<tokio::sync::broadcast::Sender<Infallible>>,
100
101 shutdown_receiver: tokio::sync::oneshot::Receiver<Infallible>,
103}
104
105impl Drop for SetInner {
106 fn drop(&mut self) {
107 if self.shutdown_sender.is_some() {
108 log::error!(
109 "the completion of all pubhubs servers was not awaited - please consume SetInner using wait() or shutdown()"
110 )
111 }
112 }
113}
114
115impl SetInner {
116 pub fn new(
121 config: &crate::servers::Config,
122 mut opts: SetOpts,
123 ) -> Result<(Self, tokio::sync::oneshot::Sender<Infallible>)> {
124 let rt_handle: tokio::runtime::Handle = tokio::runtime::Handle::current();
125 let mut joinset = tokio::task::JoinSet::<Result<()>>::new();
126
127 let (shutdown_sender, _) = tokio::sync::broadcast::channel(1); let server_count: usize = {
131 let mut counter: usize = 0;
132
133 macro_rules! count_server {
134 ($server:ident) => {
135 if config.$server.is_some() {
136 counter += 1;
137 }
138 };
139 }
140
141 for_all_servers!(count_server);
142
143 counter
144 };
145
146 let worker_count: Option<NonZero<usize>> =
148 std::thread::available_parallelism()
149 .ok()
150 .map(|parallelism: NonZero<usize>| {
151 if server_count == 0 {
152 return NonZero::<usize>::new(1).unwrap();
153 }
154
155 NonZero::<usize>::try_from(parallelism.get() / server_count)
156 .unwrap_or(NonZero::<usize>::new(1).unwrap()) });
158
159 macro_rules! run_server {
160 ($server:ident) => {
161 if config.$server.is_some() {
162 let config = config.clone();
163 let rt_handle = rt_handle.clone();
164 let shutdown_receiver = shutdown_sender.subscribe();
165 let listener =
166 opts.take_listener(<crate::servers::$server::Server as Server>::NAME);
167
168 joinset.spawn_blocking(move || -> Result<()> {
171 Self::run_server::<crate::servers::$server::Server>(
172 config,
173 rt_handle,
174 shutdown_receiver,
175 worker_count,
176 listener,
177 )
178 });
179 }
180 };
181 }
182
183 for_all_servers!(run_server);
184
185 debug_assert!(
188 opts.phc_listener.is_none()
189 && opts.transcryptor_listener.is_none()
190 && opts.auths_listener.is_none(),
191 "SetOpts has a pre-bound listener for a server not present in the config"
192 );
193
194 let (external_shutdown_sender, external_shutdown_receiver) =
195 tokio::sync::oneshot::channel();
196
197 Ok((
198 Self {
199 joinset,
200 shutdown_sender: Some(shutdown_sender),
201 shutdown_receiver: external_shutdown_receiver,
202 },
203 external_shutdown_sender,
204 ))
205 }
206
207 fn run_server<S: Server>(
211 config: crate::servers::Config,
212 rt_handle: tokio::runtime::Handle,
213 shutdown_receiver: tokio::sync::broadcast::Receiver<Infallible>,
214 worker_count: Option<NonZero<usize>>,
215 listener: Option<std::net::TcpListener>,
216 ) -> Result<()> {
217 assert!(config.preparation_state == crate::servers::config::PreparationState::Preliminary);
218
219 let localset = tokio::task::LocalSet::new();
220
221 let fut = localset.run_until(async {
222 let config = config.prepare_for(S::NAME).await?;
223
224 crate::servers::run::Runner::<S>::new(
225 &config,
226 shutdown_receiver,
227 worker_count,
228 listener,
229 )?
230 .run()
231 .await
232 });
233
234 let result = rt_handle.block_on(fut);
235
236 rt_handle.block_on(localset);
237
238 log::debug!("{} stopped with {:?}", S::NAME, result);
239
240 result
241 }
242
243 pub async fn wait(mut self) -> usize {
250 log::trace!("waiting for one of the servers to exit...");
251 let err_count: usize = tokio::select! {
252 result_maybe = self
254 .joinset
255 .join_next() => {
256 let result = result_maybe.expect("no servers to wait on");
257 let is_err : bool = matches!(result, Err(_) | Ok(Err(_)));
258
259 log::log!( if is_err { log::Level::Error } else { log::Level::Debug },
260 "one of the servers exited with {result:?}; stopping all servers.."
261 );
262
263 if is_err {
264 1
265 } else {
266 0
267 }
268 },
269
270 result = &mut self.shutdown_receiver => {
272 result.expect_err("received Infallible");
273 log::debug!("shutdown requested"); 0
274 }
275 };
276
277 self.shutdown().await + err_count
278 }
279
280 pub async fn shutdown(mut self) -> usize {
283 assert!(
284 self.shutdown_sender.is_some(),
285 "only signal_shutdown should take shutdown_sender"
286 );
287
288 drop(self.shutdown_sender.take());
291
292 let mut err_count = 0usize;
293
294 while let Some(result) = self.joinset.join_next().await {
295 err_count += if matches!(result, Err(_) | Ok(Err(_))) {
296 1
297 } else {
298 0
299 };
300 }
301
302 err_count
305 }
306}
307
308struct Runner<ServerT: Server> {
310 pubhubs_server: Rc<ServerT>,
311 shutdown_receiver: tokio::sync::broadcast::Receiver<Infallible>,
312 worker_count: Option<NonZero<usize>>,
313
314 listener: Option<std::net::TcpListener>,
318
319 generation: usize,
321}
322
323struct Handles<S: Server> {
325 actix_server_handle: actix_web::dev::ServerHandle,
328
329 actix_join_handle: tokio::task::JoinHandle<Result<(), std::io::Error>>,
331
332 ph_join_handle: tokio::task::JoinHandle<Result<Option<crate::servers::server::BoxModifier<S>>>>,
334
335 ph_shutdown_sender: Option<tokio::sync::oneshot::Sender<Infallible>>,
337
338 command_receiver: mpsc::Receiver<CommandRequest<S>>,
340
341 drop_bomb: crate::misc::drop_ext::Bomb<Box<dyn FnOnce()>>,
343}
344
345impl<S: Server> Handles<S> {
346 async fn run_until_command(&mut self, runner: &mut Runner<S>) -> anyhow::Result<Command<S>> {
348 tokio::select! {
349
350 command_request_maybe = self.command_receiver.recv() => {
352 if let Some(command_request) = command_request_maybe {
353 return Ok(command_request.accept());
354 }
355
356 log::error!("{}'s command receiver is unexpectedly closed", S::NAME);
357 bail!("{}'s command receiver is unexpectedly closed", S::NAME);
358 },
359
360 res = &mut self.ph_join_handle => {
362 let modifier : crate::servers::server::BoxModifier<S> =
363 res.with_context(|| format!("{}'s pubhubs task joined unexpectedly", S::NAME))?
364 .with_context(|| format!("{}'s pubhubs task crashed", S::NAME))?
365 .with_context(|| format!("{}'s pubhubs task stopped without being asked to", S::NAME))?;
366
367
368 #[expect(clippy::needless_return)] return Ok(Command::Modify(modifier));
370 },
371
372 Err(err) = runner.shutdown_receiver.recv() => {
374 match err {
375 tokio::sync::broadcast::error::RecvError::Lagged(_) => {
376 panic!("got impossible `Lagged` error from shutdown sender");
377 },
378 tokio::sync::broadcast::error::RecvError::Closed => {
379 #[expect(clippy::needless_return)] return Ok(Command::Exit);
381 },
382 }
383 },
384
385 res = &mut self.actix_join_handle => {
387 res.inspect_err(|err| log::error!("{}'s actix task joined unexpectedly: {}", S::NAME, err) )
388 .with_context(|| format!("{}'s actix task joined unexpectedly", S::NAME))?
389 .inspect_err(|err| log::error!("{}'s http server crashed: {}", S::NAME, err) )
390 .with_context(|| format!("{}'s http server crashed", S::NAME))?;
391
392 log::error!("{}'s actix server stopped unexpectedly", S::NAME);
393 bail!("{}'s actix server stopped unexpectedly", S::NAME);
394 },
395 };
396 }
397
398 async fn shutdown(mut self) -> anyhow::Result<()> {
400 log::debug!("Shut down of {} started", S::NAME);
401
402 anyhow::ensure!(
403 self.ph_shutdown_sender.is_some(),
404 "shutdown of ph task already ordered"
405 );
406
407 drop(self.ph_shutdown_sender.take());
408
409 self.actix_server_handle.stop(false).await;
415
416 let maybe_modifier = self
417 .ph_join_handle
418 .await
419 .with_context(|| {
420 format!(
421 "{}'s pubhubs task did not join gracefully after being asked to stop",
422 S::NAME
423 )
424 })?
425 .with_context(|| {
426 format!(
427 "{}'s pubhubs task crashed after being asked to stop",
428 S::NAME
429 )
430 })?;
431
432 if let Some(ph_modifier) = maybe_modifier {
433 log::error!(
434 "Woops! {}'s pubhubs task's modifier {} was ignored, because another modifier was first",
435 S::NAME,
436 ph_modifier
437 );
438 }
439
440 self.drop_bomb.defuse();
441
442 log::debug!("Shut down of {} completed", S::NAME);
443
444 Ok(())
445 }
446}
447
448struct DiscoveryLimiter {
450 restart_imminent_lock: Arc<tokio::sync::RwLock<bool>>,
454
455 restart_imminent_cached: std::cell::OnceCell<()>,
458}
459
460impl Clone for DiscoveryLimiter {
461 fn clone(&self) -> Self {
462 Self {
463 restart_imminent_lock: self.restart_imminent_lock.clone(),
464 restart_imminent_cached: std::cell::OnceCell::<()>::new(),
466 }
467 }
468}
469
470impl DiscoveryLimiter {
471 fn new() -> Self {
472 DiscoveryLimiter {
473 restart_imminent_lock: Arc::new(tokio::sync::RwLock::new(false)),
474 restart_imminent_cached: std::cell::OnceCell::<()>::new(),
475 }
476 }
477
478 async fn request_discovery<S: Server>(
522 &self,
523 app: Rc<S::AppT>,
524 ) -> api::Result<api::DiscoveryRunResp> {
525 log::debug!(
526 "{server_name}: discovery is requested",
527 server_name = S::NAME
528 );
529
530 let mut restart_imminent_guard = match self.obtain_lock().await {
531 Some(guard) => guard,
532 None => {
533 log::debug!(
534 "{server_name}: discovery aborted because the server is already restarting",
535 server_name = S::NAME
536 );
537 return Ok(api::DiscoveryRunResp::Restarting);
538 }
539 };
540
541 let phc_discovery_info = AppBase::<S>::discover_phc(app.clone()).await?;
545
546 if phc_discovery_info.constellation_or_id.is_none() && S::NAME != Name::PubhubsCentral {
547 log::info!(
549 "Discovery of {} is run but {} has no constellation yet",
550 S::NAME,
551 Name::PubhubsCentral,
552 );
553 return Err(api::ErrorCode::PleaseRetry);
554 }
555
556 enum PostDiscovery<Seed> {
558 Republish(Box<Constellation>, Seed),
560 RebuildRunningState(Seed),
562 ExitBinary,
564 }
565
566 let post_discovery = match app.discover(phc_discovery_info).await? {
567 DiscoverVerdict::ConstellationOutdated {
568 new_constellation,
569 seed,
570 } => PostDiscovery::Republish(new_constellation, seed),
571 DiscoverVerdict::RunningStateOutdated { seed } => {
572 PostDiscovery::RebuildRunningState(seed)
573 }
574 DiscoverVerdict::BinaryOutdated => PostDiscovery::ExitBinary,
575 DiscoverVerdict::Alright => return Ok(api::DiscoveryRunResp::UpToDate),
576 };
577
578 let display = match &post_discovery {
581 PostDiscovery::Republish(..) => "updated constellation after discovery",
582 PostDiscovery::RebuildRunningState(_) => "updated running state after discovery",
583 PostDiscovery::ExitBinary => "restarting binary hoping to update version",
584 };
585
586 let result = app
587 .handle
588 .modify(display, move |server: &mut S| -> bool {
589 let (constellation, seed) = match post_discovery {
590 PostDiscovery::Republish(new_constellation, seed) => (*new_constellation, seed),
591 PostDiscovery::RebuildRunningState(seed) => {
592 let Some(running_state) = server.running_state.as_ref() else {
593 log::error!("{}: running state to rebuild is absent", S::NAME);
594 return false;
595 };
596 (
597 AsRef::<Constellation>::as_ref(&running_state.constellation).clone(),
598 seed,
599 )
600 }
601 PostDiscovery::ExitBinary => {
602 return false; }
605 };
606
607 let extra = match server.create_running_state(&constellation, &seed) {
608 Ok(extra) => extra,
609 Err(err) => {
610 log::error!(
611 "Error while restarting {} after discovery: {}",
612 S::NAME,
613 err
614 );
615 return false; }
617 };
618
619 let new_url = constellation.url(S::NAME).clone();
620
621 let new_running_state = match RunningState::new(constellation, extra) {
622 Ok(running_state) => running_state,
623 Err(err) => {
624 log::error!(
625 "Error while restarting {} after discovery: {}",
626 S::NAME,
627 err
628 );
629 return false; }
631 };
632
633 let old_running_state = server.running_state.replace(new_running_state);
634
635 if old_running_state.is_none_or(|rs| rs.constellation.url(S::NAME) != &new_url) {
637 log::info!("{}: at {}", S::NAME, new_url);
638 }
639
640 true })
642 .await;
643
644 if let Err(()) = result {
645 log::warn!(
646 "failed to initiate restart of {} for discovery, probably because the server is already shutting down",
647 S::NAME,
648 );
649 return Err(api::ErrorCode::PleaseRetry);
650 }
651
652 log::trace!(
653 "{server_name}: registering imminent restart",
654 server_name = S::NAME
655 );
656 *restart_imminent_guard = true;
657 let _ = self.restart_imminent_cached.set(());
658
659 Ok(api::DiscoveryRunResp::Restarting)
660 }
661
662 async fn obtain_lock(&self) -> Option<tokio::sync::RwLockWriteGuard<'_, bool>> {
664 if self.restart_imminent_cached.get().is_some() {
665 log::trace!("restart imminent: cached");
666 return None;
667 }
668
669 if *self.restart_imminent_lock.read().await {
670 log::trace!("restart imminent: discovered after obtaining read lock");
671 let _ = self.restart_imminent_cached.set(());
672 return None;
673 }
674
675 let restart_imminent_guard = self.restart_imminent_lock.write().await;
676
677 if *restart_imminent_guard {
678 log::trace!("restart imminent: discovered after obtaining write lock");
680 let _ = self.restart_imminent_cached.set(());
681 return None;
682 }
683
684 Some(restart_imminent_guard)
685 }
686}
687
688pub struct Handle<S: Server> {
693 sender: mpsc::Sender<CommandRequest<S>>,
695
696 discovery_limiter: DiscoveryLimiter,
698}
699
700struct CommandRequest<S: Server> {
701 command: Command<S>,
703
704 feedback_sender: tokio::sync::oneshot::Sender<()>,
706}
707
708impl<S: Server> CommandRequest<S> {
709 fn accept(self) -> Command<S> {
711 if self.feedback_sender.send(()).is_err() {
712 log::warn!(
713 "The app issuing command '{}' that is about to execute has already dropped.",
714 &self.command
715 );
716 }
717 self.command
718 }
719}
720
721impl<S: Server> Clone for Handle<S> {
723 fn clone(&self) -> Self {
724 Handle {
725 sender: self.sender.clone(),
726 discovery_limiter: self.discovery_limiter.clone(),
727 }
728 }
729}
730
731impl<S: Server> Handle<S> {
732 pub(crate) async fn issue_command(&self, command: Command<S>) -> Result<(), ()> {
740 let (feedback_sender, feedback_receiver) = tokio::sync::oneshot::channel();
741
742 let result = self
743 .sender
744 .send(CommandRequest {
745 command,
746 feedback_sender,
747 })
748 .await;
749
750 if let Err(send_error) = result {
751 log::warn!(
752 "{server_name}: since the command receiver is closed (probably because the server is shutting down/restarting) we could not issue the command {cmd:?}",
753 server_name = S::NAME,
754 cmd = send_error.0.command.to_string(),
755 );
756 return Err(());
757 };
758
759 feedback_receiver.await.map_err(|_| ())
763 }
764
765 pub async fn modify(
766 &self,
767 display: impl std::fmt::Display + Send + 'static,
768 modifier: impl FnOnce(&mut S) -> bool + Send + 'static,
769 ) -> Result<(), ()> {
770 self.issue_command(Command::Modify(Box::new((modifier, display))))
771 .await
772 }
773
774 pub async fn inspect<T: Send + 'static>(
779 &self,
780 display: impl std::fmt::Display + Send + 'static,
781 inspector: impl FnOnce(&S) -> T + Send + 'static,
782 ) -> Result<T, ()> {
783 let (sender, receiver) = tokio::sync::oneshot::channel::<T>();
784
785 self.issue_command(Command::Inspect(Box::new((
786 |server: &S| {
787 if sender.send(inspector(server)).is_err() {
788 log::warn!(
789 "{}: could not return result of inspection because receiver was already closed",
790 S::NAME
791 );
792 }
794 },
795 display,
796 ))))
797 .await?;
798
799 receiver.await.map_err(|_| {
800 log::warn!(
801 "{server_name}: could receive result of inspector",
802 server_name = S::NAME,
803 );
804 })
805 }
806
807 pub async fn request_discovery(&self, app: Rc<S::AppT>) -> api::Result<api::DiscoveryRunResp> {
808 self.discovery_limiter.request_discovery::<S>(app).await
809 }
810}
811
812impl<S: Server> Runner<S> {
813 pub fn new(
814 global_config: &crate::servers::Config,
815 shutdown_receiver: tokio::sync::broadcast::Receiver<Infallible>,
816 worker_count: Option<NonZero<usize>>,
817 listener: Option<std::net::TcpListener>,
818 ) -> Result<Self> {
819 log::trace!("{}: creating runner...", S::NAME);
820
821 let pubhubs_server = Rc::new(S::new(global_config)?);
822
823 log::trace!("{}: created runner", S::NAME);
824
825 Ok(Runner {
826 pubhubs_server,
827 shutdown_receiver,
828 worker_count,
829 listener,
830 generation: 0,
831 })
832 }
833
834 fn create_actix_server(&self) -> Result<Handles<S>> {
835 let app_creator: S::AppCreatorT = self.pubhubs_server.deref().clone();
836
837 let (command_sender, command_receiver) = mpsc::channel(1);
838
839 let handle = Handle::<S> {
840 sender: command_sender,
841 discovery_limiter: DiscoveryLimiter::new(),
842 };
843
844 let ac_context: <S::AppCreatorT as AppCreator<S>>::ContextT = Default::default();
845
846 let server_config = self.pubhubs_server.server_config();
847
848 let bind_target: String = match self.listener.as_ref() {
849 Some(listener) => format!("pre-bound socket {:?}", listener.local_addr()),
851 None => format!(
852 "{}, port {}",
853 server_config
854 .ips
855 .iter()
856 .map(|ip| ip.to_string())
857 .collect::<Box<[String]>>()
858 .join(", "),
859 server_config.port,
860 ),
861 };
862
863 log::info!(
864 "{}: binding actix server to {}, running on {:?}",
865 S::NAME,
866 bind_target,
867 std::thread::current().id()
868 );
869
870 let app_creator2 = app_creator.clone();
871 let handle2 = handle.clone();
872 let ac_context2 = ac_context.clone();
873 let generation: usize = self.generation;
874
875 let actual_actix_server: actix_web::dev::Server = {
876 let mut builder: actix_web::HttpServer<_, _, _, _> =
878 actix_web::HttpServer::new(move || {
879 let app: Rc<S::AppT> = Rc::new(app_creator2.clone().into_app(
880 &handle2,
881 &ac_context2,
882 generation,
883 ));
884
885 actix_web::App::new().wrap(S::cors()).configure(
886 |sc: &mut web::ServiceConfig| {
887 AppBase::<S>::configure_actix_app(&app, sc);
889
890 app.configure_actix_app(sc);
892
893 let weak = Rc::downgrade(&app);
894
895 tokio::task::spawn_local(async move {
896 let thread = std::thread::current();
897
898 log::debug!(
899 "{}: spawned local task on thread {:?}",
900 S::NAME,
901 thread.id()
902 );
903
904 let _deferred = defer(|| {
905 log::debug!(
906 "{}: local task on thread {:?} stopped",
907 S::NAME,
908 thread.id()
909 );
910 });
911
912 S::AppT::local_task(weak).await;
913 });
914 },
915 )
916 })
917 .disable_signals(); builder = match self.listener.as_ref() {
920 Some(listener) => {
924 builder.listen(listener.try_clone().context("cloning pre-bound listener")?)?
925 }
926 None => builder.bind(server_config)?,
927 };
928
929 if let Some(worker_count) = self.worker_count {
930 builder = builder.workers(worker_count.get());
931 }
932
933 builder.run()
934 };
935
936 let actix_server_handle = actual_actix_server.handle().clone();
938 let actix_join_handle = tokio::task::spawn(actual_actix_server);
939
940 let (ph_shutdown_sender, ph_shutdown_receiver) = tokio::sync::oneshot::channel();
942 let ph_join_handle =
943 tokio::task::spawn_local(self.pubhubs_server.clone().run_until_modifier(
944 ph_shutdown_receiver,
945 Rc::new(app_creator.into_app(&handle, &ac_context, generation)),
946 ));
947
948 Ok(Handles {
949 actix_server_handle,
950 actix_join_handle,
951 command_receiver,
952 ph_join_handle,
953 ph_shutdown_sender: Some(ph_shutdown_sender),
954 drop_bomb: crate::misc::drop_ext::Bomb::panic(|| {
955 format!("Part of {} was not shut down properly", S::NAME)
956 }),
957 })
958 }
959
960 pub async fn run(mut self) -> Result<()> {
961 loop {
962 let modifier = self.run_until_modifier().await?;
963
964 let pubhubs_server_mutref: &mut S =
965 Rc::get_mut(&mut self.pubhubs_server).expect("pubhubs_server is still borrowed");
966
967 let modifier_fmt = format!("{modifier}"); log::info!("{}: applying modification {:?}", S::NAME, modifier_fmt);
970
971 if !modifier.modify(pubhubs_server_mutref) {
972 log::info!(
973 "{}: not restarting upon request of {:?}",
974 S::NAME,
975 modifier_fmt
976 );
977 return Ok(());
978 }
979
980 self.generation += 1;
981 log::info!("{}: restarting...", S::NAME);
982 }
983 }
984
985 pub async fn run_until_modifier(
986 &mut self,
987 ) -> Result<crate::servers::server::BoxModifier<S>, anyhow::Error> {
988 let mut handles = self.create_actix_server()?;
989
990 let result = Self::run_until_modifier_inner(&mut handles, self).await;
991
992 handles.shutdown().await?;
993
994 result
995 }
996
997 async fn run_until_modifier_inner(
998 handles: &mut Handles<S>,
999 runner: &mut Runner<S>,
1000 ) -> Result<crate::servers::server::BoxModifier<S>, anyhow::Error> {
1001 loop {
1002 match handles.run_until_command(runner).await? {
1003 Command::Modify(modifier) => {
1004 log::debug!(
1005 "Stopping {} for modification {:?}...",
1006 S::NAME,
1007 modifier.to_string()
1008 );
1009
1010 return Ok::<_, anyhow::Error>(modifier);
1011 }
1012 Command::Inspect(inspector) => {
1013 log::debug!(
1014 "{}: applying inspection {:?}",
1015 S::NAME,
1016 inspector.to_string()
1017 );
1018 inspector.inspect(&*runner.pubhubs_server);
1019 }
1020 Command::Exit => {
1021 log::debug!("Stopping {}, as requested", S::NAME);
1022
1023 return Ok::<_, anyhow::Error>(Box::new(crate::servers::server::Exiter));
1024 }
1025 }
1026 }
1027 }
1028}