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
Capture Memory Leak #3174
Comments
Here's a single line simplified case which leaks about 50MB per F5 preview.
I've also built OpenSCAD using LLVM's LeakSanitizer and made this log of the results (scroll down to the first "Indirect leak" to see the more significant leaks): The build was basically created using clang and enabling |
From some discussion on IRC, its suspected that the issue relates to But I still haven't pinpointed the exact cause yet. |
The cause of this appears to be that in value.h, class FunctionType for function literals stores a shared_ptr<Context>. Context, defined in context.h, contains a std::unordered_map<std::string, ValuePtr> (where ValuePtr in value.h is derived from shared_ptr) of every variable, including the function literal itself. This produces a direct shared_ptr loop which preserves the variables in the context of the function literal until program termination. I was not able to spot a simple resolution. It appears to be a complicated side-effect of the closure nature of function literals requiring their captured contexts to stick around as long as the function literal does, combined with the extremely varied ways that function literals can be returned past the lifetime of the function. Because multiple function literals in the same context can call each other, and multiple function literals can be returned (as the function literals can reside inside of vectors), it seems to be a complicated issue of redefining ownership for this data in some more sensible manner. If anyone has ideas of how to do this, please speak up or help. |
After the recent shared_ptr cleanup, we may be able to more strongly define object ownership and use weak_ptr for non-owning references to such objects. |
I can confirm that there is a shared_ptr cycle between captured contexts. But I don't think a system with non-owning context references can fix this on its own. Consider the following example:
This creates two mutually dependent contexts: Most languages solve this problem with a reachability-analyzing garbage collector. Strictly immutable languages can often avoid this because they can avoid reference cycles in the first place, but openscad it not such a language; contexts can be added to after a reference to the context is captured (the example above relies on this), which makes contexts mutable for all memory-management-related intents and purposes. I can think of a couple of solutions to this bug that use some form of reference tracing, and a few that sidestep the issue entirely:
Looking at these options, I'd say the best solution is to bite the bullet and write that mark-and-sweep garbage collector. The predictable-memory-management-purity alternatives do not seem worth it. |
After fairly thorough discussion of these avenues and alternatives, I reluctantly concur with redlizard's above comment. A mark-and-sweep or similar garbage collector appears to be the only performant and scalable solution to this issue. |
I implemented this garbage collector, above. The essence is a mark and sweep garbage collection algorithm. It doesn't keep track of roots; instead, it keeps track of all existent contexts, and when performing a sweep, it computes which contexts only have inbound references from other contexts, and those that do not are considered roots. From there a standard reachability and deletion are done. Running time of a garbage collection run is proportional to the total size of all allocated values in the managed contexts. This consists of context variables, but also of elements of vectors in those context variables. Garbage is collected whenever this total size gets too big; to that end, vector values do some bookkeeping to keep track of this total size. Which is a bit messy, but necessary. |
Fixed by #3765. |
Capture from function literals leaks memory. Repeatedly previewing with the leaker line uncommented leaks 2GB more each preview. Having just the nonleaker line uncommented does not do this.
The text was updated successfully, but these errors were encountered: