widget child->parent assertion fails while closing ASIO?

Hi all,

did you ever noticed this scenario before?

image

It seems that while closing Rack, on Closing RtAudio ASIO device 1, an assertion failed on Widget.cpp? Are the operations in sequence or maybe somethings in parallel between gui and dsp threads? So I need to investigate on my own Widgets?

Pretty strange… but maybe its a know issue? I’m on Windows.

Note: it happens sometimes, can’t really replicate systematically.

Unrelated to RtAudio ASIO - it’s just something happening after the Audio device is closed. GUI and DSP threads are separate threads, so unless they’re in a synchronization lock, they are running parallel - that’s what threads are.

This is an assert in void Widget::removeChild(Widget* child). To know more would require seeing the callstack. There is definitely something wrong somewhere. This assert happens if removeChild is called with a widget that isn’t that widget’s child.

If you can repro without running your plugin, then it’s not your issue. If you’re developing a plugin, and you’re hitting Rack asserts when your plugin is loaded, then yeah, you might want to review your code and do some debugging. Unless you have custom widgets, it’s rare to explicitly call removeChild in a module. If you are messing with widget parent/child relationships in your module then yeah, you should review your usage.

1 Like

Thanks for the support and for the fast answer :slight_smile:

Oh yes, basically I use removeChild, I think in a correct way (probably, its not hehe). Here’s the basic code I use on my ModuleWidget:

void onRemove(const RemoveEvent &e) override {
    if (module != NULL) {
        MyModule *pModule = static_cast<MyModule *>(module);
        if (pModule) {
            removeLeds(pModule->mTargetModuleId);
        }
    }
}

void addLeds(int64_t targetModuleId) {
    MyModule *pModule = static_cast<MyModule *>(module);
    if (pModule) {
        ModuleWidget *pTargetModuleWidget = APP->scene->rack->getModule(targetModuleId);
        if (pTargetModuleWidget) {
            for (size_t i = 0; i < pModule->mSelectedTargetInputsIds.size(); i++) {
                const int inputId = (int)pModule->mSelectedTargetInputsIds[i];
                PortWidget *portWidget = pTargetModuleWidget->getInput(inputId);
                portWidget->addChild(new mymod::widgets::LedPort());
            }
        }
    }
}
void removeLeds(int64_t targetModuleId) {
    MyModule *pModule = static_cast<MyModule *>(module);
    if (pModule) {
        ModuleWidget *pTargetModuleWidget = APP->scene->rack->getModule(targetModuleId);
        if (pTargetModuleWidget) {
            for (PortWidget *portWidget : pTargetModuleWidget->getInputs()) {
                for (Widget *childWidget : portWidget->children) {
                    if (dynamic_cast<mymod::widgets::LedPort *>(childWidget)) {
                        portWidget->removeChild(childWidget);
                    }
                }
            }
        }
    }
}

void step() override {
    ModuleWidget::step();

    if (module != NULL) {
        MyModule *pModule = static_cast<MyModule *>(module);
        if (pModule) {
            // remove+add
            removeLeds(pModule->mTargetModuleId);
            if (pModule->mTargetModuleId != -1 && pModule->mAdd) {
                addLeds(pModule->mTargetModuleId);
            }
        }
    }
}

In short, my module adds to target module some Leds on some input ports, that’s all.

For my undestand, the way I do should be correct? Do you see some critical parts on it? If it delete the target module first/later my own module, this shouldn’t introduce any dongle pointer to leds (I suppose on gui thread the clean is executed on sequence, so one module can’t delete widgets in parallel).

Messing with another module’s widgets is dangerous.

I don’t think there’s enough code here to fully diagnose the issue, although I do see potential pitfalls like removing items from a list while you’re iterating it.

1 Like

I know :slight_smile: but in this case is a very basic task, so I guess it could be done. CV-MAP by @stoermelder does pretty similar task.

there isn’t additional code, that’s basically all: I add a led on some port input and remove it first, before adds. nothing more.

it adds max 1 mymod::widgets::LedPort for input, so that removeChild will be always called once (which trigger the assertion you see).

I could ensure it with a break after removeChild, yes, but the pitfall you could see will raise another error if messes with array/list (such as segmentation fault I believe).

If you’re talking about the handle overlay, the difference is that CV-MAP is using the Rack API’s functions for establishing lazy handles for parameters.

I did (in V1, with sadly no estimate for when I’ll be able to bring it over to V2) a fair bit of work on module overlays and my suggestion from that experience is that you manipulate other modules only through the Rack API; if you want to update something visually, build an overlay and drop it in the correct location.

1 Like

I see, but here I’m on Input port, not param handler at all. Not sure there are Rack API for this. For such basic task, I’d like first to understand why this should be evil (in my mind, its just an additional element, that will be removed on target module OR (in sequence, since there are not concurrency on the same thread, I belive) on my own module. Am I missing somethings?

That would be a even more pain I believe: what if I move the other/my module? Rescale all positions every step? And what about z-index? (such as if I add the target module after my own). It won’t render my widget…

There’s definitely not…

Well, there are two senses to your question: (1) why isn’t this working? (2) should this be allowed to work?

Regarding (1), as @pachde noted, the assertion that’s getting tripped is child->parent == this; I’m not in front of my dev machine right now so I can’t test this, but on a glance it looks like your new mymod::widgets::LedPort() that you’re passing to portWidget->addChild isn’t getting parented under the other module’s portWidget when portWidget->addChild() is getting called. You should breakpoint (or log) before your portWidget->removeChild call to see what childWidget->parent is (for example, is it still NULL? Is it holding a pointer to something in your module? &c).

Regarding (2), this is definitely not a basic task in context–the vast majority of Rack modules are entirely isolated from other modules, and the ones that do any cross-module work typically work through the higher-level API (such as the ones used by the GUI, or the ParamHandle weakref API), not the lower-level stuff. Rack is very flexible but you may find that there’s no way to get around the parenting problem. In that case, as a starting point I’d suggest compiling your own version of Rack with the assert commented out of widget.cpp, trying your code again, and then (if it doesn’t segfault immediately) doing really extensive testing to determine if anything else is going wrong. I assume the parenting rules are there to avoid the obvious problems (memory leaks on the one hand, null pointer dereferences on the other). That may give you a sense of why the assert is there.

These are all things you’ll have to deal with, but the difference is that you won’t crash Rack :slight_smile: I may be able to give you some ideas to start with, based on my own experiments with overlays, but before that it might be good to know what your goal is (not just “draw an LED on some other module’s port”–I mean what musical/interactive goal are you trying to accomplish; maybe we’re in an XY problem situation and there’s another solution?)

1 Like

Let see :slight_smile: I’ve add this debug code, so if that’s happening again, I’ll catch it (at least NULL/pointer and such):

if (dynamic_cast<mymod::widgets::LedPort *>(childWidget)) {
	if (childWidget->parent != portWidget) {
		DEBUG("PROBLEM removeLeds Start");
		DEBUG("childWidget         %p", childWidget);
		DEBUG("childWidget->parent %p", childWidget->parent);
		DEBUG("portWidget          %p", portWidget);
		DEBUG("PROBLEM removeLeds End");
	}
	portWidget->removeChild(childWidget);
}

Thanks for the suggestion, I monitoring next days and in case come back hehe

1 Like

That should give you a thread to tug on. Please do update if you have time, it’s a very interesting situation!

1 Like

Damn, should be somethings other :slight_smile: Just happened right now, with those DEBUG guard, and nothing has been printed on terminal.

Am I wrong, or the code I wrote as guard its correct? I mean: if that’s the module which cause the trouble, at least “PROBLEM removeLeds Start” should be printed right? :open_mouth: (its the only part where i call removeChild() on my own).

1 Like

Huh… it hit the same assert as before? Assuming so, initial hypotheses:

  1. Could there be some other call to removeChild before yours?
  2. Maybe (for some reason) childWidget->parent != portWidget isn’t the right problem identifier?
  3. Could there be a race condition somewhere? (If the problem doesn’t always occur, this is more likely).

I’d start with the obvious modification below, to make sure you’re actually exercising the test code. My main recommendation, though: if you’re going to attempt this level of Rack programming, build your own Rack binary and instrument that. With your own build, you can log whenever removeChild gets called, turn on and off the assertion, etc. Rack is beautifully simple in some ways, but ultimately it’s real-time, multi-threaded, plugin-enabled code, so it can be hard to reason about in the abstract.

if (dynamic_cast<mymod::widgets::LedPort *>(childWidget)) {		 
    DEBUG("Before test");
	if (childWidget->parent != portWidget) {
		DEBUG("PROBLEM removeLeds Start");
		DEBUG("childWidget         %p", childWidget);
		DEBUG("childWidget->parent %p", childWidget->parent);
		DEBUG("portWidget          %p", portWidget);
		DEBUG("PROBLEM removeLeds End");
	}
    DEBUG("After test");
    portWidget->removeChild(childWidget);
    DEBUG("After call");
}

This is during tear-down, so order of module destruction, your destructor behavior come into play.

1 Like