Memory Leak: GC does not deallocate values allocated while it was turned off, unless specifically requested

Hi, I’m considering opening a bug report about this, so I’d like to get your guys opinion on this behavior.

The following is a memory leak:

x = 1234
while (true)
    GC.enable(false)
    x = rand()
    GC.enable(true)
    #GC.collect(GC.GC_AUTO)
end

This slowly fills up the RAM until the OS kills the process. The unused values of rand() are never deallocated.

With GC.collect commented in, this code behaves as expected. With none of the GC. calls, no memory leak occurrs too, of course.

Clearly the GC is still updating the roots, otherwise GC.collect would have no effect.

This same behavior is exhibited if implementing the above using the C-API. Tested in Julia 1.7.2, freshly downloaded. I’m on Ubuntu 20.04.2 LTS, 64-bit.

Thoughts? Should I open an issue?

yeah open an issue. I can confirm this behavior. It’s very odd.

I decided to open an issue: https://github.com/JuliaLang/julia/issues/45068

2 Likes

They closed the issue stating it is intended behavior which doesn’t make any sense to me, if this is intended then GC.enable is useless by itself, it potentially causes a memory leak and should never be used.

Furthermore, the following still leaks

x = 1234
while (true)
  GC.enable(false)
  x = rand()
  GC.enable(true)
  GC.safepoint()
end

So the only safe way to use GC.enable is to manually cause a collection afterwards, I don’t see how that is good design, users who do not know this could be leaking memory which is a fatal error that can cause any application to die. It’s one of the most serious bugs possible imo

I think it’s pretty pragmatic? Julia allocates a lot. It takes very serious effort to avoid any allocations whatsoever. Meanwhile, nothing prevents you from putting a GC.gc() call after GC.enable(true). Yes, the behaviour is a bit surprising, but

help?> GC.enable
  GC.enable(on::Bool)

  Control whether garbage collection is enabled using a boolean argument (true for
  enabled, false for disabled). Return previous GC state.

  │ Warning
  │
  │  Disabling garbage collection should be used only with caution, as it can
  │  cause memory use to grow without bound.

You’ve been warned?

1 Like

I think you somewhat misunderstood the answer that was given to you on Github.
GC.enable(true) is not useless, it turns on the GC checks for future collection points but it does not make the GC run by itself. The savepoint is for multithreading coordination and not relevant here. Your example leaks because you Instantly turn the GC back off before it even attempts to run. If you try to allocate anything between GC.enable(true) and the next disable, it should run as intended.

4 Likes

Automatic collection occurs when something tries to allocate and the system needs to free up some memory to give you. GC.enable(false) disables that; GC.enable(true) re-enables it. An allocation must occur while GC is enabled in order for collection to be triggered. Your code here only allocates while GC is disabled, so automatic collection never happens. Disabling collection is low-level, advanced functionality that should only be used with caution and some understanding of the system. In particular, it is on you to make sure you don’t construct a program where collection is never triggered. If you want to perform a collection when you re-enable GC, then you can trigger it manually by calling GC.gc().

12 Likes