diff --git a/lib/memory/cache.rb b/lib/memory/cache.rb index b496952..c9209c7 100644 --- a/lib/memory/cache.rb +++ b/lib/memory/cache.rb @@ -56,14 +56,22 @@ def lookup_class_name(klass) end # Look up and cache a string value. + # + # This maps the original string to a shortened representation. + # # Strings are truncated to 64 characters to reduce memory usage. # @parameter obj [String] The string object to cache. # @returns [String] A cached copy of the string (truncated to 64 characters). def lookup_string(obj) - # This string is shortened to 200 characters which is what the string report shows - # The string report can still list unique strings longer than 200 characters - # separately because the object_id of the shortened string will be different + # This string is shortened to 64 characters which is what the string report shows. The string report can still list unique strings longer than 64 characters separately because the object_id of the shortened string will be different. @string_cache[obj] ||= String.new << obj[0, 64] + rescue RuntimeError => error + # It is possible for the String to be temporarily locked from another Fiber which raises an error when we try to use it as a hash key. i.e: `Socket#read` locks a buffer string while reading data into it. In this case we `#dup`` the string to get an unlocked copy. + if error.message == "can't modify string; temporarily locked" + @string_cache[obj.dup] ||= String.new << obj[0, 64] + else + raise + end end end end diff --git a/memory.gemspec b/memory.gemspec index a9636b5..1764ce1 100644 --- a/memory.gemspec +++ b/memory.gemspec @@ -7,7 +7,7 @@ Gem::Specification.new do |spec| spec.version = Memory::VERSION spec.summary = "Memory profiling routines for Ruby 2.3+" - spec.authors = ["Sam Saffron", "Dave Gynn", "Samuel Williams", "Nick LaMuro", "Jonas Peschla", "Ashwin Maroli", "Søren Skovsbøll", "Richard Schneeman", "Anton Davydov", "Benoit Tigeot", "Jean Boussier", "Vincent Woo", "Andrew Grimm", "Boris Staal", "Danny Ben Shitrit", "Espartaco Palma", "Florian Schwab", "Hamdi Akoğuz", "Jaiden Mispy", "John Bachir", "Luís Ferreira", "Mike Subelsky", "Olle Jonsson", "Vasily Kolesnikov", "William Tabi"] + spec.authors = ["Sam Saffron", "Dave Gynn", "Samuel Williams", "Nick LaMuro", "Jonas Peschla", "Ashwin Maroli", "Søren Skovsbøll", "Richard Schneeman", "Anton Davydov", "Benoit Tigeot", "Jean Boussier", "Vincent Woo", "Andrew Grimm", "Boris Staal", "Danny Ben Shitrit", "Espartaco Palma", "Florian Schwab", "Hamdi Akoğuz", "Jaiden Mispy", "John Bachir", "Luís Ferreira", "Mike Subelsky", "Olle Jonsson", "Vasily Kolesnikov", "William Tabi", "Michael Go"] spec.license = "MIT" spec.cert_chain = ["release.cert"] diff --git a/test/memory/sampler.rb b/test/memory/sampler.rb index cba7dce..8844729 100644 --- a/test/memory/sampler.rb +++ b/test/memory/sampler.rb @@ -39,6 +39,26 @@ class MyThing expect(allocation.retained).to be_truthy end + it "safely captures locked string objects" do + fiber = Fiber.new do |string| + IO::Buffer.for(string) do + # string is now locked for the duration of this block. + sleep(0.1) + Fiber.yield + end + end + + memory = Memory::Sampler.new + memory.start + + # Lock the string + key = String.new("foo") + fiber.resume(key) + + memory.stop + memory.report # buffer string is locked while reading ObjectSpace#each_object + end + with "#as_json" do it "returns allocation count" do x = nil