@@ -2,14 +2,16 @@ use std::collections::HashMap;
22use std:: fmt;
33use std:: result:: Result as StdResult ;
44
5- use futures:: future:: { BoxFuture , join_all} ;
5+ use futures:: future:: BoxFuture ;
6+ use futures:: stream:: { FuturesUnordered , StreamExt } ;
67use rocket:: catcher:: Handler ;
78use rocket:: fairing:: Fairing ;
89use rocket:: http:: Status ;
910use rocket:: http:: uri:: Origin ;
1011use rocket:: response:: { Redirect , Responder , status} ;
1112use rocket:: serde:: json:: Value ;
1213use rocket:: { Build , Catcher , Error as RocketError , Ignite , Request , Rocket , Route , catcher} ;
14+ use tracing:: { error, info} ;
1315
1416use tokio:: sync:: mpsc;
1517use tokio:: task_local;
@@ -35,6 +37,53 @@ task_local! {
3537 pub static CONFIG : ReloadableLitConfig ;
3638}
3739
40+ #[ derive( Debug , Clone ) ]
41+ struct RocketTarget {
42+ address : String ,
43+ port : u16 ,
44+ tls_enabled : bool ,
45+ role : & ' static str ,
46+ }
47+
48+ #[ derive( Debug , Clone ) ]
49+ struct RocketTargets ( Vec < RocketTarget > ) ;
50+
51+ impl From < & [ Rocket < Ignite > ] > for RocketTargets {
52+ fn from ( ignited : & [ Rocket < Ignite > ] ) -> Self {
53+ Self (
54+ ignited
55+ . iter ( )
56+ . enumerate ( )
57+ . map ( |( idx, r) | {
58+ let cfg = r. config ( ) ;
59+ RocketTarget {
60+ address : cfg. address . to_string ( ) ,
61+ port : cfg. port ,
62+ tls_enabled : cfg. tls . is_some ( ) ,
63+ role : if idx == 0 { "primary" } else { "aux" } ,
64+ }
65+ } )
66+ . collect ( ) ,
67+ )
68+ }
69+ }
70+
71+ impl RocketTargets {
72+ fn log ( & self ) {
73+ for ( idx, t) in self . 0 . iter ( ) . enumerate ( ) {
74+ let proto = if t. tls_enabled { "https" } else { "http" } ;
75+ info ! (
76+ rocket_index = idx,
77+ proto,
78+ role = t. role,
79+ address = %t. address,
80+ port = t. port,
81+ "rocket launch starting"
82+ ) ;
83+ }
84+ }
85+ }
86+
3887pub struct Launcher {
3988 cfg : ReloadableLitConfig ,
4089 rocket : Option < Rocket < Build > > ,
@@ -154,15 +203,45 @@ impl Launcher {
154203
155204 pub async fn launch ( & mut self ) -> StdResult < ( ) , RocketError > {
156205 if self . ignited . is_empty ( ) {
206+ error ! ( "rocket launcher - launch called before ignite (no ignited rockets)" ) ;
157207 panic ! ( "ignite must be called prior to launch" ) ;
158208 }
159209
160- let mut futures = Vec :: new ( ) ;
161- while !self . ignited . is_empty ( ) {
162- futures. push ( self . ignited . remove ( 0 ) . launch ( ) ) ;
210+ // Extra diagnostics: log the configured bind targets and surface bind/listen failures
211+ let targets = RocketTargets :: from ( self . ignited . as_slice ( ) ) ;
212+ targets. log ( ) ;
213+
214+ // FuturesUnordered so we can fail fast on the first launch error (irrespective of other launches)
215+ let mut futures: FuturesUnordered < _ > = FuturesUnordered :: new ( ) ;
216+ for ( idx, rocket) in self . ignited . drain ( ..) . enumerate ( ) {
217+ futures. push ( async move { ( idx, rocket. launch ( ) . await ) } ) ;
163218 }
164219
165- join_all ( futures) . await ;
220+ // Each `launch()` future will typically run indefinitely while the server is up.
221+ // We await launch results as they complete and fail fast on the first error.
222+ while let Some ( ( idx, res) ) = futures. next ( ) . await {
223+ if let Err ( e) = res {
224+ let t = targets. 0 . get ( idx) . cloned ( ) . unwrap_or ( RocketTarget {
225+ address : "<unknown>" . to_string ( ) ,
226+ port : 0 ,
227+ tls_enabled : false ,
228+ role : "unknown" ,
229+ } ) ;
230+ let proto = if t. tls_enabled { "https" } else { "http" } ;
231+
232+ error ! (
233+ rocket_index = idx,
234+ proto,
235+ role = t. role,
236+ address = %t. address,
237+ port = t. port,
238+ error = ?e,
239+ "rocket launch failed (likely bind/listen failure for configured address/port)"
240+ ) ;
241+
242+ return Err ( e) ;
243+ }
244+ }
166245
167246 Ok ( ( ) )
168247 }
0 commit comments