rsense-server 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/Gemfile +14 -0
  4. data/Guardfile +5 -0
  5. data/LICENSE.txt +1 -0
  6. data/README.md +51 -0
  7. data/Rakefile +9 -0
  8. data/bin/_rsense.rb +115 -0
  9. data/config/puma.rb +2 -0
  10. data/lib/rsense/server/code.rb +38 -0
  11. data/lib/rsense/server/command/completion_result.rb +11 -0
  12. data/lib/rsense/server/command/special_meth.rb +18 -0
  13. data/lib/rsense/server/command/type_inference_method.rb +24 -0
  14. data/lib/rsense/server/command.rb +239 -0
  15. data/lib/rsense/server/config.rb +70 -0
  16. data/lib/rsense/server/gem_path.rb +18 -0
  17. data/lib/rsense/server/listeners/find_definition_event_listener.rb +91 -0
  18. data/lib/rsense/server/listeners/where_event_listener.rb +39 -0
  19. data/lib/rsense/server/load_path.rb +62 -0
  20. data/lib/rsense/server/options.rb +85 -0
  21. data/lib/rsense/server/parser.rb +17 -0
  22. data/lib/rsense/server/path_info.rb +17 -0
  23. data/lib/rsense/server/project.rb +24 -0
  24. data/lib/rsense/server/version.rb +5 -0
  25. data/lib/rsense/server.rb +18 -0
  26. data/rsense-server.gemspec +35 -0
  27. data/spec/fixtures/config_fixture/.rsense +4 -0
  28. data/spec/fixtures/deeply/nested/thing.rb +0 -0
  29. data/spec/fixtures/find_def_sample.json +10 -0
  30. data/spec/fixtures/sample.json +10 -0
  31. data/spec/fixtures/test_gem/.gitignore +22 -0
  32. data/spec/fixtures/test_gem/Gemfile +4 -0
  33. data/spec/fixtures/test_gem/LICENSE.txt +22 -0
  34. data/spec/fixtures/test_gem/README.md +29 -0
  35. data/spec/fixtures/test_gem/Rakefile +2 -0
  36. data/spec/fixtures/test_gem/lib/sample/version.rb +3 -0
  37. data/spec/fixtures/test_gem/lib/sample.rb +16 -0
  38. data/spec/fixtures/test_gem/sample.gemspec +23 -0
  39. data/spec/fixtures/test_gem/test.json +10 -0
  40. data/spec/rsense/server/code_spec.rb +44 -0
  41. data/spec/rsense/server/command/special_meth_spec.rb +23 -0
  42. data/spec/rsense/server/command_spec.rb +108 -0
  43. data/spec/rsense/server/config_spec.rb +27 -0
  44. data/spec/rsense/server/gem_path_spec.rb +16 -0
  45. data/spec/rsense/server/load_path_spec.rb +63 -0
  46. data/spec/rsense/server/options_spec.rb +33 -0
  47. data/spec/rsense/server/path_info_spec.rb +11 -0
  48. data/spec/rsense/server/project_spec.rb +18 -0
  49. data/spec/rsense/server_spec.rb +7 -0
  50. data/spec/spec_helper.rb +16 -0
  51. data/vendor/gems/puma-2.8.2-java/COPYING +55 -0
  52. data/vendor/gems/puma-2.8.2-java/DEPLOYMENT.md +92 -0
  53. data/vendor/gems/puma-2.8.2-java/Gemfile +17 -0
  54. data/vendor/gems/puma-2.8.2-java/History.txt +532 -0
  55. data/vendor/gems/puma-2.8.2-java/LICENSE +26 -0
  56. data/vendor/gems/puma-2.8.2-java/Manifest.txt +68 -0
  57. data/vendor/gems/puma-2.8.2-java/README.md +251 -0
  58. data/vendor/gems/puma-2.8.2-java/Rakefile +158 -0
  59. data/vendor/gems/puma-2.8.2-java/bin/puma +10 -0
  60. data/vendor/gems/puma-2.8.2-java/bin/puma-wild +17 -0
  61. data/vendor/gems/puma-2.8.2-java/bin/pumactl +12 -0
  62. data/vendor/gems/puma-2.8.2-java/docs/config.md +0 -0
  63. data/vendor/gems/puma-2.8.2-java/docs/nginx.md +80 -0
  64. data/vendor/gems/puma-2.8.2-java/docs/signals.md +42 -0
  65. data/vendor/gems/puma-2.8.2-java/ext/puma_http11/PumaHttp11Service.java +17 -0
  66. data/vendor/gems/puma-2.8.2-java/ext/puma_http11/ext_help.h +15 -0
  67. data/vendor/gems/puma-2.8.2-java/ext/puma_http11/extconf.rb +8 -0
  68. data/vendor/gems/puma-2.8.2-java/ext/puma_http11/http11_parser.c +1225 -0
  69. data/vendor/gems/puma-2.8.2-java/ext/puma_http11/http11_parser.h +64 -0
  70. data/vendor/gems/puma-2.8.2-java/ext/puma_http11/http11_parser.java.rl +161 -0
  71. data/vendor/gems/puma-2.8.2-java/ext/puma_http11/http11_parser.rl +146 -0
  72. data/vendor/gems/puma-2.8.2-java/ext/puma_http11/http11_parser_common.rl +54 -0
  73. data/vendor/gems/puma-2.8.2-java/ext/puma_http11/io_buffer.c +155 -0
  74. data/vendor/gems/puma-2.8.2-java/ext/puma_http11/mini_ssl.c +195 -0
  75. data/vendor/gems/puma-2.8.2-java/ext/puma_http11/org/jruby/puma/Http11.java +225 -0
  76. data/vendor/gems/puma-2.8.2-java/ext/puma_http11/org/jruby/puma/Http11Parser.java +488 -0
  77. data/vendor/gems/puma-2.8.2-java/ext/puma_http11/org/jruby/puma/MiniSSL.java +289 -0
  78. data/vendor/gems/puma-2.8.2-java/ext/puma_http11/puma_http11.c +491 -0
  79. data/vendor/gems/puma-2.8.2-java/lib/puma/accept_nonblock.rb +23 -0
  80. data/vendor/gems/puma-2.8.2-java/lib/puma/app/status.rb +59 -0
  81. data/vendor/gems/puma-2.8.2-java/lib/puma/binder.rb +298 -0
  82. data/vendor/gems/puma-2.8.2-java/lib/puma/capistrano.rb +86 -0
  83. data/vendor/gems/puma-2.8.2-java/lib/puma/cli.rb +587 -0
  84. data/vendor/gems/puma-2.8.2-java/lib/puma/client.rb +289 -0
  85. data/vendor/gems/puma-2.8.2-java/lib/puma/cluster.rb +389 -0
  86. data/vendor/gems/puma-2.8.2-java/lib/puma/compat.rb +18 -0
  87. data/vendor/gems/puma-2.8.2-java/lib/puma/configuration.rb +377 -0
  88. data/vendor/gems/puma-2.8.2-java/lib/puma/const.rb +165 -0
  89. data/vendor/gems/puma-2.8.2-java/lib/puma/control_cli.rb +251 -0
  90. data/vendor/gems/puma-2.8.2-java/lib/puma/daemon_ext.rb +25 -0
  91. data/vendor/gems/puma-2.8.2-java/lib/puma/delegation.rb +11 -0
  92. data/vendor/gems/puma-2.8.2-java/lib/puma/detect.rb +4 -0
  93. data/vendor/gems/puma-2.8.2-java/lib/puma/events.rb +130 -0
  94. data/vendor/gems/puma-2.8.2-java/lib/puma/io_buffer.rb +7 -0
  95. data/vendor/gems/puma-2.8.2-java/lib/puma/java_io_buffer.rb +45 -0
  96. data/vendor/gems/puma-2.8.2-java/lib/puma/jruby_restart.rb +83 -0
  97. data/vendor/gems/puma-2.8.2-java/lib/puma/minissl.rb +148 -0
  98. data/vendor/gems/puma-2.8.2-java/lib/puma/null_io.rb +34 -0
  99. data/vendor/gems/puma-2.8.2-java/lib/puma/puma_http11.jar +0 -0
  100. data/vendor/gems/puma-2.8.2-java/lib/puma/rack_default.rb +7 -0
  101. data/vendor/gems/puma-2.8.2-java/lib/puma/rack_patch.rb +45 -0
  102. data/vendor/gems/puma-2.8.2-java/lib/puma/reactor.rb +183 -0
  103. data/vendor/gems/puma-2.8.2-java/lib/puma/runner.rb +146 -0
  104. data/vendor/gems/puma-2.8.2-java/lib/puma/server.rb +801 -0
  105. data/vendor/gems/puma-2.8.2-java/lib/puma/single.rb +102 -0
  106. data/vendor/gems/puma-2.8.2-java/lib/puma/tcp_logger.rb +32 -0
  107. data/vendor/gems/puma-2.8.2-java/lib/puma/thread_pool.rb +185 -0
  108. data/vendor/gems/puma-2.8.2-java/lib/puma/util.rb +9 -0
  109. data/vendor/gems/puma-2.8.2-java/lib/puma.rb +14 -0
  110. data/vendor/gems/puma-2.8.2-java/lib/rack/handler/puma.rb +66 -0
  111. data/vendor/gems/puma-2.8.2-java/puma.gemspec +55 -0
  112. data/vendor/gems/puma-2.8.2-java/test/test_app_status.rb +92 -0
  113. data/vendor/gems/puma-2.8.2-java/test/test_cli.rb +173 -0
  114. data/vendor/gems/puma-2.8.2-java/test/test_config.rb +26 -0
  115. data/vendor/gems/puma-2.8.2-java/test/test_http10.rb +27 -0
  116. data/vendor/gems/puma-2.8.2-java/test/test_http11.rb +144 -0
  117. data/vendor/gems/puma-2.8.2-java/test/test_integration.rb +165 -0
  118. data/vendor/gems/puma-2.8.2-java/test/test_iobuffer.rb +38 -0
  119. data/vendor/gems/puma-2.8.2-java/test/test_minissl.rb +25 -0
  120. data/vendor/gems/puma-2.8.2-java/test/test_null_io.rb +31 -0
  121. data/vendor/gems/puma-2.8.2-java/test/test_persistent.rb +238 -0
  122. data/vendor/gems/puma-2.8.2-java/test/test_puma_server.rb +323 -0
  123. data/vendor/gems/puma-2.8.2-java/test/test_rack_handler.rb +10 -0
  124. data/vendor/gems/puma-2.8.2-java/test/test_rack_server.rb +141 -0
  125. data/vendor/gems/puma-2.8.2-java/test/test_tcp_rack.rb +42 -0
  126. data/vendor/gems/puma-2.8.2-java/test/test_thread_pool.rb +156 -0
  127. data/vendor/gems/puma-2.8.2-java/test/test_unix_socket.rb +39 -0
  128. data/vendor/gems/puma-2.8.2-java/test/test_ws.rb +89 -0
  129. data/vendor/gems/puma-2.8.2-java/tools/jungle/README.md +9 -0
  130. data/vendor/gems/puma-2.8.2-java/tools/jungle/init.d/README.md +54 -0
  131. data/vendor/gems/puma-2.8.2-java/tools/jungle/init.d/puma +332 -0
  132. data/vendor/gems/puma-2.8.2-java/tools/jungle/init.d/run-puma +3 -0
  133. data/vendor/gems/puma-2.8.2-java/tools/jungle/upstart/README.md +61 -0
  134. data/vendor/gems/puma-2.8.2-java/tools/jungle/upstart/puma-manager.conf +31 -0
  135. data/vendor/gems/puma-2.8.2-java/tools/jungle/upstart/puma.conf +63 -0
  136. data/vendor/gems/puma-2.8.2-java/tools/trickletest.rb +45 -0
  137. metadata +389 -0
@@ -0,0 +1,289 @@
1
+ class IO
2
+ # We need to use this for a jruby work around on both 1.8 and 1.9.
3
+ # So this either creates the constant (on 1.8), or harmlessly
4
+ # reopens it (on 1.9).
5
+ module WaitReadable
6
+ end
7
+ end
8
+
9
+ require 'puma/detect'
10
+
11
+ if Puma::IS_JRUBY
12
+ # We have to work around some OpenSSL buffer/io-readiness bugs
13
+ # so we pull it in regardless of if the user is binding
14
+ # to an SSL socket
15
+ require 'openssl'
16
+ end
17
+
18
+ module Puma
19
+
20
+ class ConnectionError < RuntimeError; end
21
+
22
+ class Client
23
+ include Puma::Const
24
+
25
+ def initialize(io, env=nil)
26
+ @io = io
27
+ @to_io = io.to_io
28
+ @proto_env = env
29
+ if !env
30
+ @env = nil
31
+ else
32
+ @env = env.dup
33
+ end
34
+
35
+ @parser = HttpParser.new
36
+ @parsed_bytes = 0
37
+ @read_header = true
38
+ @ready = false
39
+
40
+ @body = nil
41
+ @buffer = nil
42
+
43
+ @timeout_at = nil
44
+
45
+ @requests_served = 0
46
+ @hijacked = false
47
+ end
48
+
49
+ attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked
50
+
51
+ def inspect
52
+ "#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
53
+ end
54
+
55
+ # For the hijack protocol (allows us to just put the Client object
56
+ # into the env)
57
+ def call
58
+ @hijacked = true
59
+ env[HIJACK_IO] ||= @io
60
+ end
61
+
62
+ def in_data_phase
63
+ !@read_header
64
+ end
65
+
66
+ def set_timeout(val)
67
+ @timeout_at = Time.now + val
68
+ end
69
+
70
+ def reset(fast_check=true)
71
+ @parser.reset
72
+ @read_header = true
73
+ @env = @proto_env.dup
74
+ @body = nil
75
+ @parsed_bytes = 0
76
+ @ready = false
77
+
78
+ if @buffer
79
+ @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
80
+
81
+ if @parser.finished?
82
+ return setup_body
83
+ elsif @parsed_bytes >= MAX_HEADER
84
+ raise HttpParserError,
85
+ "HEADER is longer than allowed, aborting client early."
86
+ end
87
+
88
+ return false
89
+ elsif fast_check &&
90
+ IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
91
+ return try_to_finish
92
+ end
93
+ end
94
+
95
+ def close
96
+ begin
97
+ @io.close
98
+ rescue IOError
99
+ end
100
+ end
101
+
102
+ # The object used for a request with no body. All requests with
103
+ # no body share this one object since it has no state.
104
+ EmptyBody = NullIO.new
105
+
106
+ def setup_body
107
+ @in_data_phase = true
108
+ body = @parser.body
109
+ cl = @env[CONTENT_LENGTH]
110
+
111
+ unless cl
112
+ @buffer = body.empty? ? nil : body
113
+ @body = EmptyBody
114
+ @requests_served += 1
115
+ @ready = true
116
+ return true
117
+ end
118
+
119
+ remain = cl.to_i - body.bytesize
120
+
121
+ if remain <= 0
122
+ @body = StringIO.new(body)
123
+ @buffer = nil
124
+ @requests_served += 1
125
+ @ready = true
126
+ return true
127
+ end
128
+
129
+ if remain > MAX_BODY
130
+ @body = Tempfile.new(Const::PUMA_TMP_BASE)
131
+ @body.binmode
132
+ else
133
+ # The body[0,0] trick is to get an empty string in the same
134
+ # encoding as body.
135
+ @body = StringIO.new body[0,0]
136
+ end
137
+
138
+ @body.write body
139
+
140
+ @body_remain = remain
141
+
142
+ @read_header = false
143
+
144
+ return false
145
+ end
146
+
147
+ def try_to_finish
148
+ return read_body unless @read_header
149
+
150
+ begin
151
+ data = @io.read_nonblock(CHUNK_SIZE)
152
+ rescue Errno::EAGAIN
153
+ return false
154
+ rescue SystemCallError, IOError
155
+ raise ConnectionError, "Connection error detected during read"
156
+ end
157
+
158
+ if @buffer
159
+ @buffer << data
160
+ else
161
+ @buffer = data
162
+ end
163
+
164
+ @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
165
+
166
+ if @parser.finished?
167
+ return setup_body
168
+ elsif @parsed_bytes >= MAX_HEADER
169
+ raise HttpParserError,
170
+ "HEADER is longer than allowed, aborting client early."
171
+ end
172
+
173
+ false
174
+ end
175
+
176
+ if IS_JRUBY
177
+ def jruby_start_try_to_finish
178
+ return read_body unless @read_header
179
+
180
+ begin
181
+ data = @io.sysread_nonblock(CHUNK_SIZE)
182
+ rescue OpenSSL::SSL::SSLError => e
183
+ return false if e.kind_of? IO::WaitReadable
184
+ raise e
185
+ end
186
+
187
+ if @buffer
188
+ @buffer << data
189
+ else
190
+ @buffer = data
191
+ end
192
+
193
+ @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
194
+
195
+ if @parser.finished?
196
+ return setup_body
197
+ elsif @parsed_bytes >= MAX_HEADER
198
+ raise HttpParserError,
199
+ "HEADER is longer than allowed, aborting client early."
200
+ end
201
+
202
+ false
203
+ end
204
+
205
+ def eagerly_finish
206
+ return true if @ready
207
+
208
+ if @io.kind_of? OpenSSL::SSL::SSLSocket
209
+ return true if jruby_start_try_to_finish
210
+ end
211
+
212
+ return false unless IO.select([@to_io], nil, nil, 0)
213
+ try_to_finish
214
+ end
215
+
216
+ else
217
+
218
+ def eagerly_finish
219
+ return true if @ready
220
+ return false unless IO.select([@to_io], nil, nil, 0)
221
+ try_to_finish
222
+ end
223
+ end # IS_JRUBY
224
+
225
+ def read_body
226
+ # Read an odd sized chunk so we can read even sized ones
227
+ # after this
228
+ remain = @body_remain
229
+
230
+ if remain > CHUNK_SIZE
231
+ want = CHUNK_SIZE
232
+ else
233
+ want = remain
234
+ end
235
+
236
+ begin
237
+ chunk = @io.read_nonblock(want)
238
+ rescue Errno::EAGAIN
239
+ return false
240
+ rescue SystemCallError, IOError
241
+ raise ConnectionError, "Connection error detected during read"
242
+ end
243
+
244
+ # No chunk means a closed socket
245
+ unless chunk
246
+ @body.close
247
+ @buffer = nil
248
+ @requests_served += 1
249
+ @ready = true
250
+ raise EOFError
251
+ end
252
+
253
+ remain -= @body.write(chunk)
254
+
255
+ if remain <= 0
256
+ @body.rewind
257
+ @buffer = nil
258
+ @requests_served += 1
259
+ @ready = true
260
+ return true
261
+ end
262
+
263
+ @body_remain = remain
264
+
265
+ false
266
+ end
267
+
268
+ def write_400
269
+ begin
270
+ @io << ERROR_400_RESPONSE
271
+ rescue StandardError
272
+ end
273
+ end
274
+
275
+ def write_408
276
+ begin
277
+ @io << ERROR_408_RESPONSE
278
+ rescue StandardError
279
+ end
280
+ end
281
+
282
+ def write_500
283
+ begin
284
+ @io << ERROR_500_RESPONSE
285
+ rescue StandardError
286
+ end
287
+ end
288
+ end
289
+ end
@@ -0,0 +1,389 @@
1
+ require 'puma/runner'
2
+
3
+ module Puma
4
+ class Cluster < Runner
5
+ def initialize(cli)
6
+ super cli
7
+
8
+ @phase = 0
9
+ @workers = []
10
+ @next_check = nil
11
+
12
+ @phased_state = :idle
13
+ @phased_restart = false
14
+ end
15
+
16
+ def stop_workers
17
+ log "- Gracefully shutting down workers..."
18
+ @workers.each { |x| x.term }
19
+
20
+ begin
21
+ Process.waitall
22
+ rescue Interrupt
23
+ log "! Cancelled waiting for workers"
24
+ end
25
+ end
26
+
27
+ def start_phased_restart
28
+ @phase += 1
29
+ log "- Starting phased worker restart, phase: #{@phase}"
30
+
31
+ # Be sure to change the directory again before loading
32
+ # the app. This way we can pick up new code.
33
+ if dir = @options[:worker_directory]
34
+ log "+ Changing to #{dir}"
35
+ Dir.chdir dir
36
+ end
37
+ end
38
+
39
+ class Worker
40
+ def initialize(idx, pid, phase)
41
+ @index = idx
42
+ @pid = pid
43
+ @phase = phase
44
+ @stage = :started
45
+ @signal = "TERM"
46
+ @last_checkin = Time.now
47
+ end
48
+
49
+ attr_reader :index, :pid, :phase, :signal, :last_checkin
50
+
51
+ def booted?
52
+ @stage == :booted
53
+ end
54
+
55
+ def boot!
56
+ @last_checkin = Time.now
57
+ @stage = :booted
58
+ end
59
+
60
+ def ping!
61
+ @last_checkin = Time.now
62
+ end
63
+
64
+ def ping_timeout?(which)
65
+ Time.now - @last_checkin > which
66
+ end
67
+
68
+ def term
69
+ begin
70
+ if @first_term_sent && (Time.new - @first_term_sent) > 30
71
+ @signal = "KILL"
72
+ else
73
+ @first_term_sent ||= Time.new
74
+ end
75
+
76
+ Process.kill @signal, @pid
77
+ rescue Errno::ESRCH
78
+ end
79
+ end
80
+
81
+ def kill
82
+ Process.kill "KILL", @pid
83
+ rescue Errno::ESRCH
84
+ end
85
+ end
86
+
87
+ def spawn_workers
88
+ diff = @options[:workers] - @workers.size
89
+
90
+ master = Process.pid
91
+
92
+ diff.times do
93
+ idx = next_worker_index
94
+
95
+ pid = fork { worker(idx, master) }
96
+ @cli.debug "Spawned worker: #{pid}"
97
+ @workers << Worker.new(idx, pid, @phase)
98
+ @options[:after_worker_boot].each { |h| h.call }
99
+ end
100
+
101
+ if diff > 0
102
+ @phased_state = :idle
103
+ end
104
+ end
105
+
106
+ def next_worker_index
107
+ all_positions = 0...@options[:workers]
108
+ occupied_positions = @workers.map { |w| w.index }
109
+ available_positions = all_positions.to_a - occupied_positions
110
+ available_positions.first
111
+ end
112
+
113
+ def all_workers_booted?
114
+ @workers.count { |w| !w.booted? } == 0
115
+ end
116
+
117
+ def check_workers
118
+ return if @next_check && @next_check >= Time.now
119
+
120
+ @next_check = Time.now + 5
121
+
122
+ any = false
123
+
124
+ @workers.each do |w|
125
+ if w.ping_timeout?(@options[:worker_timeout])
126
+ log "! Terminating timed out worker: #{w.pid}"
127
+ w.kill
128
+ any = true
129
+ end
130
+ end
131
+
132
+ # If we killed any timed out workers, try to catch them
133
+ # during this loop by giving the kernel time to kill them.
134
+ sleep 1 if any
135
+
136
+ while @workers.any?
137
+ pid = Process.waitpid(-1, Process::WNOHANG)
138
+ break unless pid
139
+
140
+ @workers.delete_if { |w| w.pid == pid }
141
+ end
142
+
143
+ spawn_workers
144
+
145
+ if all_workers_booted?
146
+ # If we're running at proper capacity, check to see if
147
+ # we need to phase any workers out (which will restart
148
+ # in the right phase).
149
+ #
150
+ w = @workers.find { |x| x.phase != @phase }
151
+
152
+ if w
153
+ if @phased_state == :idle
154
+ @phased_state = :waiting
155
+ log "- Stopping #{w.pid} for phased upgrade..."
156
+ end
157
+
158
+ w.term
159
+ log "- #{w.signal} sent to #{w.pid}..."
160
+ end
161
+ end
162
+ end
163
+
164
+ def wakeup!
165
+ begin
166
+ @wakeup.write "!" unless @wakeup.closed?
167
+ rescue SystemCallError, IOError
168
+ end
169
+ end
170
+
171
+ def worker(index, master)
172
+ $0 = "puma: cluster worker #{index}: #{master}"
173
+ Signal.trap "SIGINT", "IGNORE"
174
+
175
+ @master_read.close
176
+ @suicide_pipe.close
177
+
178
+ Thread.new do
179
+ IO.select [@check_pipe]
180
+ log "! Detected parent died, dying"
181
+ exit! 1
182
+ end
183
+
184
+ # If we're not running under a Bundler context, then
185
+ # report the info about the context we will be using
186
+ if !ENV['BUNDLER_GEMFILE'] and File.exist?("Gemfile")
187
+ log "+ Gemfile in context: #{File.expand_path("Gemfile")}"
188
+ end
189
+
190
+ # Invoke any worker boot hooks so they can get
191
+ # things in shape before booting the app.
192
+ hooks = @options[:before_worker_boot]
193
+ hooks.each { |h| h.call(index) }
194
+
195
+ server = start_server
196
+
197
+ Signal.trap "SIGTERM" do
198
+ server.stop
199
+ end
200
+
201
+ begin
202
+ @worker_write << "b#{Process.pid}\n"
203
+ rescue SystemCallError, IOError
204
+ STDERR.puts "Master seems to have exitted, exitting."
205
+ return
206
+ end
207
+
208
+ Thread.new(@worker_write) do |io|
209
+ payload = "p#{Process.pid}\n"
210
+
211
+ while true
212
+ sleep 5
213
+ io << payload
214
+ end
215
+ end
216
+
217
+ server.run.join
218
+
219
+ ensure
220
+ @worker_write.close
221
+ end
222
+
223
+ def restart
224
+ @restart = true
225
+ stop
226
+ end
227
+
228
+ def phased_restart
229
+ return false if @options[:preload_app]
230
+
231
+ @phased_restart = true
232
+ wakeup!
233
+
234
+ true
235
+ end
236
+
237
+ def stop
238
+ @status = :stop
239
+ wakeup!
240
+ end
241
+
242
+ def stop_blocked
243
+ @status = :stop if @status == :run
244
+ wakeup!
245
+ @control.stop(true) if @control
246
+ Process.waitall
247
+ end
248
+
249
+ def halt
250
+ @status = :halt
251
+ wakeup!
252
+ end
253
+
254
+ def stats
255
+ %Q!{ "workers": #{@workers.size}, "phase": #{@phase}, "booted_workers": #{@workers.count{|w| w.booted?}} }!
256
+ end
257
+
258
+ def preload?
259
+ @options[:preload_app]
260
+ end
261
+
262
+ def run
263
+ @status = :run
264
+
265
+ output_header "cluster"
266
+
267
+ log "* Process workers: #{@options[:workers]}"
268
+
269
+ if preload?
270
+ log "* Preloading application"
271
+ load_and_bind
272
+ else
273
+ log "* Phased restart available"
274
+
275
+ unless @cli.config.app_configured?
276
+ error "No application configured, nothing to run"
277
+ exit 1
278
+ end
279
+
280
+ @cli.binder.parse @options[:binds], self
281
+ end
282
+
283
+ read, @wakeup = Puma::Util.pipe
284
+
285
+ Signal.trap "SIGCHLD" do
286
+ wakeup!
287
+ end
288
+
289
+ Signal.trap "TTIN" do
290
+ @options[:workers] += 1
291
+ wakeup!
292
+ end
293
+
294
+ Signal.trap "TTOU" do
295
+ @options[:workers] -= 1 if @options[:workers] >= 2
296
+ @workers.last.term
297
+ wakeup!
298
+ end
299
+
300
+ master_pid = Process.pid
301
+
302
+ Signal.trap "SIGTERM" do
303
+ # The worker installs their own SIGTERM when booted.
304
+ # Until then, this is run by the worker and the worker
305
+ # should just exit if they get it.
306
+ if Process.pid != master_pid
307
+ log "Early termination of worker"
308
+ exit! 0
309
+ else
310
+ stop
311
+ end
312
+ end
313
+
314
+ # Used by the workers to detect if the master process dies.
315
+ # If select says that @check_pipe is ready, it's because the
316
+ # master has exited and @suicide_pipe has been automatically
317
+ # closed.
318
+ #
319
+ @check_pipe, @suicide_pipe = Puma::Util.pipe
320
+
321
+ if daemon?
322
+ log "* Daemonizing..."
323
+ Process.daemon(true)
324
+ else
325
+ log "Use Ctrl-C to stop"
326
+ end
327
+
328
+ redirect_io
329
+
330
+ start_control
331
+
332
+ @cli.write_state
333
+
334
+ @master_read, @worker_write = read, @wakeup
335
+ spawn_workers
336
+
337
+ Signal.trap "SIGINT" do
338
+ stop
339
+ end
340
+
341
+ @cli.events.fire_on_booted!
342
+
343
+ begin
344
+ while @status == :run
345
+ begin
346
+ res = IO.select([read], nil, nil, 5)
347
+
348
+ if res
349
+ req = read.read_nonblock(1)
350
+
351
+ next if !req || req == "!"
352
+
353
+ pid = read.gets.to_i
354
+
355
+ if w = @workers.find { |x| x.pid == pid }
356
+ case req
357
+ when "b"
358
+ w.boot!
359
+ log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}"
360
+ when "p"
361
+ w.ping!
362
+ end
363
+ else
364
+ log "! Out-of-sync worker list, no #{pid} worker"
365
+ end
366
+ end
367
+
368
+ if @phased_restart
369
+ start_phased_restart
370
+ @phased_restart = false
371
+ end
372
+
373
+ check_workers
374
+
375
+ rescue Interrupt
376
+ @status = :stop
377
+ end
378
+ end
379
+
380
+ stop_workers unless @status == :halt
381
+ ensure
382
+ @check_pipe.close
383
+ @suicide_pipe.close
384
+ read.close
385
+ @wakeup.close
386
+ end
387
+ end
388
+ end
389
+ end
@@ -0,0 +1,18 @@
1
+ # Provides code to work properly on 1.8 and 1.9
2
+
3
+ class String
4
+ unless method_defined? :bytesize
5
+ alias_method :bytesize, :size
6
+ end
7
+
8
+ unless method_defined? :byteslice
9
+ if RUBY_VERSION < '1.9'
10
+ alias_method :byteslice, :[]
11
+ else
12
+ def byteslice(*arg)
13
+ enc = self.encoding
14
+ self.dup.force_encoding(Encoding::ASCII_8BIT).slice(*arg).force_encoding(enc)
15
+ end
16
+ end
17
+ end
18
+ end