From 1c5ff5a9a615f73ba4731549c1e328b21b753290 Mon Sep 17 00:00:00 2001 From: Adam Cooke Date: Sat, 24 Feb 2024 15:10:02 +0000 Subject: [PATCH] feat: add option to delay starting processes until all migrations are run --- config/initializers/_wait_for_migrations.rb | 5 ++ lib/migration_waiter.rb | 60 +++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 config/initializers/_wait_for_migrations.rb create mode 100644 lib/migration_waiter.rb diff --git a/config/initializers/_wait_for_migrations.rb b/config/initializers/_wait_for_migrations.rb new file mode 100644 index 0000000..12640f1 --- /dev/null +++ b/config/initializers/_wait_for_migrations.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require "migration_waiter" + +MigrationWaiter.wait_if_appropriate diff --git a/lib/migration_waiter.rb b/lib/migration_waiter.rb new file mode 100644 index 0000000..65e39ef --- /dev/null +++ b/lib/migration_waiter.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +# This initializer will wait for all pending migrations to be applied before +# continuing to start the application. This is useful when running the application +# in a cluster where migrations are run in a separate job which runs at the same +# time as the other processes. + +class MigrationWaiter + + ATTEMPTS = ENV.fetch("MIGRATION_WAITER_ATTEMPTS", 120) + SLEEP_TIME = ENV.fetch("MIGRATION_WAITER_SLEEP_TIME", 2) + + class << self + + def wait + attempts_remaining = ATTEMPTS + loop do + pending_migrations = ActiveRecord::Base.connection.migration_context.open.pending_migrations.size + if pending_migrations.zero? + Postal.logger.info "no pending migrations, continuing" + return + end + + attempts_remaining -= 1 + + if attempts_remaining.zero? + Postal.logger.info "#{pending_migrations} migration(s) are still pending after #{ATTEMPTS} attempts, exiting" + Process.exit(1) + else + Postal.logger.info "waiting for #{pending_migrations} migration(s) to be applied (#{attempts_remaining} remaining)" + sleep SLEEP_TIME + end + end + end + + def wait_if_appropriate + # Don't wait if not configured + return unless ENV.fetch("MIGRATION_WAITER_ENABLED", "false") == "true" + + # Don't wait in the console, rake tasks or rails commands + return if console? || rake_task? || rails_command? + + wait + end + + def console? + Rails.const_defined?("Console") + end + + def rake_task? + Rake.application.top_level_tasks.any? + end + + def rails_command? + caller.any? { |c| c =~ /rails\/commands/ } + end + + end + +end