wendell-puma 2.9.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 +7 -0
- data/COPYING +55 -0
- data/DEPLOYMENT.md +92 -0
- data/Gemfile +17 -0
- data/History.txt +588 -0
- data/LICENSE +26 -0
- data/Manifest.txt +68 -0
- data/README.md +251 -0
- data/Rakefile +158 -0
- data/bin/puma +10 -0
- data/bin/puma-wild +31 -0
- data/bin/pumactl +12 -0
- data/docs/config.md +0 -0
- data/docs/nginx.md +80 -0
- data/docs/signals.md +43 -0
- data/ext/puma_http11/PumaHttp11Service.java +17 -0
- data/ext/puma_http11/ext_help.h +15 -0
- data/ext/puma_http11/extconf.rb +9 -0
- data/ext/puma_http11/http11_parser.c +1225 -0
- data/ext/puma_http11/http11_parser.h +64 -0
- data/ext/puma_http11/http11_parser.java.rl +161 -0
- data/ext/puma_http11/http11_parser.rl +146 -0
- data/ext/puma_http11/http11_parser_common.rl +54 -0
- data/ext/puma_http11/io_buffer.c +155 -0
- data/ext/puma_http11/mini_ssl.c +198 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +225 -0
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +488 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +391 -0
- data/ext/puma_http11/puma_http11.c +491 -0
- data/lib/puma.rb +14 -0
- data/lib/puma/accept_nonblock.rb +23 -0
- data/lib/puma/app/status.rb +59 -0
- data/lib/puma/binder.rb +298 -0
- data/lib/puma/capistrano.rb +86 -0
- data/lib/puma/cli.rb +606 -0
- data/lib/puma/client.rb +289 -0
- data/lib/puma/cluster.rb +404 -0
- data/lib/puma/compat.rb +18 -0
- data/lib/puma/configuration.rb +377 -0
- data/lib/puma/const.rb +165 -0
- data/lib/puma/control_cli.rb +251 -0
- data/lib/puma/daemon_ext.rb +25 -0
- data/lib/puma/delegation.rb +11 -0
- data/lib/puma/detect.rb +4 -0
- data/lib/puma/events.rb +130 -0
- data/lib/puma/io_buffer.rb +7 -0
- data/lib/puma/java_io_buffer.rb +45 -0
- data/lib/puma/jruby_restart.rb +83 -0
- data/lib/puma/minissl.rb +187 -0
- data/lib/puma/null_io.rb +34 -0
- data/lib/puma/rack_default.rb +7 -0
- data/lib/puma/rack_patch.rb +45 -0
- data/lib/puma/reactor.rb +183 -0
- data/lib/puma/runner.rb +146 -0
- data/lib/puma/server.rb +801 -0
- data/lib/puma/single.rb +102 -0
- data/lib/puma/tcp_logger.rb +32 -0
- data/lib/puma/thread_pool.rb +185 -0
- data/lib/puma/util.rb +9 -0
- data/lib/rack/handler/puma.rb +66 -0
- data/test/test_app_status.rb +92 -0
- data/test/test_cli.rb +173 -0
- data/test/test_config.rb +26 -0
- data/test/test_http10.rb +27 -0
- data/test/test_http11.rb +144 -0
- data/test/test_integration.rb +165 -0
- data/test/test_iobuffer.rb +38 -0
- data/test/test_minissl.rb +29 -0
- data/test/test_null_io.rb +31 -0
- data/test/test_persistent.rb +238 -0
- data/test/test_puma_server.rb +288 -0
- data/test/test_puma_server_ssl.rb +137 -0
- data/test/test_rack_handler.rb +10 -0
- data/test/test_rack_server.rb +141 -0
- data/test/test_tcp_rack.rb +42 -0
- data/test/test_thread_pool.rb +156 -0
- data/test/test_unix_socket.rb +39 -0
- data/test/test_ws.rb +89 -0
- data/tools/jungle/README.md +9 -0
- data/tools/jungle/init.d/README.md +54 -0
- data/tools/jungle/init.d/puma +332 -0
- data/tools/jungle/init.d/run-puma +3 -0
- data/tools/jungle/upstart/README.md +61 -0
- data/tools/jungle/upstart/puma-manager.conf +31 -0
- data/tools/jungle/upstart/puma.conf +63 -0
- data/tools/trickletest.rb +45 -0
- data/wendell-puma.gemspec +55 -0
- metadata +225 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
require 'ffi'
|
|
2
|
+
|
|
3
|
+
module Puma
|
|
4
|
+
module JRubyRestart
|
|
5
|
+
extend FFI::Library
|
|
6
|
+
ffi_lib 'c'
|
|
7
|
+
|
|
8
|
+
attach_function :execlp, [:string, :varargs], :int
|
|
9
|
+
attach_function :chdir, [:string], :int
|
|
10
|
+
attach_function :fork, [], :int
|
|
11
|
+
attach_function :exit, [:int], :void
|
|
12
|
+
attach_function :setsid, [], :int
|
|
13
|
+
|
|
14
|
+
def self.chdir_exec(dir, argv)
|
|
15
|
+
chdir(dir)
|
|
16
|
+
cmd = argv.first
|
|
17
|
+
argv = ([:string] * argv.size).zip(argv).flatten
|
|
18
|
+
argv << :string
|
|
19
|
+
argv << nil
|
|
20
|
+
execlp(cmd, *argv)
|
|
21
|
+
raise SystemCallError.new(FFI.errno)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
PermKey = 'PUMA_DAEMON_PERM'
|
|
25
|
+
RestartKey = 'PUMA_DAEMON_RESTART'
|
|
26
|
+
|
|
27
|
+
# Called to tell things "Your now always in daemon mode,
|
|
28
|
+
# don't try to reenter it."
|
|
29
|
+
#
|
|
30
|
+
def self.perm_daemonize
|
|
31
|
+
ENV[PermKey] = "1"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.daemon?
|
|
35
|
+
ENV.key?(PermKey) || ENV.key?(RestartKey)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.daemon_init
|
|
39
|
+
return true if ENV.key?(PermKey)
|
|
40
|
+
|
|
41
|
+
return false unless ENV.key? RestartKey
|
|
42
|
+
|
|
43
|
+
master = ENV[RestartKey]
|
|
44
|
+
|
|
45
|
+
# In case the master disappears early
|
|
46
|
+
begin
|
|
47
|
+
Process.kill "SIGUSR2", master.to_i
|
|
48
|
+
rescue SystemCallError => e
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
ENV[RestartKey] = ""
|
|
52
|
+
|
|
53
|
+
setsid
|
|
54
|
+
|
|
55
|
+
null = File.open "/dev/null", "w+"
|
|
56
|
+
STDIN.reopen null
|
|
57
|
+
STDOUT.reopen null
|
|
58
|
+
STDERR.reopen null
|
|
59
|
+
|
|
60
|
+
true
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.daemon_start(dir, argv)
|
|
64
|
+
ENV['PUMA_DAEMON_RESTART'] = Process.pid.to_s
|
|
65
|
+
|
|
66
|
+
if k = ENV['PUMA_JRUBY_DAEMON_OPTS']
|
|
67
|
+
ENV['JRUBY_OPTS'] = k
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
cmd = argv.first
|
|
71
|
+
argv = ([:string] * argv.size).zip(argv).flatten
|
|
72
|
+
argv << :string
|
|
73
|
+
argv << nil
|
|
74
|
+
|
|
75
|
+
chdir(dir)
|
|
76
|
+
ret = fork
|
|
77
|
+
return ret if ret != 0
|
|
78
|
+
execlp(cmd, *argv)
|
|
79
|
+
raise SystemCallError.new(FFI.errno)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
data/lib/puma/minissl.rb
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
if Puma::IS_JRUBY
|
|
2
|
+
require 'java'
|
|
3
|
+
java_import 'java.lang.RuntimeException'
|
|
4
|
+
require 'puma/delegation'
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
module Puma
|
|
9
|
+
module MiniSSL
|
|
10
|
+
class EngineWrapper
|
|
11
|
+
extend Puma::Delegation
|
|
12
|
+
|
|
13
|
+
%w(inject extract write).each do |action|
|
|
14
|
+
forward action, :@engine
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def initialize(engine)
|
|
18
|
+
@engine=engine
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
if Puma::IS_JRUBY
|
|
22
|
+
def read
|
|
23
|
+
begin
|
|
24
|
+
@engine.read
|
|
25
|
+
rescue RuntimeException=>e
|
|
26
|
+
raise IOError.new("Unable to read from engine, #{e.message}")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
else
|
|
30
|
+
forward :read, :@engine
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
class Socket
|
|
34
|
+
def initialize(socket, engine)
|
|
35
|
+
@socket = socket
|
|
36
|
+
@engine = EngineWrapper.new(engine)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def to_io
|
|
40
|
+
@socket
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def readpartial(size)
|
|
44
|
+
while true
|
|
45
|
+
output = @engine.read
|
|
46
|
+
return output if output
|
|
47
|
+
|
|
48
|
+
data = @socket.readpartial(size)
|
|
49
|
+
@engine.inject(data)
|
|
50
|
+
output = @engine.read
|
|
51
|
+
|
|
52
|
+
return output if output
|
|
53
|
+
|
|
54
|
+
while neg_data = @engine.extract
|
|
55
|
+
@socket.write neg_data
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def engine_read_all
|
|
61
|
+
output = @engine.read
|
|
62
|
+
while output and additional_output = @engine.read
|
|
63
|
+
output << additional_output
|
|
64
|
+
end
|
|
65
|
+
output
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def read_nonblock(size)
|
|
69
|
+
while true
|
|
70
|
+
output = engine_read_all
|
|
71
|
+
return output if output
|
|
72
|
+
|
|
73
|
+
data = @socket.read_nonblock(size)
|
|
74
|
+
|
|
75
|
+
@engine.inject(data)
|
|
76
|
+
output = engine_read_all
|
|
77
|
+
|
|
78
|
+
return output if output
|
|
79
|
+
|
|
80
|
+
while neg_data = @engine.extract
|
|
81
|
+
@socket.write neg_data
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def write(data)
|
|
87
|
+
need = data.bytesize
|
|
88
|
+
|
|
89
|
+
while true
|
|
90
|
+
wrote = @engine.write data
|
|
91
|
+
enc = @engine.extract
|
|
92
|
+
|
|
93
|
+
while enc
|
|
94
|
+
@socket.write enc
|
|
95
|
+
enc = @engine.extract
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
need -= wrote
|
|
99
|
+
|
|
100
|
+
return data.bytesize if need == 0
|
|
101
|
+
|
|
102
|
+
data = data[wrote..-1]
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
alias_method :syswrite, :write
|
|
107
|
+
|
|
108
|
+
def flush
|
|
109
|
+
@socket.flush
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def close
|
|
113
|
+
@socket.close
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def peeraddr
|
|
117
|
+
@socket.peeraddr
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
class Context
|
|
122
|
+
attr_accessor :verify_mode
|
|
123
|
+
|
|
124
|
+
if defined?(JRUBY_VERSION)
|
|
125
|
+
# jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
|
|
126
|
+
attr_reader :keystore
|
|
127
|
+
attr_accessor :keystore_pass
|
|
128
|
+
attr_accessor :enable_SSLv3
|
|
129
|
+
|
|
130
|
+
def initialize
|
|
131
|
+
@enable_SSLv3 = false
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def keystore=(keystore)
|
|
135
|
+
raise ArgumentError, "No such keystore file '#{keystore}'" unless File.exist? keystore
|
|
136
|
+
@keystore = keystore
|
|
137
|
+
end
|
|
138
|
+
else
|
|
139
|
+
# non-jruby Context properties
|
|
140
|
+
attr_reader :key
|
|
141
|
+
attr_reader :cert
|
|
142
|
+
|
|
143
|
+
def key=(key)
|
|
144
|
+
raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
|
|
145
|
+
@key = key
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def cert=(cert)
|
|
149
|
+
raise ArgumentError, "No such cert file '#{cert}'" unless File.exist? cert
|
|
150
|
+
@cert = cert
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
VERIFY_NONE = 0
|
|
156
|
+
VERIFY_PEER = 1
|
|
157
|
+
|
|
158
|
+
class Server
|
|
159
|
+
def initialize(socket, ctx)
|
|
160
|
+
@socket = socket
|
|
161
|
+
@ctx = ctx
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def to_io
|
|
165
|
+
@socket
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def accept
|
|
169
|
+
io = @socket.accept
|
|
170
|
+
engine = Engine.server @ctx
|
|
171
|
+
|
|
172
|
+
Socket.new io, engine
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def accept_nonblock
|
|
176
|
+
io = @socket.accept_nonblock
|
|
177
|
+
engine = Engine.server @ctx
|
|
178
|
+
|
|
179
|
+
Socket.new io, engine
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def close
|
|
183
|
+
@socket.close
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
data/lib/puma/null_io.rb
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Puma
|
|
2
|
+
|
|
3
|
+
# Provides an IO-like object that always appears to contain no data.
|
|
4
|
+
# Used as the value for rack.input when the request has no body.
|
|
5
|
+
#
|
|
6
|
+
class NullIO
|
|
7
|
+
# Always returns nil
|
|
8
|
+
#
|
|
9
|
+
def gets
|
|
10
|
+
nil
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Never yields
|
|
14
|
+
#
|
|
15
|
+
def each
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Mimics IO#read with no data
|
|
19
|
+
#
|
|
20
|
+
def read(count=nil,buffer=nil)
|
|
21
|
+
(count && count > 0) ? nil : ""
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Does nothing
|
|
25
|
+
#
|
|
26
|
+
def rewind
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Does nothing
|
|
30
|
+
#
|
|
31
|
+
def close
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'rack/commonlogger'
|
|
2
|
+
|
|
3
|
+
module Rack
|
|
4
|
+
# Patch CommonLogger to use after_reply.
|
|
5
|
+
#
|
|
6
|
+
# Simply request this file and CommonLogger will be a bit more
|
|
7
|
+
# efficient.
|
|
8
|
+
class CommonLogger
|
|
9
|
+
remove_method :call
|
|
10
|
+
|
|
11
|
+
def call(env)
|
|
12
|
+
began_at = Time.now
|
|
13
|
+
status, header, body = @app.call(env)
|
|
14
|
+
header = Utils::HeaderHash.new(header)
|
|
15
|
+
|
|
16
|
+
# If we've been hijacked, then output a special line
|
|
17
|
+
if env['rack.hijack_io']
|
|
18
|
+
log_hijacking(env, 'HIJACK', header, began_at)
|
|
19
|
+
elsif ary = env['rack.after_reply']
|
|
20
|
+
ary << lambda { log(env, status, header, began_at) }
|
|
21
|
+
else
|
|
22
|
+
body = BodyProxy.new(body) { log(env, status, header, began_at) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
[status, header, body]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
HIJACK_FORMAT = %{%s - %s [%s] "%s %s%s %s" HIJACKED -1 %0.4f\n}
|
|
29
|
+
|
|
30
|
+
def log_hijacking(env, status, header, began_at)
|
|
31
|
+
now = Time.now
|
|
32
|
+
|
|
33
|
+
logger = @logger || env['rack.errors']
|
|
34
|
+
logger.write HIJACK_FORMAT % [
|
|
35
|
+
env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
|
|
36
|
+
env["REMOTE_USER"] || "-",
|
|
37
|
+
now.strftime("%d/%b/%Y %H:%M:%S"),
|
|
38
|
+
env["REQUEST_METHOD"],
|
|
39
|
+
env["PATH_INFO"],
|
|
40
|
+
env["QUERY_STRING"].empty? ? "" : "?"+env["QUERY_STRING"],
|
|
41
|
+
env["HTTP_VERSION"],
|
|
42
|
+
now - began_at ]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
data/lib/puma/reactor.rb
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
require 'puma/util'
|
|
2
|
+
|
|
3
|
+
module Puma
|
|
4
|
+
class Reactor
|
|
5
|
+
DefaultSleepFor = 5
|
|
6
|
+
|
|
7
|
+
def initialize(server, app_pool)
|
|
8
|
+
@server = server
|
|
9
|
+
@events = server.events
|
|
10
|
+
@app_pool = app_pool
|
|
11
|
+
|
|
12
|
+
@mutex = Mutex.new
|
|
13
|
+
@ready, @trigger = Puma::Util.pipe
|
|
14
|
+
@input = []
|
|
15
|
+
@sleep_for = DefaultSleepFor
|
|
16
|
+
@timeouts = []
|
|
17
|
+
|
|
18
|
+
@sockets = [@ready]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def run_internal
|
|
24
|
+
sockets = @sockets
|
|
25
|
+
|
|
26
|
+
while true
|
|
27
|
+
begin
|
|
28
|
+
ready = IO.select sockets, nil, nil, @sleep_for
|
|
29
|
+
rescue IOError => e
|
|
30
|
+
if sockets.any? { |socket| socket.closed? }
|
|
31
|
+
STDERR.puts "Error in select: #{e.message} (#{e.class})"
|
|
32
|
+
STDERR.puts e.backtrace
|
|
33
|
+
sockets = sockets.reject { |socket| socket.closed? }
|
|
34
|
+
retry
|
|
35
|
+
else
|
|
36
|
+
raise
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
if ready and reads = ready[0]
|
|
41
|
+
reads.each do |c|
|
|
42
|
+
if c == @ready
|
|
43
|
+
@mutex.synchronize do
|
|
44
|
+
case @ready.read(1)
|
|
45
|
+
when "*"
|
|
46
|
+
sockets += @input
|
|
47
|
+
@input.clear
|
|
48
|
+
when "c"
|
|
49
|
+
sockets.delete_if do |s|
|
|
50
|
+
if s == @ready
|
|
51
|
+
false
|
|
52
|
+
else
|
|
53
|
+
s.close
|
|
54
|
+
true
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
when "!"
|
|
58
|
+
return
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
else
|
|
62
|
+
# We have to be sure to remove it from the timeout
|
|
63
|
+
# list or we'll accidentally close the socket when
|
|
64
|
+
# it's in use!
|
|
65
|
+
if c.timeout_at
|
|
66
|
+
@mutex.synchronize do
|
|
67
|
+
@timeouts.delete c
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
begin
|
|
72
|
+
if c.try_to_finish
|
|
73
|
+
@app_pool << c
|
|
74
|
+
sockets.delete c
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# The client doesn't know HTTP well
|
|
78
|
+
rescue HttpParserError => e
|
|
79
|
+
c.write_400
|
|
80
|
+
c.close
|
|
81
|
+
|
|
82
|
+
sockets.delete c
|
|
83
|
+
|
|
84
|
+
@events.parse_error @server, c.env, e
|
|
85
|
+
rescue StandardError => e
|
|
86
|
+
c.write_500
|
|
87
|
+
c.close
|
|
88
|
+
|
|
89
|
+
sockets.delete c
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
unless @timeouts.empty?
|
|
96
|
+
@mutex.synchronize do
|
|
97
|
+
now = Time.now
|
|
98
|
+
|
|
99
|
+
while @timeouts.first.timeout_at < now
|
|
100
|
+
c = @timeouts.shift
|
|
101
|
+
c.write_408 if c.in_data_phase
|
|
102
|
+
c.close
|
|
103
|
+
sockets.delete c
|
|
104
|
+
|
|
105
|
+
break if @timeouts.empty?
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
calculate_sleep
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
public
|
|
115
|
+
|
|
116
|
+
def run
|
|
117
|
+
run_internal
|
|
118
|
+
ensure
|
|
119
|
+
@trigger.close
|
|
120
|
+
@ready.close
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def run_in_thread
|
|
124
|
+
@thread = Thread.new do
|
|
125
|
+
begin
|
|
126
|
+
run_internal
|
|
127
|
+
rescue StandardError => e
|
|
128
|
+
STDERR.puts "Error in reactor loop escaped: #{e.message} (#{e.class})"
|
|
129
|
+
STDERR.puts e.backtrace
|
|
130
|
+
retry
|
|
131
|
+
ensure
|
|
132
|
+
@trigger.close
|
|
133
|
+
@ready.close
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def calculate_sleep
|
|
139
|
+
if @timeouts.empty?
|
|
140
|
+
@sleep_for = DefaultSleepFor
|
|
141
|
+
else
|
|
142
|
+
diff = @timeouts.first.timeout_at.to_f - Time.now.to_f
|
|
143
|
+
|
|
144
|
+
if diff < 0.0
|
|
145
|
+
@sleep_for = 0
|
|
146
|
+
else
|
|
147
|
+
@sleep_for = diff
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def add(c)
|
|
153
|
+
@mutex.synchronize do
|
|
154
|
+
@input << c
|
|
155
|
+
@trigger << "*"
|
|
156
|
+
|
|
157
|
+
if c.timeout_at
|
|
158
|
+
@timeouts << c
|
|
159
|
+
@timeouts.sort! { |a,b| a.timeout_at <=> b.timeout_at }
|
|
160
|
+
|
|
161
|
+
calculate_sleep
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Close all watched sockets and clear them from being watched
|
|
167
|
+
def clear!
|
|
168
|
+
begin
|
|
169
|
+
@trigger << "c"
|
|
170
|
+
rescue IOError
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def shutdown
|
|
175
|
+
begin
|
|
176
|
+
@trigger << "!"
|
|
177
|
+
rescue IOError
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
@thread.join
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|