Dynamo geometry stability improvements / request for feedback

Hello Dynamo users and Dynamo package developers,

We’ve been working for a while on improving the stability of the Dynamo geometry library. A major stumbling block for developing stable Dynamo packages - both internal to Autodesk and for external node and package authors is the requirement that memory be managed manually when using Dynamo Geometry types from python and c# .

see below:

The dreaded system access violation or mysterious hang and crash of Revit/Dynamo!

A small snippet that reproduces the issue in python is:

for i in range(100000):
    x = Cuboid.ByLengths(0,0,0)

OUT = "some string"

If you are not familiar with garbage collection - the idea here is that the runtime (in this case .net) detects that the cubes have gone out of scope, and cannot be referenced from anywhere else in your code, including the Dynamo graph - because they have not been returned - and tries to clean their memory up.

Unfortunately, this causes issues with the Dynamo geometry library, and as advised in the link above, should be avoided by manually calling dispose() on geometry that is not returned or referenced.

This is a pretty big burden when writing exploratory code, and hard to get right.

Well, I have some good news.

In the latest daily builds of Dynamo master ( DynamoCoreRuntime_2.5.0.5845 ) - we have removed the requirement for you to manually manage memory for most use cases.

Yay!

But what’s the catch?

There are some complex use cases that still require manual disposal, and during our investigation into this issue we believe we have identified some good rules to follow:

  • If you are using the Dynamo Geometry library namespaces ( Autodesk.DesignScript.Geometry - ie ProtoGeometry.dll ) - from c# or python and you are not spinning up your own threads, or using classes which create threads (Task, Thread, Parallel etc) - then you should be safe to no longer manually dispose geometry which you do not return - Dynamo will do it for you safely.

  • If you are using Autodesk.DesignScript.Geometry classes, and you are using them in a multi-threaded way - you are still responsible for disposing of that geometry which you do not return into the graph or hold onto in your classes. You must do this on the same threads which you created the geometry!

  • The multi-threaded safety of LibG has not been satisfactorily tested, and we have never guaranteed its thread safety, and are not doing so now.

  • You are still free to manually dispose geometry if you want to and have determined it is the best option for your use case. This means, you don’t need to update your packages to take advantage of this fix! If you are writing a library for others to use, manually disposing is still a good practice.

What do we need from you?

We need some help testing this change, as it is a fundamental change to the geometry library. If you have a graph using packages, zero touch nodes, or python nodes that create geometry - and you have seen the crashing behavior described above as you run this graph on larger sets of geometry, we would love to hear your feedback after you try out the latest daily build.

You’ll need to run these tests in sandbox, so no Revit interactions of course (unless you want to move some dlls around)

Please check out these changes in the latest daily build and let us know what you think or if you find any improvements or strange behavior. We are still internally profiling and testing this change, but wanted to get it out for feedback now.

15 Likes

Dumb question perhaps… but what do I do with the zip?

it will have a DynamoSandbox.exe in it -
checkout this page:

Can I run it alongside the standard install?

Yes indeed! When you unzip in a location of your choice Sandbox will only touch that location. It won’t mess with any other install, nor your registry :slight_smile:

2 Likes

Testing with the most recent daily build (DynamoCoreRuntime_2.5.0.6687_20191031T1721), and I’m still seeing the same garbage build-up. It’s a slightly smaller amount per run than the most recent Stable build (300mb vs 500mb), but it’s still accumulating with each successive run.

Graph has a single Python node, I’m only importing Point, Vector, Plane, and Polygon types from ProtoGeometry. The script is running a brute-force packing algorithm, progressively rotating one polygon to evaluate the domain of rotation where it is fully contained within an larger polygon.

DM me on ADSK slack and I can share the DYN file.

3 Likes

I might have an interesting case regarding this issue! @Michael_Kirschner2

I’m not yet a master in Python or C#, so in Dynamo I used Design Script to build a universal loop. For every index of a list it applies different (custom) nodes (create geometry), then saves the file, then removes the geometry. With the generation of complex geometry and/or a big list of input-values the script runs for a while and starts building up memory. This continues up to crashing Dynamo.

Is there anyway to dispose of the old memory-usage, while continuing the generation of models. Maybe by adding an extra node/python script to the loop?

image

the DS garbage collector is not going to get invoked until after the graph run - so your DS code will build up lots of memory. You can try calling dispose on those objects to see if that helps - it may give you an exception at the end of the graph run though as theres the potential for dispose to get called again by the GC… give it a shot, I think it will work.

Otherwise move to c# or python and call dispose manually as soon as your are done with the geometry.

Thanks for the quick reply @Michael_Kirschner2 !

I can get a seperate list going within every node for every geometry I create. Is het enough to add a python script at the end of the loop with the code below and input all the created geometry?

import clr
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import*
clr.AddReference('RevitServices')
import RevitServices


elems = UnwrapElement(IN[0])

del elems

OUT = "Succes"

Hello Michael and others!

Feel like I’m almost there :smiley:

I found the Scripting Reference in the Dynamo Primer.
https://primer.dynamobim.org/13_Best-Practice/13-3_Scripting-Reference.html

I tried the following:

My first list is being deleted by a custom node ‘funcdel’ and consists only of Revit elements:


My second list is being iterated over the 'item.Dispose() and consists of Geometry like Points, Surfaces, etc.

It returns the following:
image

item.Dispose(Point) returns a ‘null’ and doesn’t remove anything (I can see all Geometry in Revit and Dynamo).

The Designscript does the following:
1 - f1 -> Places Revit Elements (returning list[0] elements and list[1] geometry used)
2 - fsave -> Saves file
3 - fdel -> Deletes f1[elements]
4 - fdispose -> Disposes f1[geometry]

def defLoopPara(var1:var[]..[],func1:Function,funcsave:Function,funcdel:Function){

c = [Imperative]{

f1 = [];
fsave = [];
fdel = [];
fdispose = [];

	for(i in 0..(List.Count(var1)-1)){
		f1[i] = __Apply(func1, var1[i]);
		fsave[i] = __Apply(funcsave, var1[i]);
		if(i<(List.Count(var1)-1))
		fdel[i] = __Apply(funcdel, f1[i][0]);
		for(j in 0..(List.Count(f1[i][1]))){
			item = f1[i][1][j];
			fdispose[i][j] = item.Dispose;
		}
	}
	return [f1,fsave,fdel,fdispose];
}
return = c;

Why doenst the item.Dispose() dispose of the Geometry?

Hope that there is anyone that can help!