-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
IOBuffer/fileIO Memory leak with Threads.@spawn #49545
Comments
Doesn't look like a memory leak to me. The while true seems like an application error explicitly referencing all of the accessed memory (commonly known as a fork bomb) |
So in my real code this executes much slower ofcourse, but it still keeps accumulating memory that never gets garbage collected. What would be the fix? EDIT: I mean even if I stop the loop after a while and run it again or something else, or try manually triggering the GC, the memory does not get cleaned up. In my real world application over the course of a week the process gets to 25gb occupied. In that one there are no EDIT2: see logging.jl for the real code |
I have tested the memory consumption with the following code, to also not to do the fork bombing, i.e. making it closer to my real world example: function meminfo_julia!(d)
push!(d["GC Live"], Base.gc_live_bytes()/2^20)
push!(d["Max. RSS"], Sys.maxrss()/2^20)
end
function meminfo_procfs(d)
pid=getpid()
smaps = "/proc/$pid/smaps_rollup"
if !isfile(smaps)
error("`$smaps` not found. Maybe you are using an OS without procfs support or with an old kernel.")
end
rss = pss = shared = private = 0
for line in eachline(smaps)
s = split(line)
if s[1] == "Rss:"
rss += parse(Int64, s[2])
elseif s[1] == "Pss:"
pss += parse(Int64, s[2])
end
end
push!(d["RSS"], rss/2^10)
push!(d["PSS"], pss/2^10)
end
function explode(buffer = IOBuffer())
lck = ReentrantLock()
path = "test.log"
@sync for _ in 1:1000000
Threads.@spawn begin
lock(lck) do
println(buffer, "blabla")
open(path, append=true) do f
write(f, take!(buffer))
end
end
end
end
end
memdict = Dict("GC Live" => Float64[],
"Max. RSS" => Float64[],
"RSS" => Float64[],
"PSS" => Float64[])
t = Threads.@spawn let
buffer = IOBuffer()
for i = 1:200
explode(buffer)
meminfo_julia!(memdict)
meminfo_procfs(memdict)
end
end If I plot the results I get: While it seems like it stabilizes, over time it keeps trending upwards and I think this is exactly what's causing the long term leak. Would you have any idea what might be causing this trend? |
I ran that on master, it it worked fine for me. Stayed constant at about 500-600MB RSS in htop. |
Ok, if I run it with EDIT: on a freshly compiled master |
I just tested it on 1.8.5, there this particular issue is not present. It is on 1.9-rc4 and master Edit: i meant 1.9.0-rc2 |
On linux I wasn't able to reproduce this but on macos I do see the resident set grow and grow until we start swapping. But calling GC.gc() does make it go back down significantly. Actually I can reproduce on linux I just need more threads 😕 @d-netto this might be an interesting benchmark for heuristics. |
So I must say i was quite on the wrong path in the beginning thinking it was just iobuffers, now i think it's about how Strings are allocated during locked (?) Conversion from UInt8 vectors... It's also very weird that it only shows up when the number of threads is bigger than 2, I'm not sure if that correlates with the range of the loop. I have ran everything on Linux, i verified with some of my colleagues who are running 1.8.x (on Linux) and there it seems to not happen. |
Tested it again today on julia v1.9.0-rc3 and it's still present. |
You could try a bisect to see what commit introduced the problem. |
Oh, you're right I'll have a try. I also tried the Profile.take_heap_snapshot() after seeing your workflow video, but it doesn't seem very illuminating (can still supply it if helpful) |
Success! The bisect points at commit 80346c1 as the first bad one. |
what's the perspective on fixing this? |
This PR implements GC heuristics based on the amount of pages allocated instead of live objects like was done before. The heuristic for new heap target is based on https://dl.acm.org/doi/10.1145/3563323 (in summary it argues that the heap target should have square root behaviour). From my testing this fixes #49545 and #49761
This PR implements GC heuristics based on the amount of pages allocated instead of live objects like was done before. The heuristic for new heap target is based on https://dl.acm.org/doi/10.1145/3563323 (in summary it argues that the heap target should have square root behaviour). From my testing this fixes #49545 and #49761 (cherry picked from commit 32aa29f)
This PR implements GC heuristics based on the amount of pages allocated instead of live objects like was done before. The heuristic for new heap target is based on https://dl.acm.org/doi/10.1145/3563323 (in summary it argues that the heap target should have square root behaviour). From my testing this fixes JuliaLang#49545 and JuliaLang#49761
I managed to isolate a memory leak that caused runaway memory usage for long running processes due to some logging I was doing.
MWE:
versioninfo():
Compiled from source. Also tested on downloaded binaries of julia
1.8.2
,1.6.7
The text was updated successfully, but these errors were encountered: