rsense-server 0.5.0

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.
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