populse / capsul

Collaborative Analysis Platform : Simple, Unifying, Lean
Other
7 stars 14 forks source link

Problem with internal weakref #341

Closed sapetnioc closed 10 months ago

sapetnioc commented 10 months ago

The following code raises an error. This code is extracted from a Notebook. The part that raises the exception can be executed if the beginning of the script is removed. I do not know yet what is the exact part that cause the problem.

from capsul.api import Process
from soma.controller import File, Directory, Literal, field

def cat(
    value1: str,
    value2: str,
    value3: str
) -> str:
    return '_'.join(i for i in (value1, value2, value3) if i)

def threshold(
    input_image: field(type_=File, doc='Path of a NIFTI-1 image file.'),
    output_image: field(type_=File, write=True, doc="Name of the output image."),
    method: field(type_=Literal['gt', 'ge', 'lt', 'le'], default='gt', doc="Method for thresolding."),
    threshold: field(type_=float, default=0)
):

    pass

def mask(
    input_image: field(type_=File, doc='Path of a NIFTI-1 image file.'),
    mask: field(type_=File, doc='Path of mask binary image.'),
    output_image: field(type_=File, write=True, doc="Output file name.")
):

    pass

class ProcessAsClass(Process):
    """Description of ProcessAsClass"""
    input: field(type_=File, doc="input file")
    output: field(type_=File, output=False, write=True, doc="output file")
    n: field(type_=float, doc="a parameter")

    def execute(self, context=None):
        with open(self.output, 'w') as output:
            with open(self.input) as input:
                print(input.read(), file=output)
            print('\nn =', self.n, file=output)

from capsul.api import Pipeline
from collections import OrderedDict

class SprintPipeline(Pipeline):

    def pipeline_definition(self):
        # nodes
        self.add_process("threshold_gt_1", "capsul.process.test.test_load_from_description.threshold", make_optional=['method', 'threshold'])
        self.nodes["threshold_gt_1"].threshold = 1.0
        self.add_process("threshold_gt_10", "capsul.process.test.test_load_from_description.threshold", make_optional=['method', 'threshold'])
        self.nodes["threshold_gt_10"].threshold = 10.0
        self.add_process("threshold_gt_100", "capsul.process.test.test_load_from_description.threshold", make_optional=['method', 'threshold'])
        self.nodes["threshold_gt_100"].threshold = 100.0
        self.add_process("threshold_lt_1", "capsul.process.test.test_load_from_description.threshold", make_optional=['method', 'threshold'])
        self.nodes["threshold_lt_1"].method = 'lt'
        self.nodes["threshold_lt_1"].threshold = 1.0500
        self.add_process("threshold_lt_10", "capsul.process.test.test_load_from_description.threshold", make_optional=['method', 'threshold'])
        self.nodes["threshold_lt_10"].method = 'lt'
        self.nodes["threshold_lt_10"].threshold = 10.0
        self.add_process("threshold_lt_100", "capsul.process.test.test_load_from_description.threshold", make_optional=['method', 'threshold'])
        self.nodes["threshold_lt_100"].method = 'lt'
        self.nodes["threshold_lt_100"].threshold = 100.0
        self.add_process("mask_1", "capsul.process.test.test_load_from_description.mask")
        self.add_process("mask_10", "capsul.process.test.test_load_from_description.mask")
        self.add_process("mask_100", "capsul.process.test.test_load_from_description.mask")

        # links
        self.export_parameter("threshold_lt_1", "input_image")
        self.add_link("input_image->threshold_gt_10.input_image")
        self.add_link("input_image->mask_10.input_image")
        self.add_link("input_image->threshold_gt_1.input_image")
        self.add_link("input_image->threshold_lt_100.input_image")
        self.add_link("input_image->threshold_lt_10.input_image")
        self.add_link("input_image->mask_1.input_image")
        self.add_link("input_image->threshold_gt_100.input_image")
        self.add_link("input_image->mask_100.input_image")
        self.add_link("threshold_gt_1.output_image->mask_1.mask")
        self.add_link("threshold_gt_10.output_image->mask_10.mask")
        self.add_link("threshold_gt_100.output_image->mask_100.mask")
        self.add_link("threshold_lt_1.output_image->mask_1.mask")
        self.add_link("threshold_lt_10.output_image->mask_10.mask")
        self.add_link("threshold_lt_100.output_image->mask_100.mask")
        self.export_parameter("mask_1", "output_image", "output_1")
        self.export_parameter("mask_10", "output_image", "output_10")
        self.export_parameter("mask_100", "output_image", "output_100")

        # processes selection
        self.add_processes_selection("select_method", OrderedDict([('greater than', ['threshold_gt_1', 'threshold_gt_10', 'threshold_gt_100']), ('lower than', ['threshold_lt_1', 'threshold_lt_10', 'threshold_lt_100'])]))

        # nodes positions
        self.node_position = {
            "threshold_gt_100": (386.0, 403.0),
            "inputs": (50.0, 50.0),
            "mask_1": (815.0, 153.0),
            "threshold_gt_10": (374.0, 242.0),
            "threshold_lt_100": (556.0, 314.0),
            "threshold_gt_1": (371.0, 88.0),
            "mask_10": (820.0, 293.0),
            "mask_100": (826.0, 451.0),
            "threshold_lt_1": (570.0, 6.0),
            "threshold_lt_10": (568.0, 145.0),
            "outputs": (1000.0, 100.0),
        }

        self.do_autoexport_nodes_parameters = False

