http-2: memory leak
Regarding https://github.com/ostinelli/net-http2/issues/7, I was able to reproduce the memory leak outside of net-http2
with a separate client, so the leak appears to be with http-2
.
My class for sending messages is below, though even with the hack to delete @listeners
it still leaks. (It’s been a while since I looked at this code, so I’m guessing that made it leak less.) In production sending a handful of messages every second over a persistent connection (Apple’s APNS servers), the process balloons to 512mb in about 7 hours (which triggers it to be restarted).
This is with Ruby 2.24p230 on OpenBSD. Any help troubleshooting this would be appreciated.
require "http/2"
require "openssl"
require "resolv"
require "ostruct"
# monkeypatch to free up memory
module HTTP2
module Emitter
def delete_listeners
if @listeners
@listeners.each do |k,v|
@listeners.delete(k)
end
end
@listeners = nil
end
end
end
class HTTP2Client
attr_accessor :hostname, :ssl_context, :ssl_socket, :tcp_socket, :h2_client,
:h2_stream, :headers, :body, :done
DRAFT = "h2"
def initialize(hostname, ip = nil, ctx = nil)
if !ctx
ctx = OpenSSL::SSL::SSLContext.new
end
ctx.npn_protocols = [ DRAFT ]
ctx.npn_select_cb = lambda do |protocols|
DRAFT if protocols.include?(DRAFT)
end
self.ssl_context = ctx
if !ip
ip = Resolv.getaddress(hostname)
end
begin
Timeout.timeout(10) do
self.tcp_socket = TCPSocket.new(ip, 443)
sock = OpenSSL::SSL::SSLSocket.new(self.tcp_socket, self.ssl_context)
sock.sync_close = true
sock.hostname = hostname
sock.connect
self.ssl_socket = sock
end
rescue Timeout::Error
self.log(:error, nil, "timed out connecting to #{host[:ip]}")
self.store_client(nil, dev)
return nil
end
self.hostname = hostname
self.h2_client = HTTP2::Client.new
self.h2_client.on(:frame) do |bytes|
self.ssl_socket.print bytes
self.ssl_socket.flush
nil
end
end
def call(method, path, options = {})
if self.h2_stream
self.cleanup_stream
end
headers = (options[:headers] || {})
headers.merge!({
":scheme" => "https",
":method" => method.to_s.upcase,
":path" => path,
})
headers.merge!("host" => self.hostname)
if options[:body]
headers.merge!("content-length" => options[:body].bytesize.to_s)
else
headers.delete("content-length")
end
self.h2_stream = self.h2_client.new_stream
self.headers = {}
self.h2_stream.on(:headers) do |hs_array|
hs = Hash[*hs_array.flatten]
self.headers.merge!(hs)
nil
end
self.body = ""
self.h2_stream.on(:data) do |d|
self.body << d
nil
end
self.done = false
self.h2_stream.on(:close) do |d|
self.done = true
nil
end
if options[:body]
self.h2_stream.headers(headers, :end_stream => false)
self.h2_stream.data(options[:body], :end_stream => true)
else
self.h2_stream.headers(headers, :end_stream => true)
end
while !self.ssl_socket.closed? && !self.ssl_socket.eof?
data = self.ssl_socket.read_nonblock(1024)
begin
self.h2_client << data
rescue => e
self.ssl_socket.close
raise e
end
if self.done
break
end
end
if self.done
self.cleanup_stream
return OpenStruct.new(:status => self.headers[":status"],
:headers => self.headers, :body => self.body)
else
return nil
end
end
def close
begin
if self.h2_client
self.h2_client.close
end
rescue
end
begin
if self.ssl_socket
self.ssl_socket.close
end
rescue
end
begin
if self.tcp_socket
self.tcp_socket.close
end
rescue
end
end
def cleanup_stream
if self.h2_stream
self.h2_stream.delete_listeners
end
begin
self.h2_stream.close
rescue
end
end
end
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Comments: 26 (8 by maintainers)
Commits related to this issue
- cleanup closed streams to reclaim memory Background: https://github.com/igrigorik/http-2/issues/73 We store a reference to recently closed streams and periodically purge them whenever some other str... — committed to igrigorik/http-2 by igrigorik 8 years ago
- cleanup closed streams to reclaim memory (#88) Background: https://github.com/igrigorik/http-2/issues/73 We store a reference to recently closed streams and periodically purge them whenever some ... — committed to igrigorik/http-2 by igrigorik 8 years ago
Memory usage is still reasonable after 48 hours.
If @jcs confirms I think this can be released? 😃
/me summons @kazuho for some sage implementation advice… 😃
Context from https://github.com/ostinelli/net-http2/issues/7:
@kazuho any recommendations for how to implement this, based on your experience with h2o?