API doc: info_gotcha: extend description of issues related to memory changes.
authorBastien Montagne <montagne29@wanadoo.fr>
Mon, 18 Feb 2019 13:31:42 +0000 (14:31 +0100)
committerBastien Montagne <montagne29@wanadoo.fr>
Mon, 18 Feb 2019 13:36:16 +0000 (14:36 +0100)
* Added a TL;DR first paragraph summarizing that one shall not keep any
reference to Blender data when modifying its container.

* Added some info about fact that adding items to some data containers
(like Collection) can also invalidate existing items (due to array
re-allocation).

* Added a Do/Don't example which shows a crash after adding some items
to a collection.

Related to T61297.

doc/python_api/rst/info_gotcha.rst

index c9e9e89..d9238e0 100644 (file)
@@ -604,12 +604,17 @@ so until its properly supported, best not make use of this.
 Help! My script crashes Blender
 ===============================
 
+**TL;DR:** Do not keep direct references to Blender data (of any kind) when modifying the container
+of that data, and/or when some undo/redo may happen (e.g. during modal operators execution...).
+Instead, use indices (or other data always stored by value in Python, like string keys...),
+that allow you to get access to the desired data.
+
 Ideally it would be impossible to crash Blender from Python
 however there are some problems with the API where it can be made to crash.
 
 Strictly speaking this is a bug in the API but fixing it would mean adding memory verification
 on every access since most crashes are caused by the Python objects referencing Blenders memory directly,
-whenever the memory is freed, further Python access to it can crash the script.
+whenever the memory is freed or re-allocated, further Python access to it can crash the script.
 But fixing this would make the scripts run very slow,
 or writing a very different kind of API which doesn't reference the memory directly.
 
@@ -619,10 +624,15 @@ Here are some general hints to avoid running into these problems.
   especially when working with large lists since Blender can crash simply by running out of memory.
 - Many hard to fix crashes end up being because of referencing freed data,
   when removing data be sure not to hold any references to it.
+- Re-allocation can lead to the same issues
+  (e.g. if you add a lot of items to some Collection,
+  this can lead to re-allocating the underlying container's memory,
+  invalidating all previous references to existing items).
 - Modules or classes that remain active while Blender is used,
   should not hold references to data the user may remove, instead,
   fetch data from the context each time the script is activated.
 - Crashes may not happen every time, they may happen more on some configurations/operating-systems.
+- Be wary of recursive patterns, those are very efficient at hiding the issues described here.
 
 .. note::
 
@@ -632,6 +642,55 @@ Here are some general hints to avoid running into these problems.
    While the crash may be in Blenders C/C++ code,
    this can help a lot to track down the area of the script that causes the crash.
 
+.. note::
+
+   Some container modifications are actually safe, because they will never re-allocate existing data
+   (e.g. linked lists containers will never re-allocate existing items when adding or removing others).
+
+   But knowing which cases are safe and which aren't implies a deep understanding of Blender's internals.
+   That's why, unless you are willing to dive into the RNA C implementation, it's simpler to
+   always assume that data references will become invalid when modifying their containers,
+   in any possible way.
+
+
+**Don’t:**
+
+.. code-block:: python
+
+   class TestItems(bpy.types.PropertyGroup):
+       name: bpy.props.StringProperty()
+
+   bpy.utils.register_class(TestItems)
+   bpy.types.Scene.test_items = bpy.props.CollectionProperty(type=TestItems)
+
+   first_item = bpy.context.scene.test_items.add()
+   for i in range(100):
+       bpy.context.scene.test_items.add()
+
+   # This is likely to crash, as internal code may re-allocate
+   # the whole container (the collection) memory at some point.
+   first_item.name = "foobar"
+
+
+**Do:**
+
+.. code-block:: python
+
+   class TestItems(bpy.types.PropertyGroup):
+       name: bpy.props.StringProperty()
+
+   bpy.utils.register_class(TestItems)
+   bpy.types.Scene.test_items = bpy.props.CollectionProperty(type=TestItems)
+
+   first_item = bpy.context.scene.test_items.add()
+   for i in range(100):
+       bpy.context.scene.test_items.add()
+
+   # This is safe, we are getting again desired data *after*
+   # all modifications to its container are done.
+   first_item = bpy.context.scene.test_items[0]
+   first_item.name = "foobar"
+
 
 Undo/Redo
 ---------