aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support/reloader.rb
blob: fea18e97129a628e5398b028a513b467fbe5f006 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# frozen_string_literal: true

require "active_support/execution_wrapper"
require "active_support/executor"

module ActiveSupport
  #--
  # This class defines several callbacks:
  #
  #   to_prepare -- Run once at application startup, and also from
  #   +to_run+.
  #
  #   to_run -- Run before a work run that is reloading. If
  #   +reload_classes_only_on_change+ is true (the default), the class
  #   unload will have already occurred.
  #
  #   to_complete -- Run after a work run that has reloaded. If
  #   +reload_classes_only_on_change+ is false, the class unload will
  #   have occurred after the work run, but before this callback.
  #
  #   before_class_unload -- Run immediately before the classes are
  #   unloaded.
  #
  #   after_class_unload -- Run immediately after the classes are
  #   unloaded.
  #
  class Reloader < ExecutionWrapper
    define_callbacks :prepare

    define_callbacks :class_unload

    # Registers a callback that will run once at application startup and every time the code is reloaded.
    def self.to_prepare(*args, &block)
      set_callback(:prepare, *args, &block)
    end

    # Registers a callback that will run immediately before the classes are unloaded.
    def self.before_class_unload(*args, &block)
      set_callback(:class_unload, *args, &block)
    end

    # Registers a callback that will run immediately after the classes are unloaded.
    def self.after_class_unload(*args, &block)
      set_callback(:class_unload, :after, *args, &block)
    end

    to_run(:after) { self.class.prepare! }

    # Initiate a manual reload
    def self.reload!
      executor.wrap do
        new.tap do |instance|
          begin
            instance.run!
          ensure
            instance.complete!
          end
        end
      end
      prepare!
    end

    def self.run! # :nodoc:
      if check!
        super
      else
        Null
      end
    end

    # Run the supplied block as a work unit, reloading code as needed
    def self.wrap
      executor.wrap do
        super
      end
    end

    class_attribute :executor, default: Executor
    class_attribute :check, default: lambda { false }

    def self.check! # :nodoc:
      @should_reload ||= check.call
    end

    def self.reloaded! # :nodoc:
      @should_reload = false
    end

    def self.prepare! # :nodoc:
      new.run_callbacks(:prepare)
    end

    def initialize
      super
      @locked = false
    end

    # Acquire the ActiveSupport::Dependencies::Interlock unload lock,
    # ensuring it will be released automatically
    def require_unload_lock!
      unless @locked
        ActiveSupport::Dependencies.interlock.start_unloading
        @locked = true
      end
    end

    # Release the unload lock if it has been previously obtained
    def release_unload_lock!
      if @locked
        @locked = false
        ActiveSupport::Dependencies.interlock.done_unloading
      end
    end

    def run! # :nodoc:
      super
      release_unload_lock!
    end

    def class_unload!(&block) # :nodoc:
      require_unload_lock!
      run_callbacks(:class_unload, &block)
    end

    def complete! # :nodoc:
      super
      self.class.reloaded!
    ensure
      release_unload_lock!
    end
  end
end