summaryrefslogtreecommitdiff
path: root/lib/gitlab/cluster/lifecycle_events.rb
blob: 16d9bce8de8dca22a7d723c997d6f41695135b6f (plain)
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
# frozen_string_literal: true

module Gitlab
  module Cluster
    #
    # LifecycleEvents lets Rails initializers register application startup hooks
    # that are sensitive to forking. For example, to defer the creation of
    # watchdog threads. This lets us abstract away the Unix process
    # lifecycles of Unicorn, Sidekiq, Puma, Puma Cluster, etc.
    #
    # We have three lifecycle events.
    #
    # - before_fork (only in forking processes)
    # - worker_start
    # - before_master_restart (only in forking processes)
    #
    # Blocks will be executed in the order in which they are registered.
    #
    class LifecycleEvents
      class << self
        #
        # Hook registration methods (called from initializers)
        #
        def on_worker_start(&block)
          if in_clustered_environment?
            # Defer block execution
            (@worker_start_hooks ||= []) << block
          else
            yield
          end
        end

        def on_before_fork(&block)
          return unless in_clustered_environment?

          # Defer block execution
          (@before_fork_hooks ||= []) << block
        end

        def on_master_restart(&block)
          return unless in_clustered_environment?

          # Defer block execution
          (@master_restart_hooks ||= []) << block
        end

        def on_master_start(&block)
          if in_clustered_environment?
            on_before_fork(&block)
          else
            on_worker_start(&block)
          end
        end

        #
        # Lifecycle integration methods (called from unicorn.rb, puma.rb, etc.)
        #
        def do_worker_start
          @worker_start_hooks&.each do |block|
            block.call
          end
        end

        def do_before_fork
          @before_fork_hooks&.each do |block|
            block.call
          end
        end

        def do_master_restart
          @master_restart_hooks&.each do |block|
            block.call
          end
        end

        # Puma doesn't use singletons (which is good) but
        # this means we need to pass through whether the
        # puma server is running in single mode or cluster mode
        def set_puma_options(options)
          @puma_options = options
        end

        private

        def in_clustered_environment?
          # Sidekiq doesn't fork
          return false if Sidekiq.server?

          # Unicorn always forks
          return true if defined?(::Unicorn)

          # Puma sometimes forks
          return true if in_clustered_puma?

          # Default assumption is that we don't fork
          false
        end

        def in_clustered_puma?
          return false unless defined?(::Puma)

          @puma_options && @puma_options[:workers] && @puma_options[:workers] > 0
        end
      end
    end
  end
end