Skip to content

Commit a07e026

Browse files
committed
Prevent segmentation faults during garbage collection in PipelineDeveloperView
1 parent e78d8ce commit a07e026

File tree

1 file changed

+108
-57
lines changed

1 file changed

+108
-57
lines changed

capsul/qt_gui/widgets/pipeline_developer_view.py

Lines changed: 108 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -2676,6 +2676,7 @@ def __init__(self, pipeline=None, parent=None, show_sub_pipelines=False,
26762676
self._restricted_edition = False
26772677
self.disable_overwrite = False
26782678
self._userlevel = userlevel
2679+
self._pipeline_released = False
26792680
self.doc_browser = None
26802681

26812682
self.set_pipeline(pipeline)
@@ -2688,10 +2689,10 @@ def __init__(self, pipeline=None, parent=None, show_sub_pipelines=False,
26882689
self.link_keydelete_clicked.connect(self._link_delete_clicked)
26892690
self.node_keydelete_clicked.connect(self._node_delete_clicked)
26902691

2691-
def __del__(self):
2692-
# print('PipelineDeveloperView.__del__')
2693-
self.release_pipeline(delete=True)
2694-
# super(PipelineDeveloperView, self).__del__()
2692+
# def __del__(self):
2693+
# # print('PipelineDeveloperView.__del__')
2694+
# self.release_pipeline(delete=True)
2695+
# # super(PipelineDeveloperView, self).__del__()
26952696

26962697
@property
26972698
def userlevel(self):
@@ -2791,6 +2792,11 @@ def set_pipeline(self, pipeline):
27912792
if hasattr(pipeline, 'pipeline_steps'):
27922793
pipeline.pipeline_steps.on_trait_change(
27932794
self._reset_pipeline, dispatch='ui')
2795+
2796+
def closeEvent(self, event):
2797+
"""Ensure pipeline is released before the widget is closed."""
2798+
self.release_pipeline()
2799+
super().closeEvent(event)
27942800

27952801
def release_pipeline(self, delete=False):
27962802
'''
@@ -2801,59 +2807,104 @@ def release_pipeline(self, delete=False):
28012807
'''
28022808
# Setup callback to update view when pipeline state is modified
28032809
from soma.qt_gui.qt_backend import sip
2804-
pipeline = None
2805-
if self.scene is not None and hasattr(self.scene, 'pipeline'):
2806-
pipeline = self.scene.pipeline
2807-
if pipeline is not None:
2808-
if hasattr(pipeline, 'pipeline_steps'):
2809-
pipeline.pipeline_steps.on_trait_change(
2810-
self._reset_pipeline, remove=True)
2811-
pipeline.on_trait_change(self._reset_pipeline, 'selection_changed',
2812-
remove=True)
2813-
pipeline.on_trait_change(self._reset_pipeline,
2814-
'user_traits_changed', remove=True)
2815-
if sip.isdeleted(self):
2816-
# prevent 'C++ object has been deleted' error
2817-
return
2818-
self.setScene(None)
2819-
if self.scene:
2820-
# force destruction of scene internals now that the Qt object
2821-
# still exists
2822-
self.scene._release()
2823-
# the scene is not deleted after all refs are released, even
2824-
# after self.setScene(None). This is probably a bug in PyQt:
2825-
# the C++ layer keeps ownership of the scene, whereas it should
2826-
# not: the Qt doc specifies for QGraphicsView.setScene():
2827-
# "The view does not take ownership of scene.", however in PyQt it
2828-
# does, and only releases it when the QGraphicsView is deleted.
2829-
# Thus we have to force it by hand:
2830-
from soma.qt_gui.qt_backend import sip
2831-
sip.transferback(self.scene)
2832-
self.scene = None
2833-
import gc
2834-
gc.collect()
2835-
if not delete and (pipeline is not None or self.scene is None):
2836-
self.scene = PipelineScene(self, userlevel=self.userlevel)
2837-
self.scene.set_enable_edition(self._enable_edition)
2838-
self.scene.logical_view = self._logical_view
2839-
self.scene.colored_parameters = self.colored_parameters
2840-
self.scene.subpipeline_clicked.connect(self.subpipeline_clicked)
2841-
self.scene.subpipeline_clicked.connect(self.onLoadSubPipelineClicked)
2842-
self.scene.process_clicked.connect(self._node_clicked)
2843-
self.scene.node_clicked.connect(self._node_clicked)
2844-
self.scene.node_clicked_ctrl.connect(self._node_clicked_ctrl)
2845-
self.scene.switch_clicked.connect(self.switch_clicked)
2846-
self.scene.node_right_clicked.connect(self.node_right_clicked)
2847-
self.scene.node_right_clicked.connect(self.onOpenProcessController)
2848-
self.scene.plug_clicked.connect(self.plug_clicked)
2849-
self.scene.plug_right_clicked.connect(self.plug_right_clicked)
2850-
self.scene.link_right_clicked.connect(self.link_right_clicked)
2851-
self.scene.link_keydelete_clicked.connect(self.link_keydelete_clicked)
2852-
self.scene.node_keydelete_clicked.connect(self.node_keydelete_clicked)
2853-
self.scene.pos = {}
2854-
self.scene.dim = {}
2855-
self.setWindowTitle('<no pipeline>')
2856-
self.setScene(self.scene)
2810+
2811+
if getattr(self, '_pipeline_released', False):
2812+
return # already released
2813+
2814+
self._pipeline_released = True
2815+
2816+
try:
2817+
pipeline = None
2818+
2819+
2820+
if (
2821+
hasattr(self, 'scene')
2822+
and self.scene is not None
2823+
and hasattr(self.scene, 'pipeline')
2824+
):
2825+
pipeline = self.scene.pipeline
2826+
2827+
if pipeline is not None:
2828+
2829+
if hasattr(pipeline, 'pipeline_steps'):
2830+
pipeline.pipeline_steps.on_trait_change(
2831+
self._reset_pipeline, remove=True
2832+
)
2833+
2834+
pipeline.on_trait_change(
2835+
self._reset_pipeline, 'selection_changed', remove=True
2836+
)
2837+
pipeline.on_trait_change(
2838+
self._reset_pipeline, 'user_traits_changed', remove=True
2839+
)
2840+
2841+
if sip.isdeleted(self):
2842+
# prevent 'C++ object has been deleted' error
2843+
return
2844+
2845+
self.setScene(None)
2846+
2847+
if hasattr(self, 'scene') and self.scene:
2848+
2849+
# force destruction of scene internals now that the Qt object
2850+
# still exists
2851+
try:
2852+
self.scene._release()
2853+
2854+
except Exception:
2855+
pass
2856+
2857+
# the scene is not deleted after all refs are released, even
2858+
# after self.setScene(None). This is probably a bug in PyQt:
2859+
# the C++ layer keeps ownership of the scene, whereas it should
2860+
# not: the Qt doc specifies for QGraphicsView.setScene():
2861+
# "The view does not take ownership of scene.", however in PyQt it
2862+
# does, and only releases it when the QGraphicsView is deleted.
2863+
# Thus we have to force it by hand:
2864+
try:
2865+
sip.transferback(self.scene)
2866+
2867+
except Exception:
2868+
pass
2869+
2870+
2871+
self.scene = None
2872+
import gc
2873+
gc.collect()
2874+
2875+
if not delete and (pipeline is not None or self.scene is None):
2876+
2877+
try:
2878+
self.scene = PipelineScene(self, userlevel=self.userlevel)
2879+
self.scene.set_enable_edition(self._enable_edition)
2880+
self.scene.logical_view = self._logical_view
2881+
self.scene.colored_parameters = self.colored_parameters
2882+
self.scene.subpipeline_clicked.connect(self.subpipeline_clicked)
2883+
self.scene.subpipeline_clicked.connect(self.onLoadSubPipelineClicked)
2884+
self.scene.process_clicked.connect(self._node_clicked)
2885+
self.scene.node_clicked.connect(self._node_clicked)
2886+
self.scene.node_clicked_ctrl.connect(self._node_clicked_ctrl)
2887+
self.scene.switch_clicked.connect(self.switch_clicked)
2888+
self.scene.node_right_clicked.connect(self.node_right_clicked)
2889+
self.scene.node_right_clicked.connect(self.onOpenProcessController)
2890+
self.scene.plug_clicked.connect(self.plug_clicked)
2891+
self.scene.plug_right_clicked.connect(self.plug_right_clicked)
2892+
self.scene.link_right_clicked.connect(self.link_right_clicked)
2893+
self.scene.link_keydelete_clicked.connect(self.link_keydelete_clicked)
2894+
self.scene.node_keydelete_clicked.connect(self.node_keydelete_clicked)
2895+
self.scene.pos = {}
2896+
self.scene.dim = {}
2897+
self.setWindowTitle('<no pipeline>')
2898+
self.setScene(self.scene)
2899+
2900+
except Exception as e:
2901+
print(
2902+
f"[release_pipeline] Exception "
2903+
f"during scene rebuild: {e}"
2904+
)
2905+
2906+
except Exception as e:
2907+
print(f"[release_pipeline] Exception: {e}")
28572908

28582909
def is_logical_view(self):
28592910
'''

0 commit comments

Comments
 (0)