from capsul.api import Capsul

capsul = Capsul()

import sys
from soma.qt_gui.qt_backend import QtGui
from capsul.qt_gui.widgets import PipelineDeveloperView

app = QtGui.QApplication.instance()
if not app:
    app = QtGui.QApplication(sys.argv)
pipeline = capsul.executable(SprintPipeline)
view1 = PipelineDeveloperView(pipeline)
view1.show()
app.exec_()
del view1

import sys
from capsul.api import Capsul

capsul = Capsul()
with capsul.engine() as engine:
    executable = capsul.executable('capsul.pipeline.test.test_pipeline.MyPipeline')
    executable.input_image = '/somewhere/image.nii'
    execution_id = engine.start(executable)
    try:
        engine.wait(execution_id, timeout=10)
        status = engine.status(execution_id)
        engine.raise_for_status(execution_id)
        engine.print_execution_report(
            engine.execution_report(execution_id), file=sys.stdout)
        engine.update_executable(execution_id, executable)
    finally:
        engine.dispose(execution_id)

The error is:

Exception in Event callback: <soma.utils.weak_proxy.proxy_method object at 0x7f088c1be890>
() {}
Traceback (most recent call last):
  File "/tmp/test.py", line 137, in <module>
    executable = capsul.executable('capsul.pipeline.test.test_pipeline.MyPipeline')
  File "/home/yann/dev/casaconda/src/capsul/capsul/application.py", line 145, in executable
    return executable(definition, **kwargs)
  File "/home/yann/dev/casaconda/src/capsul/capsul/application.py", line 341, in executable
    result = executable_from_python(definition, item)
  File "/home/yann/dev/casaconda/src/capsul/capsul/application.py", line 395, in executable_from_python
    result = item(definition=definition)
  File "/home/yann/dev/casaconda/src/capsul/capsul/pipeline/pipeline.py", line 261, in __init__
    self.update_nodes_and_plugs_activation()
  File "/home/yann/dev/casaconda/src/capsul/capsul/pipeline/pipeline.py", line 1497, in update_nodes_and_plugs_activation
    node.selection_changed.fire()
  File "/home/yann/dev/casaconda/build/lib/python3.10/site-packages/soma/controller/controller.py", line 56, in fire
    callback(*args, **kwargs)
  File "/home/yann/dev/casaconda/build/lib/python3.10/site-packages/soma/utils/weak_proxy.py", line 84, in __call__
    return getattr(self.proxy, self.method)(*args, **kwargs)
ReferenceError: weakly-referenced object no longer exists
sapetnioc commented 10 months ago

The selection_changed attribute is an Event instance that is created on Pipeline class. Therefore, callbacks registered on this event are probably never removed. PipelineDeveloperView register a weakref callback that makes the code fail later when the corresponding objects are deleted.

denisri commented 10 months ago

There is a lot of cleanup in references / weakrefs to do, in PipelineDeveloperView, for sure, but also in all Capsul code...