-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Severe memory leaking on Ruby 2.2 #785
Comments
Sorry for the slow response. I've been out of town at RailsConf. I took some time to reproduce this issue and to track the root cause. I'll try to update with more information tomorrow, but I have a good idea of the root cause for the leak and some possible work-arounds. |
It appears that StringIO is the culprit from Ruby stdlib. The SDK expects that the http handler should write the http response body to an IO object. The default response target is a StringIO object. The following snippet when run from a script will demonstrate the leak. require 'stringio'
def report
GC.start
memory = `ps -o rss,vsz -p #{Process.pid} | tail +2`.strip
leaks = `leaks #{Process.pid} | grep -c Leak`.strip
puts "Memory: #{memory}; Leaks: #{leaks}; Heap: #{GC.stat[:heap_live_slots]}"
end
def leak(data)
io = StringIO.new
io.write(data)
end
def no_leak(data)
StringIO.new(data)
end
data = '.' * 1024 * 1024 * 10 # 10MB data
20.times do
leak(data)
report
end By removing the |
Wow, thanks for the update! Glad you have figured out where the leak is. I've tested this against ruby 2.1.5, and there are no leaks. On ruby 2.2.0 and 2.2.1 there is severe leaking. That explains why the memory went wild after upgrading from 2.1.5 (which suffered its own memory issues) to 2.2.0 (which suffered even more :) I've changed this line: Into this: @body = StringIO.new(body_contents + chunk) And the leaking went away. I know that this is stupid and that its ruby responsibility to provide proper fix, but it will be probably months before ruby is fixed. Do you think I can safely monkeypatch locally aws-sdk until then? Do you see any problems with the patch above? I have searched through codebase to find a place where custom :body IO is passed to response, but I don't think there is. Thank you so much for your help! |
The only places a custom body is passed into response are:
I was working on a drop-in replacement for StringIO. Here is what I have so-far. It implements all of the public interfaces of StringIO required by the SDK. It needs additional testing, but should be functional: class CustomIO
def initialize(data = '')
@data = data
@offset = 0
end
def write(data)
@data << data
data.bytesize
end
def read(bytes = nil, output_buffer = nil)
if bytes
data = partial_read(bytes)
else
data = full_read
end
output_buffer ? output_buffer.replace(data || '') : data
end
def rewind
@offset = 0
end
def truncate(bytes)
@data = @data[0,bytes]
bytes
end
private
def partial_read(bytes)
if @offset >= @data.bytesize
nil
else
data = @data[@offset,@offset+bytes]
bump_offset(bytes)
data
end
end
def full_read
data = @offset == 0 ? @data : @data[@offset,-1]
@offset = @data.bytesize
data
end
def bump_offset(bytes)
@offset = [@data.bytesize, @offset + bytes].min
end
end |
I was putting together a bug report for Ruby and found a related issue already opened and then resolved: https://bugs.ruby-lang.org/issues/10942 It was closed as fixed 7 days ago. I don't know when this will become available, but the fact the same issue was reported and then fixed is good news. |
Awesome. So I can just put this in my code and it will all work without memory leaks: module Seahorse
class StringIO
def initialize(data = '')
@data = data
@offset = 0
end
def write(data)
@data << data
data.bytesize
end
def read(bytes = nil, output_buffer = nil)
if bytes
data = partial_read(bytes)
else
data = full_read
end
output_buffer ? output_buffer.replace(data || '') : data
end
def rewind
@offset = 0
end
def truncate(bytes)
@data = @data[0,bytes]
bytes
end
private
def partial_read(bytes)
if @offset >= @data.bytesize
nil
else
data = @data[@offset,@offset+bytes]
bump_offset(bytes)
data
end
end
def full_read
data = @offset == 0 ? @data : @data[@offset,-1]
@offset = @data.bytesize
data
end
def bump_offset(bytes)
@offset = [@data.bytesize, @offset + bytes].min
end
end
end Once the patch is applied to ruby, I can safely remove this. Thank you for your help! |
I've created a gem that can be included in project and which solves memory issues with aws until ruby 2.2.3 is released: https://rubygems.org/gems/aws_memfix |
In the process of developing a gem, I've tried with SimpleDelegator implementation of StringIO. It turned out that all it takes to avoid memory leaks is this: require "delegate"
module Seahorse
class StringIO < SimpleDelegator
def initialize(data = '')
@io = ::StringIO.new(data)
super(@io)
end
end
end Have no clue why, but it works.. |
Memory leak (?). See aws/aws-sdk-ruby#785
hi guys! do you know if patch is still needed with sdk-3 and Ruby 2.2, or if is need to upgrade Ruby to avoid this issue? |
I have upgraded to aws-sdk v2, and at the same time I have upgraded to ruby 2.2. All of my processes suffer memory bloats so severe that I have to restart them every hour to make things running.
My application is processing data at a large scale, so processes issue tens of aws requests per second. I have used mac "leaks" utility to diagnose what causes memory leaks. As the process time goes, this utility find tens of thousands of memory leaks. The interesting part is that 99% of them is related to aws-sdk. Here is a sample output of:
leaks PID | grep "Leak"
It feels like every AWS response is leaking memory, and I have thousands of them..
Using:
Test script:
Test output:
The text was updated successfully, but these errors were encountered: