unicorn.conf.rb 8.27 KB
Newer Older
1
2
3
# frozen_string_literal: true
# See http://unicorn.bogomips.org/Unicorn/Configurator.html

4
if (ENV["LOGSTASH_UNICORN_URI"] || "").length > 0
5
  require_relative '../lib/discourse_logstash_logger'
6
  require_relative '../lib/unicorn_logstash_patch'
7
8
9
10
11
12
13
14
15
16
17
  logger DiscourseLogstashLogger.logger(uri: ENV['LOGSTASH_UNICORN_URI'], type: :unicorn)
end

discourse_path = "/discourse"

# tune down if not enough ram
worker_processes (ENV["UNICORN_WORKERS"] || 3).to_i

working_directory discourse_path

# listen "#{discourse_path}/tmp/sockets/unicorn.sock"
18
listen ENV["UNICORN_LISTENER"] || "#{(ENV["UNICORN_BIND_ALL"] ? "" : "127.0.0.1:")}#{(ENV["UNICORN_PORT"] || 3000).to_i}"
19
20
21
22
23
24
25
26

if !File.exist?("#{discourse_path}/tmp/pids")
  FileUtils.mkdir_p("#{discourse_path}/tmp/pids")
end

# feel free to point this anywhere accessible on the filesystem
pid (ENV["UNICORN_PID_PATH"] || "#{discourse_path}/tmp/pids/unicorn.pid")

27
if ENV["RAILS_ENV"] != "production"
Ismael Posada Trobo's avatar
Ismael Posada Trobo committed
28
  logger Logger.new(STDOUT)
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
  # we want a longer timeout in dev cause first request can be really slow
  timeout (ENV["UNICORN_TIMEOUT"] && ENV["UNICORN_TIMEOUT"].to_i || 60)
else
  # By default, the Unicorn logger will write to stderr.
  # Additionally, some applications/frameworks log to stderr or stdout,
  # so prevent them from going to /dev/null when daemonized here:
  # stderr_path "#{discourse_path}/log/unicorn.stderr.log"
  # stderr_path "/proc/1/fd/2"
  stderr_path "/dev/stderr"
  # stdout_path "#{discourse_path}/log/unicorn.stdout.log"
  # stdout_path "/proc/1/fd/1"
  stdout_path "/dev/stdout"
  # nuke workers after 30 seconds instead of 60 seconds (the default)
  timeout 30
end

# important for Ruby 2.0
preload_app true

# Enable this flag to have unicorn test client connections by writing the
# beginning of the HTTP headers before calling the application.  This
# prevents calling the application for connections that have disconnected
# while queued.  This is only guaranteed to detect clients on the same
# host unicorn runs on, and unlikely to detect disconnects even on a
# fast LAN.
check_client_connection false

initialized = false
before_fork do |server, worker|

  unless initialized
60
    Discourse.preload_rails!
61
62
63
64
    # V8 does not support forking, make sure all contexts are disposed
    ObjectSpace.each_object(MiniRacer::Context) { |c| c.dispose }

    # get rid of rubbish so we don't share it
65
66
67
    # longer term we will use compact! here
    GC.start
    GC.start
68
69
70
71
72
73
74
75
    GC.start

    initialized = true

    supervisor = ENV['UNICORN_SUPERVISOR_PID'].to_i
    if supervisor > 0
      Thread.new do
        while true
76
          unless File.exist?("/proc/#{supervisor}")
77
78
79
80
81
82
83
84
85
86
            puts "Kill self supervisor is gone"
            Process.kill "TERM", Process.pid
          end
          sleep 2
        end
      end
    end

    sidekiqs = ENV['UNICORN_SIDEKIQS'].to_i
    if sidekiqs > 0
87
      server.logger.info "starting #{sidekiqs} supervised sidekiqs"
88
89
90
91
92
93
94
95
96
97
98
99
100

      require 'demon/sidekiq'
      Demon::Sidekiq.after_fork do
        DiscourseEvent.trigger(:sidekiq_fork_started)
      end

      Demon::Sidekiq.start(sidekiqs)

      Signal.trap("SIGTSTP") do
        STDERR.puts "#{Time.now}: Issuing stop to sidekiq"
        Demon::Sidekiq.stop
      end

101
102
103
104
105
106
      # Trap USR1, so we can re-issue to sidekiq workers
      # but chain the default unicorn implementation as well
      old_handler = Signal.trap("USR1") do
        Demon::Sidekiq.kill("USR1")
        old_handler.call
      end
107
    end
108

109
    if ENV['DISCOURSE_ENABLE_EMAIL_SYNC_DEMON'] == 'true'
110
      server.logger.info "starting up EmailSync demon"
111
112
113
114
115
116
      Demon::EmailSync.start
      Signal.trap("SIGTSTP") do
        STDERR.puts "#{Time.now}: Issuing stop to EmailSync"
        Demon::EmailSync.stop
      end
    end
117

118
119
120
121
122
    DiscoursePluginRegistry.demon_processes.each do |demon_class|
      server.logger.info "starting #{demon_class.prefix} demon"
      demon_class.start
    end

123
124
    class ::Unicorn::HttpServer
      alias :master_sleep_orig :master_sleep
125

126
127
128
129
130
      def max_sidekiq_rss
        rss = `ps -eo rss,args | grep sidekiq | grep -v grep | awk '{print $1}'`
          .split("\n")
          .map(&:to_i)
          .max
131

132
        rss ||= 0
133

134
135
        rss * 1024
      end
136

137
138
139
      def max_allowed_sidekiq_rss
        [ENV['UNICORN_SIDEKIQ_MAX_RSS'].to_i, 500].max.megabytes
      end
140

141
142
143
144
145
146
147
      def force_kill_rogue_sidekiq
        info = `ps -eo pid,rss,args | grep sidekiq | grep -v grep | awk '{print $1,$2}'`
        info.split("\n").each do |row|
          pid, mem = row.split(" ").map(&:to_i)
          if pid > 0 && (mem * 1024) > max_allowed_sidekiq_rss
            Rails.logger.warn "Detected rogue Sidekiq pid #{pid} mem #{mem * 1024}, killing"
            Process.kill("KILL", pid) rescue nil
148
149
          end
        end
150
      end
151

152
153
154
      def check_sidekiq_heartbeat
        @sidekiq_heartbeat_interval ||= 30.minutes
        @sidekiq_next_heartbeat_check ||= Time.now.to_i + @sidekiq_heartbeat_interval
155

156
        if @sidekiq_next_heartbeat_check < Time.now.to_i
157

158
159
          last_heartbeat = Jobs::RunHeartbeat.last_heartbeat
          restart = false
160

161
162
163
164
165
          sidekiq_rss = max_sidekiq_rss
          if sidekiq_rss > max_allowed_sidekiq_rss
            Rails.logger.warn("Sidekiq is consuming too much memory (using: %0.2fM) for '%s', restarting" % [(sidekiq_rss.to_f / 1.megabyte), ENV["DISCOURSE_HOSTNAME"]])
            restart = true
          end
166

167
168
169
          if last_heartbeat < Time.now.to_i - @sidekiq_heartbeat_interval
            STDERR.puts "Sidekiq heartbeat test failed, restarting"
            Rails.logger.warn "Sidekiq heartbeat test failed, restarting"
170

171
172
173
            restart = true
          end
          @sidekiq_next_heartbeat_check = Time.now.to_i + @sidekiq_heartbeat_interval
174

175
176
177
178
          if restart
            Demon::Sidekiq.restart
            sleep 10
            force_kill_rogue_sidekiq
179
          end
180
          Discourse.redis.close
181
        end
182
183
184
185
186
187
188
189
190
191
192
193
      end

      def max_email_sync_rss
        return 0 if Demon::EmailSync.demons.empty?

        email_sync_pids = Demon::EmailSync.demons.map { |uid, demon| demon.pid }
        return 0 if email_sync_pids.empty?

        rss = `ps -eo pid,rss,args | grep '#{email_sync_pids.join('|')}' | grep -v grep | awk '{print $2}'`
          .split("\n")
          .map(&:to_i)
          .max
194

195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
        (rss || 0) * 1024
      end

      def max_allowed_email_sync_rss
        [ENV['UNICORN_EMAIL_SYNC_MAX_RSS'].to_i, 500].max.megabytes
      end

      def check_email_sync_heartbeat
        # Skip first check to let process warm up
        @email_sync_next_heartbeat_check ||= (Time.now + Demon::EmailSync::HEARTBEAT_INTERVAL).to_i

        return if @email_sync_next_heartbeat_check > Time.now.to_i
        @email_sync_next_heartbeat_check = (Time.now + Demon::EmailSync::HEARTBEAT_INTERVAL).to_i

        restart = false

        # Restart process if it does not respond anymore
        last_heartbeat_ago = Time.now.to_i - Discourse.redis.get(Demon::EmailSync::HEARTBEAT_KEY).to_i
        if last_heartbeat_ago > Demon::EmailSync::HEARTBEAT_INTERVAL.to_i
          STDERR.puts("EmailSync heartbeat test failed (last heartbeat was #{last_heartbeat_ago}s ago), restarting")
          restart = true
        end

        # Restart process if memory usage is too high
        email_sync_rss = max_email_sync_rss
        if email_sync_rss > max_allowed_email_sync_rss
          STDERR.puts("EmailSync is consuming too much memory (using: %0.2fM) for '%s', restarting" % [(email_sync_rss.to_f / 1.megabyte), ENV["DISCOURSE_HOSTNAME"]])
          restart = true
        end

        Demon::EmailSync.restart if restart
      end

      def master_sleep(sec)
        sidekiqs = ENV['UNICORN_SIDEKIQS'].to_i
        if sidekiqs > 0
231
232
          Demon::Sidekiq.ensure_running
          check_sidekiq_heartbeat
233
        end
234

235
236
237
        if ENV['DISCOURSE_ENABLE_EMAIL_SYNC_DEMON'] == 'true'
          Demon::EmailSync.ensure_running
          check_email_sync_heartbeat
238
        end
239
240

        master_sleep_orig(sec)
241
242
      end
    end
243
244
245

  end

246
  Discourse.redis.close
247
248
249
250
251

  # Throttle the master from forking too quickly by sleeping.  Due
  # to the implementation of standard Unix signal handlers, this
  # helps (but does not completely) prevent identical, repeated signals
  # from being lost when the receiving process is busy.
252
  sleep 1 if !Rails.env.development?
253
254
255
256
end

after_fork do |server, worker|
  DiscourseEvent.trigger(:web_fork_started)
257
  Discourse.after_fork
258
end