[ruby-core:69113] [Ruby trunk - Bug #11088] Infinite loop on calling missing/overwritten methods of restored marshaled objects

From: halostatue@...
Date: 2015-05-10 02:45:47 UTC
List: ruby-core #69113
Issue #11088 has been updated by Austin Ziegler.

File simple-inspect.txt added
File simple-inspect-stats.txt added
File bug_hunt_benchmark.rb added
File bug_hunt_simple.rb added

The data structure in question is large and has some slightly pathological recursiveness. There are 3,204 objects marshalled. Most of these (3,088) are referenced exactly once. Of the remaining:

* 52 appear between 2x and 9x.
* 17 appear between 20x and 29x.
* 13 appear between 30x and 39x.
* 6 appear between 40x and 49x.
* 16 appear between 50x and 59x.
* 9 appear between 60x and 69x.
* 1 appears 71x.
* 1 appears 88x.
* 1 appears 89x.

I’m attaching `bug_hunt_simple.rb` (based on `SimpleInspect` below), `simple-inspect.txt` (the output of a simplified inspect), and `simple-inspect-stats.txt` (the count of appearances).

What’s interesting to me is that there’s an *observable* slowdown in the output of the `NoMethodError` result, so I benchmarked it (`bug_hunt_benchmark.rb`). There’s a consistent 0.1–0.2 second slowdown over 100,000 iterations, and the output of the exception message differs between the two forms (at the bottom of the file). (The slowdown is ~0.6s/100k on ruby 2.0 and 2.1, so there is that.)

> Note: these are the values where the inspect strings are constructed. If I remove the custom string construction and minimize the interaction with `$__inspected__` and its helper methods, there is *still* a consistent 0.05–0.1s/100k slowdown.

So there’s something about looking at an exception that triggers an expensive `#inspect`—but then discards it for a cheap `#inspect` in some circumstances. Without the `SimpleInspect` module (which is mostly useless because it keeps program-wide state, but is useful for this investigation), somehow the recursion detection of `#inspect` has been blown to bits in some recent version of Ruby (the problem shows up for ruby 2.0.0p481, ruby 2.1.6p336, ruby 2.2.2p95 and ruby 2.3.0dev (2015-04-11), which are the only Rubies I have on my Mac right now).

```ruby
module SimpleInspect
  def inspect
    result = if inspected?
               "#<%s (%d) ...>" % [ inspected_key, inspected_count ]
             else
               super
             end
    inspect!
    "\n" + result
  end

  private
  def inspected?
    inspected[inspected_key].nonzero?
  end

  def inspect!
    inspected[inspected_key] += 1
  end

  def inspected_count
    inspected[inspected_key]
  end

  def inspected
    $__inspected__ ||= Hash.new { |h, k| h[k] = 0 }
  end

  def inspected_key
    "%s:0x%014x" % [ self.class, "0x%014x" % (object_id * 2) ]
  end
end

class Object
  include SimpleInspect
end
```

----------------------------------------
Bug #11088: Infinite loop on calling missing/overwritten methods of restored marshaled objects
https://bugs.ruby-lang.org/issues/11088#change-52359

* Author: Jürgen Bickert
* Status: Open
* Priority: Normal
* Assignee: 
* ruby -v: ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
* Backport: 2.0.0: UNKNOWN, 2.1: UNKNOWN, 2.2: UNKNOWN
----------------------------------------
I have Marshal.dump some objects and then I Marshal.load them and later I call non-existent methods on those objects and instead of raising an exception (NoMethodError) it runs off in an infinite loop.

I have tested with simple cases where the dumped structure is not recursive and it works fine. So I attached a non-working dump which will when called with inspect or a non-existing method run off in an infinite loop.

When you run "ruby bug_hunt.rb" it will get stuck and you have to abort(CTRL-C) and only then will it print an error message and finish.

---Files--------------------------------
bug_hunt.rb (336 Bytes)
ruby_object.dump (561 KB)
11088_test.rb (305 Bytes)
simple-inspect.txt (1.19 MB)
simple-inspect-stats.txt (90.7 KB)
bug_hunt_benchmark.rb (1.42 KB)
bug_hunt_simple.rb (1.09 KB)


-- 
https://bugs.ruby-lang.org/

In This Thread

Prev Next