Skip to content
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

Resize handlers might be running while DPG context is being destroyed #2020

Open
v-ein opened this issue Jan 23, 2023 · 4 comments
Open

Resize handlers might be running while DPG context is being destroyed #2020

v-ein opened this issue Jan 23, 2023 · 4 comments
Labels
state: pending not addressed yet type: bug bug

Comments

@v-ein
Copy link
Contributor

v-ein commented Jan 23, 2023

Version of Dear PyGui

Version: 1.8.0
Operating System: Windows 10

My Issue/Question

When application's main window is closed, dpg.destroy_context() is typically called right away in order to shut down DPG. Looks like it does not wait until all handlers complete; in particular, if there was a dpg.window with a "resize" handler and autosize=True, there's a good chance that the handler runs in parallel with destroy_context(), and might eventually reference a DPG item that has already been destroyed by destroy_context(). This does not crash the application "natively" (with an access violation or something), but it does throw an exception like this:

Exception:
Error:     [1005]
Command:   get_item_state
Item:      0
Label:     Not found
Item Type: Unknown
Message:   Item not found: 21

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "....\dpg-test.py", line 15, in on_window_resize
    print(f"{i:3}: {dpg.get_item_rect_size(window)}")
  File "....\dearpygui.py", lin
e 831, in get_item_rect_size
    return internal_dpg.get_item_state(item)["rect_size"]
SystemError: <built-in function get_item_state> returned a result with an error
set

For some reason, the resize handler is called during termination when autosize is True, but not called for autosize=False. Might be caused by DPG destroying the window's children and therefore causing it to auto-resize to a smaller size.

One could explicitly remove all such handlers before shutting down DPG (i.e. in between start_dearpygui() and destroy_context()), but in a real-world application this would be a rather clumsy workaround.

To Reproduce

Steps to reproduce the behavior:

  1. Run the below example.
  2. Close the app window (i.e. the viewport). Look at the Python console - you'll see an exception saying an item could not be found.
  3. Uncomment the line just above destroy_context() and run the example again.
  4. Close the window - this time there will be no exceptions because the handler had plenty of time to complete its job.

Expected behavior

destroy_context() should wait for the handlers thread to complete. (Disclaimer: I haven't looked at its code yet.)

As an alternative, DPG could disable the resize handlers on a container if it's going to destroy it; this would be more of a workaround but seems to be a reasonable one.

I'm not sure which of the two ways would amount to less work. The former seems to be more robust since it also fixes other similar issues.

Screenshots/Video

I'm not providing a screenshot - you can find the error message above, and there's nothing to show in the GUI.

Standalone, minimal, complete and verifiable example

from time import sleep
import dearpygui.dearpygui as dpg

dpg.create_context()
dpg.create_viewport(title="Test", width=500, height=400)

dpg.setup_dearpygui()
 
def on_window_resize(sender, window):
    # Attempting to get some window properties - in a loop in order to
    # simulate a long-running handler that does something on the widgets tree
    for i in range(0, 100):
        print(f"{i:3}: {dpg.get_item_rect_size(window)}")

with dpg.window(label="Test", autosize=True) as wnd:
    dpg.add_text(f"Close the main app window and hope for the best.")

    with dpg.item_handler_registry() as window_handlers:
        dpg.add_item_resize_handler(callback=on_window_resize)
    dpg.bind_item_handler_registry(wnd, window_handlers)


dpg.show_viewport()
dpg.start_dearpygui()
# Uncomment this line to see that the crash is really caused by destroy_context()
# sleep(1)
dpg.destroy_context()
@v-ein v-ein added state: pending not addressed yet type: bug bug labels Jan 23, 2023
@v-ein
Copy link
Contributor Author

v-ein commented Jan 24, 2023

Looks like it does crash the application on exit from time to time. Depends on what's in the resize handler - mine got a bit more complex, and started giving me segfaults.

@v-ein
Copy link
Contributor Author

v-ein commented Jul 19, 2023

Looks like this applies to all other handlers, too, though it would be more difficult to recreate for other handlers.

The basic problem is that events are born in the renderer thread but are handled in the handlers thread. Between these two moments, they "live" in a queue for a while. If the renderer thread starts manipulating the widget tree, like it does in destroy_context, any widget that is referenced in a handler might get deleted by the moment the handler is invoked (while the event is sitting in the queue). This applies to any widget, not only the one that generated the event: if you reference another widget (e.g. you saved its ID in user_data or somewhere else), there's a chance the widget no longer exists.

Another, more complicated example of this issue would look like this:

  • Two events occur in the same frame, event1 is generated first, event2 second. For example, event1 is generated by a parent widget and event2 by a child, or maybe they are generated by two siblings.
  • Both events are put into the queue by the renderer thread.
  • The handlers thread starts executing the handler of event1.
  • That handler deletes the widget that generated event2 - maybe implicitly, e.g. by deleting its parent, or calling delete_item(children_only=True).
  • The handler of event2 gets executed, but its widget is gone.

It's difficult, if not impossible, to guard against such issues on the DPG side of things. Looks like it's what the user's program should do: if there's a chance your widget gets deleted, you know about this and must do an appropriate check (or catch exceptions).

Nonetheless, there's a bug in DPG that causes a crash. The scenario above, on its own, does not lead to a hard crash, it can only give a mere exception in Python. Looks like the crash is caused by ToPyUUID(), which is called in the handlers thread without holding the global mutex. It returns mvAppItem*, leaving the main thread a chance to delete that mvAppItem while the handler is trying to dereference the pointer. Maybe there are other ways to crash, too; that's the only one I've found so far.

In this ticket, I think we should be focusing on the crash, and leaving existence checks to DPG users to do.

Also: my suggestion that destroy_context should wait for all handlers to complete still holds true; we should not be exiting the program with the handlers thread hastily doing some work and being terminated in the middle.

@SamuMazzi
Copy link
Contributor

Does this still cause you issues? I tried to replicate it many times on my Ubuntu 24.04 but it always worked fine.
Is it possible that #2275 fixed it? In src/dearpygui_commands.h I added:

if (GContext->viewport != nullptr)
	mvCleanupViewport(*GContext->viewport);

that maybe helped in this.
Just asking, thanks :)

@v-ein
Copy link
Contributor Author

v-ein commented Sep 23, 2024

Same thing with #2275:

Exception:
Error:     [1005]
Command:   get_item_state
Item:      0
Label:     Not found
Item Type: Unknown
Message:   Item not found: 22

Which is somewhat expected since destroy_context is still not waiting for handlers to complete. I do have a fix for this and will eventually push it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
state: pending not addressed yet type: bug bug
Projects
None yet
Development

No branches or pull requests

2 participants