You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Please also set VCPKG_ROOT to the location of your installed VCPKG to test this. We use that in a CMakePresets file.
Description of expected behavior and the observed behavior
Application works as expected, but cleanup DOES not. I create a C++ object in C++ using python bindings generated using pybind11. This object is passed to the my Panel Dashboard, and it's state is used to populate several features of the dashboard. This dashboard is a real-time visualization tool for an algorithm written in C++.
Further testing has indicated that destructors on several of my Panel objects are not getting called. My Dashboard view contains a tab that the destructor never gets called on. The full version contains several tabs, plots, a controller, and a data model populated by the C++ object. All of this is stripped out for simplicity.
There is some cleanup such as closing output files that often occurs when the destructors get called in the full application, but this is not happening due to this bug.
Complete, minimal, self-contained example code that reproduces the issue
Basic Dashboard and C++ Application Launcher
main.py
"""This module contains an abstract class to act as a template for creating new pages"""importabcimportpanelaspnimportparamimportjsonimportosimportwebbrowserimportholoviewsashvimportcpp_app_testfrombokeh.server.serverimportServerpn.config.notifications=Truepn.config.theme='dark'# Allow main page to resize adaptivelypn.extension("tabulator", "plotly", sizing_mode="stretch_width", notifications=True)
hv.extension("bokeh")
pn.extension(nthreads=8)
classBasePage_Meta(type(abc.ABC), type(param.Parameterized)):
"""Metaclass that inherits from the ABC and Parameterized metaclass. """classBasePage(abc.ABC, param.Parameterized, metaclass=BasePage_Meta):
"""Abstract class for all classes pertaining to creating pages for the Dashboards. This requires each child class to instantiate their own version of the `update()` and `view()` methods. Parameters ---------- controller : Controller See `_controller` in Attributes. parent : PanelPageBase Reference to this parents panel. Attributes ---------- _controller : Controller Reference to the application controller. """def__init__(self, parent=None, **params) ->None:
super().__init__(**params)
self._parent=parent@abc.abstractmethoddefview(self) ->pn.Column|pn.Row:
"""Returns a panel view containing the content to be displayed on this page. Returns --------------- pn.Column Panel column containing the content to be displayed on this page. """classMainPanel(BasePage):
"""Main Panel for dashboard. Attributes ---------- _sidebar : Sidebar Collapsable sidebar panel containing plotting controls. Parameters ---------- app : App Reference to Python-Bound C++ app. """def__init__(self, app, **params):
super().__init__(**params)
self._app=appdef__del__(self):
print("Delete main page")
defserve(self) ->pn.Template:
""" Starts and makes available the panel as a web application. Returns ------- Template Panel template representing main panel view. """template=pn.template.FastListTemplate(
busy_indicator=None,
collapsed_sidebar=False,
header_background="#222829",
main=self.view(),
sidebar=None,
sidebar_width=250,
theme_toggle=True,
title="Test Dashboard"
)
returntemplate.servable()
defview(self) ->pn.Tabs:
""" Render panel layout. Returns ------- tabs : pn.Tabs Panel tabs object. """tabs=pn.Tabs(dynamic=True)
# Removed previously added tabs here.tabs.extend([
("Home", Home(self._app).view()),
])
returntabsclassHome(BasePage):
"""Class that contains the contents and layout for the dashboard. Parameters: ----------- controller : `Controller` Application controller (Replaced with app to test issue) Attributes: ----------- open_doc_button : pn.widgets.Button Button to open documentation in separate tab. """def__init__(self, app, **params) ->None:
super().__init__(**params)
self._app=appself.__create_widgets()
def__del__(self):
print("Delete home page")
def__create_widgets(self):
""" Creates Page Widgets."""self.open_doc_button=pn.widgets.Button(
name="Open User Manual",
button_type="primary",
on_click=self.open_documentation,
width=250
)
defopen_documentation(self, event):
"""Function callback that activates once the 'Open Documentation' button is clicked. Upon click, user documentation is launched in the browser. Parameters ---------- event Signals that button click has occurred prompting app to open the user documentation page. """file=os.path.abspath("../ReferenceDocs/doc_page.html")
returnwebbrowser.open(f"file://{file}")
defview(self) ->pn.Column:
"""View function that houses and displays the components of the dashboard homepage. Returns -------- pn.Column Object encapsulates all the pages' components to be rendered on the server. """header=pn.pane.Markdown(
""" ## Home Documentation of what this dashboard does here. """
)
pages_paragraph=pn.pane.Markdown(
""" ## Pages * __Home__: Serves as homepage and offers quick reference for documentation. * __Plots__: Displays plots generated by the sidebar controls. """
)
open_doc_row=pn.Column(pn.Row(self.open_doc_button))
returnpn.Column(
header,
open_doc_row,
pages_paragraph
)
classAppLauncher:
"""This class creates an application launcher that will create the Panel application as well as manage running any given dashboard. Parameters ---------- app : cpp_app_test.Application See `_app` in Attributes. Attributes ---------- _app : cpp_app_test.Application Python-Bound C++ Application to run. _main_panel : MainPanel Main class for real-time Dashboard. _dashboard : pn.viewable.Viewable Viewable representation of the dashboard. _server : bokeh.server.server.Server Bokeh dashboard server. """def__init__(self, app: cpp_app_test.Application) ->None:
self._app: cpp_app_test.Application=appself._main_panel: MainPanel=MainPanel(self._app)
self._dashboard: pn.viewable.Viewable=pn.panel(self._main_panel.serve(), width=1920)
self._server: Server=Nonedefrun(self):
"""Starts the C++ app (normally) and Python dashboard. When the C++ application exits, the dashboard will automatically stop. Parameters ---------- args : argparse.Namespace Argparse container containing command-line parsed arguments. """# Server is running in a separate thread.self._server=self._dashboard.show(open=False, threaded=True, port=5000, address="0.0.0.0")
# Wait here for application to exit.self._app.run()
# Stop server when C++ exits via signal handler.self._server.stop()
if__name__=="__main__":
print("Creating C++ application")
# Pybinding object.app=cpp_app_test.Application()
print("C++ application created.")
launcher=AppLauncher(app)
launcher.run()
print("Exiting interpreter. Destructors should be called after this point!")
Creating C++ application
Constructed application.
C++ application created.
Launching server at http://0.0.0.0:5000
^CCaught signal 2, shutting down...
Exiting interpreter. Destructors should be called after this point!
#include<app.hpp>
#include<functional>
#include<csignal>
#include<iostream>// Put signal handling in unnamed namespace to make it localnamespace
{
// The signal callback implementation
std::function<void(int)> shutdownHandler;
/** * @brief The signal callback to shutdown * * @param signal The signal number*/voidsignalHandler(int signal)
{
// If the function was assigned at this point, then call itif (shutdownHandler)
{
shutdownHandler(signal);
}
}
} // namespaceApplication::Application()
{
std::cout << "Constructed application." << std::endl;
}
Application::~Application()
{
std::cout << "Destroyed application" << std::endl;
}
voidApplication::stop()
{
m_running.store(false);
}
voidApplication::run()
{
// Setup signal handler for interrupt.// Set signal handling callback to capture Application object using this.
shutdownHandler = [this](intsignal)
{
std::cout << "Caught signal " << signal << ", shutting down..." << std::endl;
try
{
stop();
}
catch(std::runtime_error & e)
{
std::cout << "Exception thrown in signal handler " << e.what() << std::endl;
}
};
// Setup signal handling only once application is initialized. Otherwise, application will stall.std::signal(SIGINT, signalHandler); // For CTRL + Cstd::signal(SIGTERM, signalHandler);
std::signal(SIGKILL, signalHandler);
// Start an infinite loop. Will be interrupted when user interrupts the application and exit the app and the// dashboard server on python side.
m_running.store(true);
while (m_running.load())
{
}
}
Run the following in the same folder the files are in.
python main.py
Notice, how python exits and the application does NOT call the C++ destructor.
Screenshots or screencasts of the bug in action
I may be interested in making a pull request to address this
The text was updated successfully, but these errors were encountered:
tstrutz
changed the title
Hybrid C++ Pybound/Panel integration Destructor issues.
Hybrid C++ Pybind11/Panel integration Destructor issues.
Jun 18, 2024
I may have figured out what was causing this. It looks like Panel is holding onto Panel resources even after the Python interpreter exits. Maybe a leak of some kind?
Anyways, I found that running pn.state.kill_all_servers() as the last line in main.py eliminates all of the problems here. I also moved all of the variables to a def main function so that there are zero global variables other than the app object. The weird thing with this is I am stopping the server. It shouldn't be running, unless the state is still left behind?
Why doesn't panel automatically do this clean up operation when Python exits? It's probably a good idea.
defmain(app):
# Pybinding object.launcher=AppLauncher(app)
launcher.run()
if__name__=="__main__":
print("Creating C++ application")
app=cpp_app_test.Application()
print("C++ application created.")
main(app)
print("Exiting interpreter. Destructors should be called after this point!")
pn.state.kill_all_servers()
produces the output:
Creating C++ application
Constructed application.
C++ application created.
Launching server at http://0.0.0.0:5000
^CCaught signal 2, shutting down...
Delete main page
Exiting interpreter. Destructors should be called after this point!
Delete home page
Destroyed application
I've noticed that the full application which contains a Sidebar and a Controller still doesn't work though. I may just reduce the number of references to self_app to fix this.
I am experiencing an issue with a hybrid C++ and Python integrated app using Panel. More information below.
ALL software version info
VCPKG
GCC 11.4.0 (C++ 17 Requirement)
Ninja
CMake 3.22.1
Python 3.10
Numpy 1.24.4
Panel 1.4.2
Bokeh 3.4.1
Param 2.1.0
Pybind11 C++ (Latest)
WSL Ubuntu 22.04
Please also set VCPKG_ROOT to the location of your installed VCPKG to test this. We use that in a CMakePresets file.
Description of expected behavior and the observed behavior
Application works as expected, but cleanup DOES not. I create a C++ object in C++ using python bindings generated using pybind11. This object is passed to the my Panel Dashboard, and it's state is used to populate several features of the dashboard. This dashboard is a real-time visualization tool for an algorithm written in C++.
Further testing has indicated that destructors on several of my Panel objects are not getting called. My Dashboard view contains a tab that the destructor never gets called on. The full version contains several tabs, plots, a controller, and a data model populated by the C++ object. All of this is stripped out for simplicity.
There is some cleanup such as closing output files that often occurs when the destructors get called in the full application, but this is not happening due to this bug.
Complete, minimal, self-contained example code that reproduces the issue
Basic Dashboard and C++ Application Launcher
main.py
Stack traceback and/or browser JavaScript console output
C++ Code
app.hpp
app.cpp
Python binding code
app_pybind.cpp
Build Stuff
CMakePresets.json
vcpkg.json
CMakeLists.txt
How to build the minimum viable code
Create and place all files within the same folder. I can upload a zip if that makes it easier.
To run
Run the following in the same folder the files are in.
Notice, how python exits and the application does NOT call the C++ destructor.
Screenshots or screencasts of the bug in action
The text was updated successfully, but these errors were encountered: