Spawn a background worker that watches the parent directories of the cert, key, and CA files and calls [`reload`](Self::reload) when changes are detected. A 1-second debounce window coalesces rapid filesystem events (such as Kubernetes Secret volume atomic swaps) into a single reload. If reload fails, the old config is preserved and a warning is logged. The worker exits when the `shutdown` watch
(
&self,
mut shutdown: watch::Receiver<bool>,
)
| 125 | /// gateway to perform a graceful shutdown without orphaned reload |
| 126 | /// tasks. |
| 127 | pub fn spawn_reload_worker( |
| 128 | &self, |
| 129 | mut shutdown: watch::Receiver<bool>, |
| 130 | ) -> tokio::task::JoinHandle<()> { |
| 131 | if self.reload_spawned.swap(true, Ordering::Relaxed) { |
| 132 | warn!("TLS certificate reload worker already spawned, ignoring duplicate call"); |
| 133 | return tokio::spawn(async {}); |
| 134 | } |
| 135 | |
| 136 | let this = self.clone(); |
| 137 | |
| 138 | // Collect unique parent directories to watch. |
| 139 | let cert_dir = self.cert_path.parent().unwrap_or_else(|| Path::new(".")); |
| 140 | let key_dir = self.key_path.parent().unwrap_or_else(|| Path::new(".")); |
| 141 | let mut dirs = vec![cert_dir.to_path_buf()]; |
| 142 | if key_dir != cert_dir { |
| 143 | dirs.push(key_dir.to_path_buf()); |
| 144 | } |
| 145 | if let Some(ref ca) = self.client_ca_path { |
| 146 | let ca_dir = ca.parent().unwrap_or_else(|| Path::new(".")); |
| 147 | if ca_dir != cert_dir && ca_dir != key_dir { |
| 148 | dirs.push(ca_dir.to_path_buf()); |
| 149 | } |
| 150 | } |
| 151 | |
| 152 | let debounce = Duration::from_secs(1); |
| 153 | |
| 154 | tokio::spawn(async move { |
| 155 | let (tx, mut rx) = mpsc::unbounded_channel(); |
| 156 | |
| 157 | // recommended_watcher runs its own thread; we bridge events into |
| 158 | // the tokio runtime via the unbounded mpsc channel. |
| 159 | let mut watcher = match notify::recommended_watcher( |
| 160 | move |res: std::result::Result<Event, notify::Error>| { |
| 161 | if let Ok(event) = res |
| 162 | && matches!(event.kind, EventKind::Modify(_) | EventKind::Create(_)) |
| 163 | { |
| 164 | let _ = tx.send(()); |
| 165 | } |
| 166 | }, |
| 167 | ) { |
| 168 | Ok(w) => w, |
| 169 | Err(e) => { |
| 170 | warn!(error = %e, "Failed to start TLS cert file watcher, hot-reload disabled"); |
| 171 | return; |
| 172 | } |
| 173 | }; |
| 174 | |
| 175 | for dir in &dirs { |
| 176 | if let Err(e) = watcher.watch(dir, RecursiveMode::NonRecursive) { |
| 177 | warn!(error = %e, dir = %dir.display(), "Failed to watch TLS cert directory, hot-reload disabled"); |
| 178 | return; |
| 179 | } |
| 180 | } |
| 181 | |
| 182 | info!(?dirs, "TLS certificate file watcher started"); |
| 183 | |
| 184 | // Event loop with manual debounce. |