[#114348] [Ruby master Feature#19832] Method#destructive?, UnboundMethod#destructive? — "sawa (Tsuyoshi Sawada) via ruby-core" <ruby-core@...>

Issue #19832 has been reported by sawa (Tsuyoshi Sawada).

15 messages 2023/08/06

[#114365] [Ruby master Bug#19834] Segmentation fault while running in docker — "ramachandran@... (Ramachandran A) via ruby-core" <ruby-core@...>

Issue #19834 has been reported by ramachandran@mallow-tech.com (Ramachandran A).

7 messages 2023/08/09

[#114380] [Ruby master Bug#19837] Concurrent calls to Process.waitpid2 misbehave on Ruby 3.1 & 3.2 — "kjtsanaktsidis (KJ Tsanaktsidis) via ruby-core" <ruby-core@...>

Issue #19837 has been reported by kjtsanaktsidis (KJ Tsanaktsidis).

7 messages 2023/08/11

[#114399] [Ruby master Feature#19839] Need a method to check if two ranges overlap — "shouichi (Shouichi KAMIYA) via ruby-core" <ruby-core@...>

Issue #19839 has been reported by shouichi (Shouichi KAMIYA).

27 messages 2023/08/18

[#114410] [Ruby master Bug#19841] Marshal.dump stack overflow with recursive Time — "segiddins (Samuel Giddins) via ruby-core" <ruby-core@...>

Issue #19841 has been reported by segiddins (Samuel Giddins).

9 messages 2023/08/18

[#114422] [Ruby master Feature#19842] Intorduce M:N threads — "ko1 (Koichi Sasada) via ruby-core" <ruby-core@...>

Issue #19842 has been reported by ko1 (Koichi Sasada).

30 messages 2023/08/21

[#114590] [Ruby master Bug#19857] Eval coverage is reset after each `eval`. — "ioquatix (Samuel Williams) via ruby-core" <ruby-core@...>

Issue #19857 has been reported by ioquatix (Samuel Williams).

21 messages 2023/08/30

[ruby-core:114463] [Ruby master Bug#19436] Call Cache for singleton methods can lead to "memory leaks"

From: cedricm via ruby-core <ruby-core@...>
Date: 2023-08-23 13:48:43 UTC
List: ruby-core #114463
Issue #19436 has been updated by cedricm (C=E9dric Ma=EFon).





Hi,



Will this be backported to 3.2?

I'm currently seeing this in production on a large Rails app: when YJIT is =
enabled, after usual initial warmup, unicorn workers memory usage continue =
to grows linearly until reaching a significant amount of RAM -- at which po=
int they are either killed by a monitoring tool due to unreasonable memory =
usage (>650MB), or some cache gets flushed and a big chunk of memory gets r=
eleased (and then it goes back for another round).

Memory usage is perfectly stable without YJIT (~420MB).

I believe that this is linked to this bug.



Cheers



----------------------------------------

Bug #19436: Call Cache for singleton methods can lead to "memory leaks"

https://bugs.ruby-lang.org/issues/19436#change-104229



* Author: byroot (Jean Boussier)

* Status: Closed

* Priority: Normal

* Assignee: ko1 (Koichi Sasada)

* Target version: 3.3

* Backport: 2.7: UNKNOWN, 3.0: UNKNOWN, 3.1: UNKNOWN, 3.2: UNKNOWN

----------------------------------------

Using "memory leaks" with quotes, because strictly speaking the memory isn'=
t leaked, but it can nonetheless lead to large memory overheads.



### Minimal Reproduction



```ruby

module Foo

  def bar

  end

end



def call_bar(obj)

  # Here the call cache we'll keep a ref on the method_entry

  # which then keep a ref on the singleton_class, making that

  # instance immortal until the method is called again with

  # another instance.



  # The reference chain is IMEMO(callcache) -> IMEMO(ment) -> ICLASS -> CLA=
SS(singleton) -> OBJECT

  obj.bar

end



obj =3D Object.new

obj.extend(Foo)



call_bar(obj)



id =3D obj.object_id

obj =3D nil



4.times { GC.start }

p ObjectSpace._id2ref(id)

```



### Explanation



Call caches keep a strong reference onto the "callable method entry" (CME),=
 which itself keeps a strong reference on the called object class

and in the cache of a singleton class, it keeps a strong reference onto the=
 `attached_object` (instance).



This means that any call site that calls a singleton method, will effective=
ly keep a strong reference onto the last receiver.

If the method is frequently called it's not too bad, but if it's infrequent=
ly called, it's effectively a (bounded) memory leak.

And if the `attached_object` is big, the wasted memory can be very substant=
ial.



### Practical Implications



Once relative common API impacted by this is [Rails' `extending` API](https=
://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-exte=
nding). This API allow to extend a "query result set" with a module.

These query results set can sometimes be very big, especially since they ke=
ep references to the instantiated `ActiveRecord::Base` instances etc.



### Possible Solutions



#### Only keep a weak reference to the CME



The fairly "obvious" solution is to keep a weak reference to the CME, that'=
s what I explored in https://github.com/ruby/ruby/pull/7272, and it seems t=
o work.

However in debug mode It does fail on an assertion during compaction, but i=
t's isn't quite clear to me what the impact is.



Additionally, something that makes me think this would be the right solutio=
n, is that call caches already try to avoid marking the class:



```c

# vm_callinfo.h:275



struct rb_callcache {

    const VALUE flags;



    /* inline cache: key */

    const VALUE klass; // should not mark it because klass can not be free'd

                       // because of this marking. When klass is collected,

                       // cc will be cleared (cc->klass =3D 0) at vm_ccs_fr=
ee().

```



So it appears that the class being also marked through the CME is some kind=
 of oversight?



#### Don't cache based on some heuristics



If the above isn't possible or too complicated, an alternative would be to =
not cache CMEs found in singleton classes, except if it's the the singleton=
 class of a `Class` or `Module`.



It would make repeated calls to such methods slower, but the assumption is =
that it's unlikely that these CME would live very long.



#### Make `Class#attached_object` a weak reference



Alternatively we could make the `attached_object` a weak reference, which w=
ould drastically limit the amount of memory that may be leaked in such scen=
ario.



The downside is that `Class#attached_object` was very recently exposed in R=
uby 3.2.0, so it means changing its semantic a bit.





cc @peterzhu2118 @ko1







--=20

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

 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-c=
ore.ml.ruby-lang.org/

In This Thread