Page MenuHome

ID user decrement error when removing object which is referred to with a PointerProperty
Closed, ResolvedPublicBUG

Description

System Information
Operating system: Windows 10 Home 1909 (64-bit)
Graphics card: NVidia GeForce GTX 1050 Ti

Blender Version
Broken: 2.90.0
Worked: Unknown

Short description of error

I have added a PointerProperty to the EditBone type. This PointerProperty refers to an object (which is separate from the armature).

When removing the separate object, it causes the following error in the console:

ERROR (bke.lib_id): C:\b\buildbot-worker-windows\windows_290\blender.git\source\blender\blenkernel\intern\lib_id.c:288 id_us_min: ID user decrement error:  (from '[Main]'): 0 <= 0

Eventually this causes Blender to crash with a Windows fatal exception: access violation error.

Exact steps for others to reproduce the error

Starting with a new General Blend file:

  1. Run the following code as a Text (it will automatically create an armature and panel):
import bpy

def make_all(context):
    bpy.ops.object.mode_set(mode='EDIT')

    armature = context.active_object

    for bone in armature.data.edit_bones:
        mesh = bpy.data.meshes.new(name="Test")
        object = bpy.data.objects.new("Test", mesh)
        context.collection.objects.link(object)
        bone.test = object


def remove_all(context):
    bpy.ops.object.mode_set(mode='EDIT')

    armature = context.active_object

    for bone in armature.data.edit_bones:
        if bone.test:
            bpy.data.objects.remove(bone.test)
            bone.test = None


def update(self, context):
    if self.enabled:
        print("Enabling")
        make_all(context)

    else:
        print("Disabling")
        remove_all(context)


class Panel(bpy.types.Panel):
    bl_idname = "DATA_PT_test"
    bl_label = "Test"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "Item"
    bl_options = set()

    @classmethod
    def poll(cls, context):
        return (context.active_object.type == 'ARMATURE')

    def draw(self, context):
        self.layout.prop(context.active_object.data, "enabled")


bpy.types.Armature.enabled = bpy.props.BoolProperty(name="Test enabled", default=False, update=update)
bpy.types.EditBone.test = bpy.props.PointerProperty(type=bpy.types.Object)

bpy.utils.register_class(Panel)

# Create new armature
bpy.ops.object.armature_add()
  1. Press the N key to open the sidebar.
  1. In the Item category, find the Test panel.
  1. Click the "Test enabled" checkbox 4 or more times. This triggers the ID user decrement error.

I have also attached a simple .blend file. You can simply open it and then click the "Test enabled" checkbox 4 or more times.

Event Timeline

Philipp Oeser (lichtwerk) changed the task status from Needs Triage to Confirmed.Oct 1 2020, 9:47 AM

Can confirm a crash, will check on this.

There is one more user here than expected.

Not sure if T77557: Python Operators that add/remove ID data without an undo step crash Blender is related? Needs more investigation.

Bastien Montagne (mont29) triaged this task as High priority.
Bastien Montagne (mont29) changed the subtype of this task from "Report" to "Bug".

This is actually a fairly serious issue. Root of it is that bone IDProperties are duplicated over two structures, Bone and EditBone.

@Brecht Van Lommel (brecht), @Sergey Sharybin (sergey), @Sybren A. Stüvel (sybren), EditBone is defined in ED_armature.h, so we cannot access it from BKE code that deals with ID refcounting (especially the foreach looper over ID usages from lib_query.c and the matching callback for Armature IDs). Do you see any reason not to have that one defined in BKE_armature.h instead?

Having it in BKE_armature.h seems fine to me.

BKE_armature.h already has a lot of pose-related functions. Maybe the EditBone definition could go into a new file BKE_armature_edit.h? That may keep things a bit better separated.

@Sybren A. Stüvel (sybren) It could at some point, but am not convinced it would help at all currently, that would be a new header file with that single struct into it, and then having to link it in many extra places… I would rather do that later, when/if we actually add or move some armature bone editing code into BKE area?

Ok, in that case BKE_armature.h is fine too.

This actually revealed a nest of bugs regarding ID management and IDProperties of bones… Fixes are now incoming.

