# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1

module Vagrant
  module Util
    # Utility class which allows blocks of code to be marked as "busy"
    # with a specified interrupt handler. During busy areas of code, it
    # is often undesirable for SIGINTs to immediately kill the application.
    # This class is a helper to cleanly register callbacks to handle this
    # situation.
    class Busy
      @@registered = []
      @@mutex = Mutex.new

      class << self
        # Mark a given block of code as a "busy" block of code, which will
        # register a SIGINT handler for the duration of the block. When a
        # SIGINT occurs, the `sig_callback` proc will be called. It is up
        # to the callback to behave properly and exit the application.
        def busy(sig_callback)
          register(sig_callback)
          return yield
        ensure
          unregister(sig_callback)
        end

        # Registers a SIGINT handler. This typically is called from {busy}.
        # Callbacks are only registered once, so calling this multiple times
        # with the same callback has no consequence.
        def register(sig_callback)
          @@mutex.synchronize do
            registered << sig_callback
            registered.uniq!

            # Register the handler if this is our first callback.
            Signal.trap("INT") { fire_callbacks } if registered.length == 1
          end
        end

        # Unregisters a SIGINT handler.
        def unregister(sig_callback)
          @@mutex.synchronize do
            registered.delete(sig_callback)

            # Remove the signal trap if no more registered callbacks exist
            Signal.trap("INT", "DEFAULT") if registered.empty?
          end
        end

        # Fires all the registered callbacks.
        def fire_callbacks
          registered.reverse.each { |r| r.call }
        end

        # Helper method to get access to the class variable. This is mostly
        # exposed for tests. This shouldn't be mucked with directly, since it's
        # structure may change at any time.
        def registered; @@registered; end
      end
    end
  end
end
