-
Notifications
You must be signed in to change notification settings - Fork 23
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
Add finalizer to ZStream and make TranscodingStreams.finalize
a noop
#97
base: master
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #97 +/- ##
==========================================
+ Coverage 83.51% 84.84% +1.33%
==========================================
Files 4 4
Lines 188 198 +10
==========================================
+ Hits 157 168 +11
+ Misses 31 30 -1 ☔ View full report in Codecov by Sentry. |
if f | ||
C_NULL | ||
else | ||
ccall(:jl_malloc, Ptr{Cvoid}, (Csize_t,), s%Csize_t) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason we use ccall
here rather than Libc.malloc
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is to make sure the memory being used is visible to the GC https://discourse.julialang.org/t/c-struct-garbage-collection-not-run-frequently-enough/116919/14
If I replace this with Libc.malloc
Julia uses more memory in the memory leak test, because GC isn't run frequently enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is some reference within the C code suggesting that calloc
may be warranted here.
https://github.com/madler/zlib/blob/develop/deflate.c#L397
The comments here suggest that malloc may be an appropriate optimization on modern systems.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it looks like calloc
is only needed if pointers are 16 bits.
Per my Slack comment, consider adding and encouraging a |
Unlike with files, or GPU arrays, the only resource being used here is CPU memory, so unless I am missing something it is fine if the freeing is delayed until there is memory pressure. The issue with the |
There is a long thread about this by Jeff here: The point of the I think the use of the finalizer here is fine as a last resort measure, but we should not depend on it. Memory will appear to leak until something actually activates the GC, which may be never. |
@@ -99,6 +118,11 @@ function inflate_end!(zstream::ZStream) | |||
return ccall((:inflateEnd, libz), Cint, (Ref{ZStream},), zstream) | |||
end | |||
|
|||
function decompress_finalizer!(zstream::ZStream) | |||
inflate_end!(zstream) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if this gets called twice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nothing.
InflateEnd
starts by checking if zstream.state
is null and returns Z_STREAM_ERROR
if it is https://github.com/madler/zlib/blob/8a844d434f0eef87d972ae6406b00968f7c52944/inflate.c#L1268-L1269
InflateEnd
also ends by setting zstream.state
to null. https://github.com/madler/zlib/blob/8a844d434f0eef87d972ae6406b00968f7c52944/inflate.c#L1273
The same happens in DeflateEnd
https://github.com/madler/zlib/blob/8a844d434f0eef87d972ae6406b00968f7c52944/deflate.c#L1266-L1283
Also, since the ZStream
constructor initializes .state
to C_NULL
, the finalizer can run on uninitialized streams without doing anything.
ccall(:jl_malloc, Ptr{Cvoid}, (Csize_t,), s%Csize_t) | ||
end | ||
end | ||
zfree(::Ptr{Cvoid}, p::Ptr{Cvoid}) = ccall(:jl_free, Cvoid, (Ptr{Cvoid},), p) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since zfree
is just a pass through to jl_free
perhaps you should just provide the function pointer directly:
cglobal(:jl_free)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They have different signatures. zfree
takes a pointer to an allocator context and a pointer to free, while jl_free
just takes a pointer to free.
IIUC, the problem in JuliaLang/julia#11207 that is relevant here is that finalizers can slow down when there are many different objects with finalizers. I can try and benchmark with ZipArchives.jl where I create new compressors for each new file that needs to be compressed. When this PR is merged, I can safely reuse the compressor if the new file has the same compression level as the previous file without worrying about memory leaks or use after free, so I will benchmark that as well. GC never running sounds like a good thing, no need to waste cycles freeing memory if a process is short-lived and never uses much memory. |
Fixes #48 and fixes #88 by porting JuliaIO/CodecBzip2.jl#43 to this package.
According to https://www.zlib.net/manual.html#Basic
So, there shouldn't be any thread safety issues with the finalizer here.