unicorn_wrangler 0.3.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 39f9ac2e8cbfa6df6d0028f7290ce66aa63f04d0
4
- data.tar.gz: 51c10c3a567dae4aaa5fdb585946b1e35232b64d
2
+ SHA256:
3
+ metadata.gz: f027def0eaa75e21daf9efc322a1be988248b5cd117d12eb2ddfadc10494e4d7
4
+ data.tar.gz: a765f7d06d90f8ef448ae1ac87eeb0fc01dddac587dce8d1c69f00acbf6c0164
5
5
  SHA512:
6
- metadata.gz: 88cb0d183f32cbf7e15d0da3a070e90106dc2858d2224f349224225d8bd645aa36631b4c2516e239c35cef97b9a6f6e46b7b020207b02e173d66393d71f41eae
7
- data.tar.gz: 79c384c059e73b602d7d2e6d74387c1ea495098279f6284819072596a7e2db84e2ca700ed66a9b8b171ee32de1164f766f828cb9fa99a8e98ead96e4f0cf0fe2
6
+ metadata.gz: 91787cc20b2bafa1334af58dc5bc29985c3f7d165451cfd36fb45e5d63069ce36525f4d63f2a27fb09fdc29215aab0dcb62ad8cacf0e1cae067b5a20e4740188
7
+ data.tar.gz: 7b6ca2549873589cd9c837affafcc9a9fc07878476c7554f785b20c30b132a1d9e1f9ad12c24af9c16b27d40dae70cd47a7581f28642ea643e055bf9ff95d2ca
@@ -9,6 +9,7 @@ module UnicornWrangler
9
9
 
10
10
  class << self
11
11
  attr_reader :handlers
12
+ attr_accessor :sending_myself_term
12
13
 
13
14
  # called from unicorn config (usually config/unicorn.rb)
14
15
  # high level interface to keep setup consistent / simple
@@ -17,6 +18,7 @@ module UnicornWrangler
17
18
  kill_after_requests: 10000,
18
19
  gc_after_request_time: 10,
19
20
  kill_on_too_much_memory: {},
21
+ map_term_to_quit: false,
20
22
  logger:,
21
23
  stats: nil # provide a statsd client with your apps namespace to collect stats
22
24
  )
@@ -25,9 +27,40 @@ module UnicornWrangler
25
27
  @handlers << RequestKiller.new(logger, stats, kill_after_requests) if kill_after_requests
26
28
  @handlers << OutOfMemoryKiller.new(logger, stats, kill_on_too_much_memory) if kill_on_too_much_memory
27
29
  @handlers << OutOfBandGC.new(logger, stats, gc_after_request_time) if gc_after_request_time
30
+
31
+ @hooks = {}
32
+ if map_term_to_quit
33
+ # - on heroku & kubernetes all processes get TERM, so we need to trap in master and worker
34
+ # - trapping has to be done in the before_fork since unicorn sets up it's own traps on start
35
+ # - we cannot write to logger inside of a trap, so need to spawn a new Thread
36
+ # - manual test: add a slow route + rails s + curl + pkill -TERM -f 'unicorn master' - request finished?
37
+ @hooks[:before_fork] = -> do
38
+ Signal.trap :TERM do
39
+ Thread.new { logger.info 'master intercepting TERM and sending myself QUIT instead' }
40
+ Process.kill :QUIT, Process.pid
41
+ end
42
+ end
43
+
44
+ @hooks[:after_fork] = ->(*) do
45
+ # Signal.trap returns the trap that unicorn set, which is an exit!(0) and calls that when sending myself term
46
+ previous_trap = Signal.trap :TERM do
47
+ if sending_myself_term
48
+ previous_trap.call
49
+ else
50
+ Thread.new { logger.info 'worker intercepting TERM and doing nothing. Wait for master to send QUIT' }
51
+ end
52
+ end
53
+ end
54
+ end
55
+
28
56
  Unicorn::HttpServer.prepend UnicornExtension
29
57
  end
30
58
 
59
+ def kill_worker
60
+ self.sending_myself_term = true # no need to clean up since we are dead after
61
+ Process.kill(:TERM, Process.pid)
62
+ end
63
+
31
64
  # called from the unicorn server after each request
32
65
  def perform_request
33
66
  returned = nil
@@ -39,12 +72,37 @@ module UnicornWrangler
39
72
  ensure
40
73
  @handlers.each { |handler| handler.call(@requests, @request_time) }
41
74
  end
75
+
76
+ def perform_hook(name)
77
+ if hook = @hooks[name]
78
+ hook.call
79
+ end
80
+ end
42
81
  end
43
82
 
44
83
  module UnicornExtension
84
+ # call our hook and the users hook since only a single hook can be configured at a time
85
+ # we need to call super so the @<hook> variables get set and unset properly in after_fork to not leak memory
86
+ [:after_fork, :before_fork].each do |hook|
87
+ define_method("#{hook}=") do |value|
88
+ super(->(*args) do
89
+ UnicornWrangler.perform_hook(hook)
90
+ value.call(*args)
91
+ end)
92
+ end
93
+ end
94
+
45
95
  def process_client(*)
46
96
  UnicornWrangler.perform_request { super }
47
97
  end
98
+
99
+ # run GC after we finished loading out app so forks inherit a clean GC environment
100
+ def build_app!
101
+ super
102
+ ensure
103
+ GC.start
104
+ GC.disable
105
+ end
48
106
  end
49
107
 
50
108
  class Killer
@@ -67,9 +125,9 @@ module UnicornWrangler
67
125
  @stats.histogram("#{STATS_NAMESPACE}.kill.total_request_time", request_time)
68
126
  end
69
127
 
70
- report_status "Killing", reason, memory, requests, request_time
128
+ report_status "Killing", reason, memory, requests, request_time, :warn
71
129
 
72
- Process.kill(:TERM, Process.pid)
130
+ UnicornWrangler.kill_worker
73
131
  end
74
132
 
75
133
  # expensive, do not run on every request
@@ -77,8 +135,8 @@ module UnicornWrangler
77
135
  `ps -o rss= -p #{Process.pid}`.to_i / 1024
78
136
  end
79
137
 
80
- def report_status(status, reason, memory, requests, request_time)
81
- @logger.info "#{status} unicorn worker ##{Process.pid} for #{reason}. Requests: #{requests}, Time: #{request_time}, Memory: #{memory}MB"
138
+ def report_status(status, reason, memory, requests, request_time, log_level = :debug)
139
+ @logger.send log_level, "#{status} unicorn worker ##{Process.pid} for #{reason}. Requests: #{requests}, Time: #{request_time}, Memory: #{memory}MB"
82
140
  end
83
141
  end
84
142
 
@@ -124,7 +182,6 @@ module UnicornWrangler
124
182
  @logger = logger
125
183
  @stats = stats
126
184
  @max_request_time = max_request_time
127
- GC.disable
128
185
  @logger.info "Garbage collecting after #{@max_request_time}s of request processing time"
129
186
  @gc_ran_at = 0
130
187
  end
@@ -145,7 +202,7 @@ module UnicornWrangler
145
202
  @stats.increment("#{STATS_NAMESPACE}.oobgc.runs")
146
203
  @stats.timing("#{STATS_NAMESPACE}.oobgc.time", time)
147
204
  end
148
- @logger.info "Garbage collecting: took #{time}ms"
205
+ @logger.debug "Garbage collecting: took #{time}ms"
149
206
  true
150
207
  end
151
208
  end
@@ -1,3 +1,3 @@
1
1
  module UnicornWrangler
2
- VERSION = "0.3.0"
2
+ VERSION = "0.5.2"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unicorn_wrangler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Grosser
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-19 00:00:00.000000000 Z
11
+ date: 2020-09-22 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: michael@grosser.it
@@ -38,8 +38,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
38
38
  - !ruby/object:Gem::Version
39
39
  version: '0'
40
40
  requirements: []
41
- rubyforge_project:
42
- rubygems_version: 2.5.1
41
+ rubygems_version: 3.1.4
43
42
  signing_key:
44
43
  specification_version: 4
45
44
  summary: 'Unicorn: out of band GC / restart on max memory bloat / restart after X