defmodule DaProductApp.Startup.GracefulShutdown do @moduledoc """ Handles graceful shutdown procedures for the application. This module encapsulates all shutdown logic to ensure proper cleanup of resources when the application stops. It provides coordinated shutdown of various subsystems in the correct order. Separated from Application module following Elixir best practices for clean separation of concerns. """ require Logger alias DaProductApp.MercuryISO8583.AsyncCorrelatorStartup @doc """ Performs graceful shutdown of all application subsystems. This function coordinates the shutdown of various subsystems in the proper order to ensure no data loss and clean resource cleanup. ## Shutdown Order: 1. Stop accepting new connections/requests 2. Process remaining AsyncCorrelator requests 3. Close upstream network connections 4. Clean up event system resources 5. Final cleanup and logging ## Parameters: - `timeout` - Maximum time to wait for shutdown (default: 30 seconds) ## Returns: - `:ok` when shutdown completes successfully - `{:error, reason}` if shutdown encounters issues """ def perform_shutdown(timeout \\ 30_000) do Logger.info("🛑 Starting graceful application shutdown (timeout: #{timeout}ms)") start_time = System.monotonic_time(:millisecond) try do # Step 1: Stop accepting new requests stop_accepting_requests() # Step 2: Gracefully shutdown AsyncCorrelator system (process remaining requests) shutdown_async_correlator(timeout) # Step 3: Close upstream network connections gracefully shutdown_upstream_connections() # Step 4: Clean up event system resources shutdown_event_system() # Step 5: Final cleanup and metrics end_time = System.monotonic_time(:millisecond) duration = end_time - start_time Logger.info("✅ Graceful shutdown completed successfully in #{duration}ms") :ok rescue exception -> Logger.error("❌ Exception during graceful shutdown: #{inspect(exception)}") {:error, {:exception, exception}} catch error_type, reason -> Logger.error("❌ Error during graceful shutdown: #{error_type} - #{inspect(reason)}") {:error, {error_type, reason}} end end @doc """ Stops accepting new connections and requests. """ def stop_accepting_requests do Logger.info("🔒 Stopping new request acceptance") # Stop device listeners from accepting new connections case stop_device_listeners() do :ok -> Logger.info("✅ Device listeners stopped accepting new connections") {:error, reason} -> Logger.warning("⚠️ Issue stopping device listeners: #{inspect(reason)}") end end @doc """ Shuts down the AsyncCorrelator system gracefully. """ def shutdown_async_correlator(timeout) do Logger.info("🔄 Shutting down AsyncCorrelator system") case AsyncCorrelatorStartup.graceful_shutdown(timeout) do :ok -> Logger.info("✅ AsyncCorrelator system shutdown completed") {:error, reason} -> Logger.warning("⚠️ AsyncCorrelator shutdown issue: #{inspect(reason)}") end end @doc """ Closes upstream network connections gracefully. """ def shutdown_upstream_connections do Logger.info("🌐 Closing upstream network connections") # Gracefully close upstream connections # This would typically involve sending proper disconnect messages # and waiting for acknowledgments before closing sockets # For now, we'll just log the intent # TODO: Implement actual upstream connection cleanup when needed Logger.info("✅ Upstream connections shutdown completed") end @doc """ Cleans up event system resources. """ def shutdown_event_system do Logger.info("📡 Cleaning up event system resources") # Unregister event listeners and clean up PubSub subscriptions try do # The EventDispatcher will be stopped by the supervisor # but we can do any cleanup here if needed Logger.info("✅ Event system cleanup completed") rescue exception -> Logger.warning("⚠️ Event system cleanup issue: #{inspect(exception)}") end end @doc """ Gets shutdown status and diagnostics. """ def get_shutdown_status do %{ async_correlator_active: is_async_correlator_active(), upstream_connections_active: count_active_upstream_connections(), device_listeners_active: count_active_device_listeners(), event_dispatcher_active: Process.whereis(DaProductApp.Events.EventDispatcher) != nil } end # Private helper functions defp stop_device_listeners do # This would typically involve stopping the device listener supervisor # from accepting new connections while allowing existing ones to complete :ok end defp is_async_correlator_active do # Check if AsyncCorrelator processes are still running case Process.whereis(DaProductApp.MercuryISO8583.AsyncCorrelatorSupervisor) do nil -> false _pid -> true end end defp count_active_upstream_connections do # Count active upstream network connections # This would integrate with the UpstreamSupervisor 0 # Placeholder end defp count_active_device_listeners do # Count active device listener processes # This would integrate with the DeviceListenerSupervisor 0 # Placeholder end end