aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support/reloader.rb
blob: 44062e3491682c77179dfb701c5fd286d37d7b46 (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
# frozen_string_literal: true

require_relative "execution_wrapper"

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

    def self.to_prepare(*args, &block)
      set_callback(:prepare, *args, &block)
    end

    def self.before_class_unload(*args, &block)
      set_callback(:class_unload, *args, &block)
    end

    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