Stop triggers reconciler shutdown and waits for it to complete. The ctx parameter provides a timeout, if cleanup doesn't finish within this timeout, Stop() logs an error and returns.
(ctx context.Context, cause error)
| 277 | // The ctx parameter provides a timeout, if cleanup doesn't finish within |
| 278 | // this timeout, Stop() logs an error and returns. |
| 279 | func (c *StoreReconciler) Stop(ctx context.Context, cause error) { |
| 280 | if cause != nil { |
| 281 | c.logger.Info(context.Background(), "stopping reconciler", slog.F("cause", cause.Error())) |
| 282 | } else { |
| 283 | c.logger.Info(context.Background(), "stopping reconciler") |
| 284 | } |
| 285 | |
| 286 | // Mark the reconciler as stopped. If it was already stopped, return early. |
| 287 | // If the reconciler is running, we'll proceed to shut it down. |
| 288 | // |
| 289 | // NOTE: we need to *prospectively* mark this as stopped to prevent the |
| 290 | // reconciler from being stopped multiple times and causing problems. |
| 291 | c.mu.Lock() |
| 292 | if c.stopped { |
| 293 | c.mu.Unlock() |
| 294 | return |
| 295 | } |
| 296 | c.stopped = true |
| 297 | running := c.running |
| 298 | c.mu.Unlock() |
| 299 | |
| 300 | // Unregister prebuilds state and operational metrics. |
| 301 | if c.metrics != nil && c.registerer != nil { |
| 302 | if !c.registerer.Unregister(c.metrics) { |
| 303 | // The API doesn't allow us to know why the de-registration failed, but it's not very consequential. |
| 304 | // The only time this would be an issue is if the premium license is removed, leading to the feature being |
| 305 | // disabled (and consequently this Stop method being called), and then adding a new license which enables the |
| 306 | // feature again. If the metrics cannot be registered, it'll log an error from NewStoreReconciler. |
| 307 | c.logger.Warn(context.Background(), "failed to unregister metrics collector") |
| 308 | } |
| 309 | if c.reconciliationDuration != nil { |
| 310 | if !c.registerer.Unregister(c.reconciliationDuration) { |
| 311 | c.logger.Warn(context.Background(), "failed to unregister reconciliation duration histogram") |
| 312 | } |
| 313 | } |
| 314 | } |
| 315 | |
| 316 | // If the reconciler is not running, there's nothing else to do. |
| 317 | if !running { |
| 318 | return |
| 319 | } |
| 320 | |
| 321 | // Trigger reconciler shutdown by canceling its internal context. |
| 322 | if c.cancelFn != nil { |
| 323 | c.cancelFn(cause) |
| 324 | } |
| 325 | |
| 326 | // Wait for the reconciler to signal that it has fully exited and cleaned up. |
| 327 | select { |
| 328 | // Timeout: reconciler didn't finish cleanup within the timeout period. |
| 329 | case <-ctx.Done(): |
| 330 | // nolint:gocritic // it's okay to use slog.F() for an error in this case |
| 331 | // because we want to differentiate two different types of errors: ctx.Err() and context.Cause() |
| 332 | c.logger.Error( |
| 333 | context.Background(), |
| 334 | "reconciler stop exited prematurely", |
| 335 | slog.Error(ctx.Err()), |
| 336 | slog.F("cause", context.Cause(ctx)), |