@Pauan (Pauan) Please note that manipulating data-blocks from update callback as you do in your script is extremely discouraged, it will basically break undo/redo process, among other potential severe issues, see the API documentation .

@Bastien Montagne (mont29) Thanks!

  1. Then what is the idiomatic way to create/remove objects in response to a property changing?
  1. Are the problems only for creating new data blocks, or also manipulating properties on existing data blocks (e.g. changing Object.location)?

If update callbacks are so harmful, I think the docs should clearly show what the alternative is, rather than essentially telling the developer "you can't do that", which is not helpful.

(As a meta note, I've spent many hours reading through the addons documentation, StackOverflow, and the devtalk forums, but I still find it very hard to figure out how to do even very common things with the addon API. Some more usage examples would be great, though of course I know other things have higher priority.)

@Bastien Montagne (mont29) Thanks!

  1. Then what is the idiomatic way to create/remove objects in response to a property changing?

Idiomatic way to handle this case is simple: you don't.
In Blender, significant changes to data should happen through operators. Any other way to do it is at best a hack that might work within certain boundaries.

If you really, really want to react to a property value change, then either you run a modal operator and check said value on each modal run (which will happen within the main even loop). Or you use an application handler, but this might not work always very well either.

  1. Are the problems only for creating new data blocks, or also manipulating properties on existing data blocks (e.g. changing Object.location)?

Creating or deleting data in general is a critical 'don't' in properties callbacks, be it an ID or some sub-data structure. Changing other parameters is generally fine, though it should preferably be inside the same data-block, otherwise you may break undo/redo stack.

If update callbacks are so harmful, I think the docs should clearly show what the alternative is, rather than essentially telling the developer "you can't do that", which is not helpful.

(As a meta note, I've spent many hours reading through the addons documentation, StackOverflow, and the devtalk forums, but I still find it very hard to figure out how to do even very common things with the addon API. Some more usage examples would be great, though of course I know other things have higher priority.)

Best usages examples are official add-ons usually… Agree this is not as friendly as a complete comprehensive set of advanced examples (you do know about our 'templates' available in the Text editor of Blender, right? ) would be nice, but this is indeed quiet a lot of work to create and maintain, and we simply do not have the man power for such thing currently.

Pauan (Pauan) added a comment.EditedOct 7 2020, 10:01 AM

@Bastien Montagne (mont29) My add-on allows the user to create rigid body physics for bones:

https://github.com/Pauan/blender-rigid-body-bones

The user selects a bone, clicks on a checkbox to enable rigid body physics for that bone, and then changes various rigid body settings:

https://raw.githubusercontent.com/Pauan/blender-rigid-body-bones/master/Usage%2002.PNG

So when the checkbox is enabled, it *must* create a rigid body object, and when the checkbox is disabled, it *must* remove that object.

Having a checkbox to enable/disable things does not seem like an extreme case to me, it seems like a common thing to do. So it's very bizarre that you are saying that Blender does not support it.

And using a modal operator or app handler sounds terribly wasteful and bad performance, since it will run all the time, but I only need to run the code when the checkbox is changed.

It also sounds like an over-complication, since my use case is not complicated, and modal operators are clearly not intended for my use case.


If the problem is a lack of Operator, could I solve that by creating an Operator and then calling that Operator inside the update callback? That would be very easy for me to do.

If that doesn't work, then that is a failing in the Python API, and the Python API should be changed (or new APIs created). Perhaps an API that says, "run this callback on the next update tick, during a time where it is safe to create objects".

If you do not provide a correct API, people will instead abuse the current APIs. You cannot simply tell people "don't do that", because their use cases are legitimate and their goals are correct (even if their methods aren't), so they *will* do it no matter how much you say no. So if you don't want them using a bad API, you must instead provide a good API as an alternative.

you do know about our 'templates' available in the Text editor of Blender, right?

Yes, I did see them, but none of them match my use case (which is probably intentional, since it sounds like my use case is simply not supported, which is quite odd).

Would you accept pull requests improving the Python API documentation? I am new to the Python API, but I am a quite experienced programmer in general.

You should have an operator to enable/disable this, not use a property, if you need to create objects etc.

Patches are always welcome, whether it is for documentation or code. :)

Also please do not use this tracker for that kind of discussion, we have other channels for that (blender.chat, our devltalk).