puma 3.7.1 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/History.md +229 -1
- data/README.md +179 -212
- data/docs/architecture.md +37 -0
- data/{DEPLOYMENT.md → docs/deployment.md} +24 -4
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/plugins.md +28 -0
- data/docs/restart.md +41 -0
- data/docs/signals.md +56 -3
- data/docs/systemd.md +130 -37
- data/ext/puma_http11/PumaHttp11Service.java +2 -0
- data/ext/puma_http11/extconf.rb +8 -0
- data/ext/puma_http11/http11_parser.c +84 -84
- data/ext/puma_http11/http11_parser.rl +9 -9
- data/ext/puma_http11/mini_ssl.c +105 -9
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +13 -16
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +30 -6
- data/lib/puma.rb +10 -0
- data/lib/puma/accept_nonblock.rb +2 -0
- data/lib/puma/app/status.rb +13 -0
- data/lib/puma/binder.rb +33 -18
- data/lib/puma/cli.rb +48 -33
- data/lib/puma/client.rb +94 -22
- data/lib/puma/cluster.rb +69 -21
- data/lib/puma/commonlogger.rb +2 -0
- data/lib/puma/configuration.rb +134 -136
- data/lib/puma/const.rb +16 -2
- data/lib/puma/control_cli.rb +31 -18
- data/lib/puma/convenient.rb +5 -3
- data/lib/puma/daemon_ext.rb +2 -0
- data/lib/puma/delegation.rb +2 -0
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +349 -113
- data/lib/puma/events.rb +8 -4
- data/lib/puma/io_buffer.rb +3 -6
- data/lib/puma/jruby_restart.rb +2 -1
- data/lib/puma/launcher.rb +60 -36
- data/lib/puma/minissl.rb +85 -28
- data/lib/puma/null_io.rb +2 -0
- data/lib/puma/plugin.rb +2 -0
- data/lib/puma/plugin/tmp_restart.rb +3 -2
- data/lib/puma/rack/builder.rb +4 -1
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +218 -30
- data/lib/puma/runner.rb +18 -4
- data/lib/puma/server.rb +149 -56
- data/lib/puma/single.rb +16 -5
- data/lib/puma/state_file.rb +2 -0
- data/lib/puma/tcp_logger.rb +2 -0
- data/lib/puma/thread_pool.rb +59 -6
- data/lib/puma/util.rb +2 -6
- data/lib/rack/handler/puma.rb +58 -19
- data/tools/jungle/README.md +12 -2
- data/tools/jungle/init.d/README.md +2 -0
- data/tools/jungle/init.d/puma +8 -8
- data/tools/jungle/init.d/run-puma +1 -1
- data/tools/jungle/rc.d/README.md +74 -0
- data/tools/jungle/rc.d/puma +61 -0
- data/tools/jungle/rc.d/puma.conf +10 -0
- data/tools/trickletest.rb +1 -1
- metadata +25 -85
- data/.github/issue_template.md +0 -20
- data/Gemfile +0 -12
- data/Manifest.txt +0 -77
- data/Rakefile +0 -158
- data/gemfiles/2.1-Gemfile +0 -12
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
- data/puma.gemspec +0 -52
data/lib/puma/runner.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'puma/server'
|
2
4
|
require 'puma/const'
|
3
5
|
|
4
6
|
module Puma
|
7
|
+
# Generic class that is used by `Puma::Cluster` and `Puma::Single` to
|
8
|
+
# serve requests. This class spawns a new instance of `Puma::Server` via
|
9
|
+
# a call to `start_server`.
|
5
10
|
class Runner
|
6
11
|
def initialize(cli, events)
|
7
12
|
@launcher = cli
|
@@ -9,6 +14,7 @@ module Puma
|
|
9
14
|
@options = cli.options
|
10
15
|
@app = nil
|
11
16
|
@control = nil
|
17
|
+
@started_at = Time.now
|
12
18
|
end
|
13
19
|
|
14
20
|
def daemon?
|
@@ -19,6 +25,10 @@ module Puma
|
|
19
25
|
@options[:environment] == "development"
|
20
26
|
end
|
21
27
|
|
28
|
+
def test?
|
29
|
+
@options[:environment] == "test"
|
30
|
+
end
|
31
|
+
|
22
32
|
def log(str)
|
23
33
|
@events.log str
|
24
34
|
end
|
@@ -46,7 +56,7 @@ module Puma
|
|
46
56
|
app = Puma::App::Status.new @launcher
|
47
57
|
|
48
58
|
if token = @options[:control_auth_token]
|
49
|
-
app.auth_token = token unless token.empty?
|
59
|
+
app.auth_token = token unless token.empty? || token == 'none'
|
50
60
|
end
|
51
61
|
|
52
62
|
control = Puma::Server.new app, @launcher.events
|
@@ -107,7 +117,7 @@ module Puma
|
|
107
117
|
append = @options[:redirect_append]
|
108
118
|
|
109
119
|
if stdout
|
110
|
-
unless Dir.
|
120
|
+
unless Dir.exist?(File.dirname(stdout))
|
111
121
|
raise "Cannot redirect STDOUT to #{stdout}"
|
112
122
|
end
|
113
123
|
|
@@ -117,7 +127,7 @@ module Puma
|
|
117
127
|
end
|
118
128
|
|
119
129
|
if stderr
|
120
|
-
unless Dir.
|
130
|
+
unless Dir.exist?(File.dirname(stderr))
|
121
131
|
raise "Cannot redirect STDERR to #{stderr}"
|
122
132
|
end
|
123
133
|
|
@@ -161,7 +171,11 @@ module Puma
|
|
161
171
|
server.tcp_mode!
|
162
172
|
end
|
163
173
|
|
164
|
-
|
174
|
+
if @options[:early_hints]
|
175
|
+
server.early_hints = true
|
176
|
+
end
|
177
|
+
|
178
|
+
unless development? || test?
|
165
179
|
server.leak_stack_on_error = false
|
166
180
|
end
|
167
181
|
|
data/lib/puma/server.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'stringio'
|
2
4
|
|
3
5
|
require 'puma/thread_pool'
|
4
6
|
require 'puma/const'
|
5
7
|
require 'puma/events'
|
6
8
|
require 'puma/null_io'
|
7
|
-
require 'puma/compat'
|
8
9
|
require 'puma/reactor'
|
9
10
|
require 'puma/client'
|
10
11
|
require 'puma/binder'
|
@@ -14,15 +15,20 @@ require 'puma/util'
|
|
14
15
|
|
15
16
|
require 'puma/puma_http11'
|
16
17
|
|
17
|
-
unless Puma.const_defined? "IOBuffer"
|
18
|
-
require 'puma/io_buffer'
|
19
|
-
end
|
20
|
-
|
21
18
|
require 'socket'
|
22
19
|
|
23
20
|
module Puma
|
24
21
|
|
25
22
|
# The HTTP Server itself. Serves out a single Rack app.
|
23
|
+
#
|
24
|
+
# This class is used by the `Puma::Single` and `Puma::Cluster` classes
|
25
|
+
# to generate one or more `Puma::Server` instances capable of handling requests.
|
26
|
+
# Each Puma process will contain one `Puma::Server` instance.
|
27
|
+
#
|
28
|
+
# The `Puma::Server` instance pulls requests from the socket, adds them to a
|
29
|
+
# `Puma::Reactor` where they get eventually passed to a `Puma::ThreadPool`.
|
30
|
+
#
|
31
|
+
# Each `Puma::Server` will have one reactor and one thread pool.
|
26
32
|
class Server
|
27
33
|
|
28
34
|
include Puma::Const
|
@@ -62,13 +68,12 @@ module Puma
|
|
62
68
|
|
63
69
|
@thread = nil
|
64
70
|
@thread_pool = nil
|
71
|
+
@early_hints = nil
|
65
72
|
|
66
73
|
@persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT)
|
74
|
+
@first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
|
67
75
|
|
68
76
|
@binder = Binder.new(events)
|
69
|
-
@own_binder = true
|
70
|
-
|
71
|
-
@first_data_timeout = FIRST_DATA_TIMEOUT
|
72
77
|
|
73
78
|
@leak_stack_on_error = true
|
74
79
|
|
@@ -78,9 +83,11 @@ module Puma
|
|
78
83
|
ENV['RACK_ENV'] ||= "development"
|
79
84
|
|
80
85
|
@mode = :http
|
86
|
+
|
87
|
+
@precheck_closing = true
|
81
88
|
end
|
82
89
|
|
83
|
-
attr_accessor :binder, :leak_stack_on_error
|
90
|
+
attr_accessor :binder, :leak_stack_on_error, :early_hints
|
84
91
|
|
85
92
|
forward :add_tcp_listener, :@binder
|
86
93
|
forward :add_ssl_listener, :@binder
|
@@ -89,7 +96,6 @@ module Puma
|
|
89
96
|
|
90
97
|
def inherit_binder(bind)
|
91
98
|
@binder = bind
|
92
|
-
@own_binder = false
|
93
99
|
end
|
94
100
|
|
95
101
|
def tcp_mode!
|
@@ -100,6 +106,8 @@ module Puma
|
|
100
106
|
# packetizes our stream. This improves both latency and throughput.
|
101
107
|
#
|
102
108
|
if RUBY_PLATFORM =~ /linux/
|
109
|
+
UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
|
110
|
+
|
103
111
|
# 6 == Socket::IPPROTO_TCP
|
104
112
|
# 3 == TCP_CORK
|
105
113
|
# 1/0 == turn on/off
|
@@ -107,6 +115,7 @@ module Puma
|
|
107
115
|
begin
|
108
116
|
socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
|
109
117
|
rescue IOError, SystemCallError
|
118
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
110
119
|
end
|
111
120
|
end
|
112
121
|
|
@@ -114,6 +123,24 @@ module Puma
|
|
114
123
|
begin
|
115
124
|
socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
|
116
125
|
rescue IOError, SystemCallError
|
126
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def closed_socket?(socket)
|
131
|
+
return false unless socket.kind_of? TCPSocket
|
132
|
+
return false unless @precheck_closing
|
133
|
+
|
134
|
+
begin
|
135
|
+
tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
|
136
|
+
rescue IOError, SystemCallError
|
137
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
138
|
+
@precheck_closing = false
|
139
|
+
false
|
140
|
+
else
|
141
|
+
state = tcp_info.unpack(UNPACK_TCP_STATE_FROM_TCP_INFO)[0]
|
142
|
+
# TIME_WAIT: 6, CLOSE: 7, CLOSE_WAIT: 8, LAST_ACK: 9, CLOSING: 11
|
143
|
+
(state >= 6 && state <= 9) || state == 11
|
117
144
|
end
|
118
145
|
end
|
119
146
|
else
|
@@ -122,6 +149,10 @@ module Puma
|
|
122
149
|
|
123
150
|
def uncork_socket(socket)
|
124
151
|
end
|
152
|
+
|
153
|
+
def closed_socket?(socket)
|
154
|
+
false
|
155
|
+
end
|
125
156
|
end
|
126
157
|
|
127
158
|
def backlog
|
@@ -132,6 +163,18 @@ module Puma
|
|
132
163
|
@thread_pool and @thread_pool.spawned
|
133
164
|
end
|
134
165
|
|
166
|
+
|
167
|
+
# This number represents the number of requests that
|
168
|
+
# the server is capable of taking right now.
|
169
|
+
#
|
170
|
+
# For example if the number is 5 then it means
|
171
|
+
# there are 5 threads sitting idle ready to take
|
172
|
+
# a request. If one request comes in, then the
|
173
|
+
# value would be 4 until it finishes processing.
|
174
|
+
def pool_capacity
|
175
|
+
@thread_pool and @thread_pool.pool_capacity
|
176
|
+
end
|
177
|
+
|
135
178
|
# Lopez Mode == raw tcp apps
|
136
179
|
|
137
180
|
def run_lopez_mode(background=true)
|
@@ -193,7 +236,11 @@ module Puma
|
|
193
236
|
# nothing
|
194
237
|
rescue Errno::ECONNABORTED
|
195
238
|
# client closed the socket even before accept
|
196
|
-
|
239
|
+
begin
|
240
|
+
io.close
|
241
|
+
rescue
|
242
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
243
|
+
end
|
197
244
|
end
|
198
245
|
end
|
199
246
|
end
|
@@ -210,11 +257,17 @@ module Puma
|
|
210
257
|
STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
|
211
258
|
STDERR.puts e.backtrace
|
212
259
|
ensure
|
213
|
-
|
214
|
-
|
260
|
+
begin
|
261
|
+
@check.close
|
262
|
+
rescue
|
263
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
264
|
+
end
|
215
265
|
|
216
|
-
|
217
|
-
|
266
|
+
# Prevent can't modify frozen IOError (RuntimeError)
|
267
|
+
begin
|
268
|
+
@notify.close
|
269
|
+
rescue IOError
|
270
|
+
# no biggy
|
218
271
|
end
|
219
272
|
end
|
220
273
|
|
@@ -268,7 +321,7 @@ module Puma
|
|
268
321
|
client.close
|
269
322
|
|
270
323
|
@events.parse_error self, client.env, e
|
271
|
-
rescue ConnectionError
|
324
|
+
rescue ConnectionError, EOFError
|
272
325
|
client.close
|
273
326
|
else
|
274
327
|
if process_now
|
@@ -339,13 +392,20 @@ module Puma
|
|
339
392
|
end
|
340
393
|
|
341
394
|
pool << client
|
342
|
-
pool.wait_until_not_full
|
395
|
+
busy_threads = pool.wait_until_not_full
|
396
|
+
if busy_threads == 0
|
397
|
+
@options[:out_of_band].each(&:call) if @options[:out_of_band]
|
398
|
+
end
|
343
399
|
end
|
344
400
|
rescue SystemCallError
|
345
401
|
# nothing
|
346
402
|
rescue Errno::ECONNABORTED
|
347
403
|
# client closed the socket even before accept
|
348
|
-
|
404
|
+
begin
|
405
|
+
io.close
|
406
|
+
rescue
|
407
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
408
|
+
end
|
349
409
|
end
|
350
410
|
end
|
351
411
|
end
|
@@ -367,10 +427,6 @@ module Puma
|
|
367
427
|
ensure
|
368
428
|
@check.close
|
369
429
|
@notify.close
|
370
|
-
|
371
|
-
if @status != :restart and @own_binder
|
372
|
-
@binder.close
|
373
|
-
end
|
374
430
|
end
|
375
431
|
|
376
432
|
@events.fire :state, :done
|
@@ -404,10 +460,6 @@ module Puma
|
|
404
460
|
def process_client(client, buffer)
|
405
461
|
begin
|
406
462
|
|
407
|
-
if client.env[HTTP_EXPECT] == CONTINUE
|
408
|
-
client.io << HTTP_11_100
|
409
|
-
end
|
410
|
-
|
411
463
|
clean_thread_locals = @options[:clean_thread_locals]
|
412
464
|
close_socket = true
|
413
465
|
|
@@ -471,6 +523,7 @@ module Puma
|
|
471
523
|
begin
|
472
524
|
client.close if close_socket
|
473
525
|
rescue IOError, SystemCallError
|
526
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
474
527
|
# Already closed
|
475
528
|
rescue StandardError => e
|
476
529
|
@events.unknown_error self, e, "Client"
|
@@ -502,7 +555,9 @@ module Puma
|
|
502
555
|
|
503
556
|
raise "No REQUEST PATH" unless env[REQUEST_PATH]
|
504
557
|
|
505
|
-
env
|
558
|
+
# A nil env value will cause a LintError (and fatal errors elsewhere),
|
559
|
+
# so only set the env value if there actually is a value.
|
560
|
+
env[QUERY_STRING] = uri.query if uri.query
|
506
561
|
end
|
507
562
|
|
508
563
|
env[PATH_INFO] = env[REQUEST_PATH]
|
@@ -533,23 +588,32 @@ module Puma
|
|
533
588
|
end
|
534
589
|
|
535
590
|
def default_server_port(env)
|
536
|
-
|
537
|
-
|
591
|
+
if ['on', HTTPS].include?(env[HTTPS_KEY]) || env[HTTP_X_FORWARDED_PROTO].to_s[0...5] == HTTPS || env[HTTP_X_FORWARDED_SCHEME] == HTTPS || env[HTTP_X_FORWARDED_SSL] == "on"
|
592
|
+
PORT_443
|
593
|
+
else
|
594
|
+
PORT_80
|
595
|
+
end
|
538
596
|
end
|
539
597
|
|
540
|
-
#
|
541
|
-
#
|
542
|
-
# the rack app. Then construct the response and write it back to
|
543
|
-
# +client+
|
598
|
+
# Takes the request +req+, invokes the Rack application to construct
|
599
|
+
# the response and writes it back to +req.io+.
|
544
600
|
#
|
545
|
-
# +
|
546
|
-
#
|
547
|
-
# it up again.
|
601
|
+
# The second parameter +lines+ is a IO-like object unique to this thread.
|
602
|
+
# This is normally an instance of Puma::IOBuffer.
|
548
603
|
#
|
604
|
+
# It'll return +false+ when the connection is closed, this doesn't mean
|
605
|
+
# that the response wasn't successful.
|
606
|
+
#
|
607
|
+
# It'll return +:async+ if the connection remains open but will be handled
|
608
|
+
# elsewhere, i.e. the connection has been hijacked by the Rack application.
|
609
|
+
#
|
610
|
+
# Finally, it'll return +true+ on keep-alive connections.
|
549
611
|
def handle_request(req, lines)
|
550
612
|
env = req.env
|
551
613
|
client = req.io
|
552
614
|
|
615
|
+
return false if closed_socket?(client)
|
616
|
+
|
553
617
|
normalize_env env, req
|
554
618
|
|
555
619
|
env[PUMA_SOCKET] = client
|
@@ -566,7 +630,29 @@ module Puma
|
|
566
630
|
head = env[REQUEST_METHOD] == HEAD
|
567
631
|
|
568
632
|
env[RACK_INPUT] = body
|
569
|
-
env[RACK_URL_SCHEME] =
|
633
|
+
env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP
|
634
|
+
|
635
|
+
if @early_hints
|
636
|
+
env[EARLY_HINTS] = lambda { |headers|
|
637
|
+
begin
|
638
|
+
fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
|
639
|
+
|
640
|
+
headers.each_pair do |k, vs|
|
641
|
+
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
642
|
+
vs.to_s.split(NEWLINE).each do |v|
|
643
|
+
fast_write client, "#{k}: #{v}\r\n"
|
644
|
+
end
|
645
|
+
else
|
646
|
+
fast_write client, "#{k}: #{vs}\r\n"
|
647
|
+
end
|
648
|
+
end
|
649
|
+
|
650
|
+
fast_write client, "\r\n".freeze
|
651
|
+
rescue ConnectionError
|
652
|
+
# noop, if we lost the socket we just won't send the early hints
|
653
|
+
end
|
654
|
+
}
|
655
|
+
end
|
570
656
|
|
571
657
|
# A rack extension. If the app writes #call'ables to this
|
572
658
|
# array, we will invoke them when the request is done.
|
@@ -665,7 +751,7 @@ module Puma
|
|
665
751
|
next
|
666
752
|
end
|
667
753
|
|
668
|
-
if vs.respond_to?(:to_s)
|
754
|
+
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
669
755
|
vs.to_s.split(NEWLINE).each do |v|
|
670
756
|
lines.append k, colon, v, line_ending
|
671
757
|
end
|
@@ -709,8 +795,8 @@ module Puma
|
|
709
795
|
|
710
796
|
begin
|
711
797
|
res_body.each do |part|
|
798
|
+
next if part.bytesize.zero?
|
712
799
|
if chunked
|
713
|
-
next if part.bytesize.zero?
|
714
800
|
fast_write client, part.bytesize.to_s(16)
|
715
801
|
fast_write client, line_ending
|
716
802
|
fast_write client, part
|
@@ -860,6 +946,10 @@ module Puma
|
|
860
946
|
@events.debug "Drained #{count} additional connections."
|
861
947
|
end
|
862
948
|
|
949
|
+
if @status != :restart
|
950
|
+
@binder.close
|
951
|
+
end
|
952
|
+
|
863
953
|
if @thread_pool
|
864
954
|
if timeout = @options[:force_shutdown_after]
|
865
955
|
@thread_pool.shutdown timeout.to_i
|
@@ -869,35 +959,38 @@ module Puma
|
|
869
959
|
end
|
870
960
|
end
|
871
961
|
|
872
|
-
|
873
|
-
# off the request queue before finally exiting.
|
874
|
-
#
|
875
|
-
def stop(sync=false)
|
962
|
+
def notify_safely(message)
|
876
963
|
begin
|
877
|
-
@notify <<
|
964
|
+
@notify << message
|
878
965
|
rescue IOError
|
879
|
-
|
966
|
+
# The server, in another thread, is shutting down
|
967
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
968
|
+
rescue RuntimeError => e
|
969
|
+
# Temporary workaround for https://bugs.ruby-lang.org/issues/13239
|
970
|
+
if e.message.include?('IOError')
|
971
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
972
|
+
else
|
973
|
+
raise e
|
974
|
+
end
|
880
975
|
end
|
976
|
+
end
|
977
|
+
private :notify_safely
|
978
|
+
|
979
|
+
# Stops the acceptor thread and then causes the worker threads to finish
|
980
|
+
# off the request queue before finally exiting.
|
881
981
|
|
982
|
+
def stop(sync=false)
|
983
|
+
notify_safely(STOP_COMMAND)
|
882
984
|
@thread.join if @thread && sync
|
883
985
|
end
|
884
986
|
|
885
987
|
def halt(sync=false)
|
886
|
-
|
887
|
-
@notify << HALT_COMMAND
|
888
|
-
rescue IOError
|
889
|
-
# The server, in another thread, is shutting down
|
890
|
-
end
|
891
|
-
|
988
|
+
notify_safely(HALT_COMMAND)
|
892
989
|
@thread.join if @thread && sync
|
893
990
|
end
|
894
991
|
|
895
992
|
def begin_restart
|
896
|
-
|
897
|
-
@notify << RESTART_COMMAND
|
898
|
-
rescue IOError
|
899
|
-
# The server, in another thread, is shutting down
|
900
|
-
end
|
993
|
+
notify_safely(RESTART_COMMAND)
|
901
994
|
end
|
902
995
|
|
903
996
|
def fast_write(io, str)
|