Skip to content
This repository has been archived by the owner on Nov 3, 2021. It is now read-only.

Udef checks dependencies #1615

Merged
merged 30 commits into from
Jul 22, 2021
Merged

Conversation

catunlock
Copy link

@catunlock catunlock commented Jun 17, 2021

This pull request resolves #1586. Now a check for dependent elements is performed when an element is going to be undefined.

So far we tested it for Pseudocounters, Motorgroups, Pseudomotors and, Measurement Groups.

The remaining tests could be testing for Pseudomotors that have as dependent elements another Pseudomotors.


IMPORTANT: This PR introduces usage of gc.collect() in Sardana. cycle-reference may exist between an element and a traceback stored in SardanaValue or SardanaAttribute as a consequence of getting sys.exc_info() and storing a reference to it. Since dependent elements are determined by introspecting the listeners we need to attempt garbage collection of these cycled-referenced objects.

catunlock and others added 16 commits June 1, 2021 08:32
When delete a element that depens of a motor group a better message for the user is printed.
now it takes into account if the listeners are from an object instance
- Dereference weakrefs stored in PoolBaseChannel._pseudo_elements.
- Fix PoolBaseChannel.remove_pseudo_element()
- Fix docstring of PoolBaseChannel.get_pseudo_elements()
Performing more tests revealed that these references are not problematic
in order to remove the pseudocounter core object correctly.
Finally do not delete them.
Seems like PyTango/Tango keeps alive Device objects after deleting them
(to be verified). These objects keep reference to the Sardana core objects,
making it impossible to delete them and affecting the dependency relations.
Delete reference to the motor_group in order to avoid this problem.
@reszelaz reszelaz added this to the Jul21 milestone Jun 17, 2021
catunlock and others added 8 commits June 17, 2021 15:41
"head" kwarg is not used by the acquisition actions and it ends up as strong reference in the
acquisition sub-actions. This leads to a cycle reference of the measurement group
and the acquisition actions causing problems when discovering dependent elements
when deleting an element.
Measurement group becomes an operator of the controller during an acquisition and remains
as operator after. This creates an additional strong reference to the measurement group
making it impossible to really delete causing troubles when discovering dependent elements
on an element deletion. Use weakref.ref instead.
When deleting element, no dependent element can exist.
First delete pseudo element and pseudo controller, and then the physical ones.
When deleting an element no dependent elements can exist. Delete the motor groups
before the physical motors are deleted.
Pseudo counters must have cycle references with physical elements.
This causes problems when deleting elements after prior deletion of pseudo counters.
This was discovered with the following tests:
- sardana/macroserver/macros/test/test_scanct.py::AscanctTest::test_ascanct_macro_runs_4
- sardana/macroserver/macros/test/test_scanct.py::AscanctTest::test_ascanct_macro_stops
- sardana/macroserver/macros/test/test_scanct.py::A2scanctTest::test_a2scanct_macro_runs
- sardana/macroserver/macros/test/test_scanct.py::MeshctTest::test_meshct_macro_runs
- sardana/tango/pool/test/test_measurementgroup.py::TangoAcquisitionTestCase::test_meas_cont_acquisition_2
- sardana/tango/pool/test/test_measurementgroup.py::TangoAcquisitionTestCase::test_stop_meas_cont_acquisition_3
- sardana/taurus/core/tango/sardana/test/test_measgrpstress.py::TestStressMeasurementGroup::test_stress_count_2
- sardana/taurus/core/tango/sardana/test/test_measgrpstress.py::TestStressMeasurementGroup::test_stress_count_3
- sardana/taurus/core/tango/sardana/test/test_measgrpstress.py::TestStressMeasurementGroup::test_stress_count_4
- sardana/taurus/core/tango/sardana/test/test_measgrpstress.py::TestStressMeasurementGroup::test_stress_count_6

Force gc.collect() to avoid this problem.
@reszelaz
Copy link
Collaborator

@dschick I think this is ready to be tested.
All the implementation looks ok, but the 97a96ee. But I think it is better to go for it with gc.collect() in this special case of pseudo counters - we already spent a lot of time trying to break the cycle reference and there are a lot of other issues waiting in the backlog. I hope it won't cause any problem.
Also, we have found that even if we delete Tango device it somehow resides in the device server precess. This explains
2b9359d. @catunlock will try to reproduce it with a dummy device server.
Thanks a lot @catunlock and @dschick ! I think this enhancement will add a great value to Sardana.

@yimli
Copy link
Collaborator

yimli commented Jul 14, 2021

I have tested this PR for #1658 . It works as expected and prevents the user from removing a measurement group dependent channel with udefelem. Thanks for the implementation!

@reszelaz
Copy link
Collaborator

Many thanks @yimli for your test!
@dschick do you also want to perform some tests?

@dschick
Copy link

dschick commented Jul 14, 2021 via email

@reszelaz reszelaz requested a review from a team July 14, 2021 21:40
@@ -557,6 +558,19 @@ def delete_element(self, name):
raise Exception("There is no element with name '%s'" % name)

elem_type = elem.get_type()
dependent_elements = elem.get_dependent_elements()
if len(dependent_elements) > 0:
gc.collect()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps a comment regarding why this is required here in the PR description?

@13bscsaamjad
Copy link

I have tested Pseudomotors that have as dependent elements another Pseudomotors and it works!

get_dependent_elements() returns only element name. Change the return
value to return the dependent elements objects in order to extend
use cases of this method e.g. allow to check the type of the returned
dependent elements.
gc.collect() workaround is tried if any kind of dependent element
resides in the pool. Call it only in the case of pseudo counters.
Disabled channels in the measurement group are
considered as not members of the group. They neither appear
in "element list" attribute nor the measurement group listens to their events.
Hence it is possible to undefine an experimental channel which is disabled.
This may lead to a situation when we try to enable again an undefined channel.
Consider this case by not accepting a configuration which contains
an undefined element. Also allow to acquire with a measurement group which
contains and undefined and disabled channel.
PoolElement implements get_dependent_elements() and
has_dependent_elements() while PoolBaseElement only implements
get_dependent_elements(). Implement has_dependent_elements() also
for the PoolBaseElement to be able to use both methods on any type of
Sardana element.
cycle-reference may exist between an element and a traceback
stored in SardanaValue or SardanaAttribute as a consequence of
getting sys.exc_info(). Keeping a list of dependent elements in the
scope of delete_element() makes it impossible to gc.collect() to
collect the cycled objects. Use has_dependent_elements() in order
to determine if there are dependent elements and then call the
gc.collect(). Also, do not call it conditionally only for the pseudo counters
but for any element type - the cycle may be created for between any
alement and a traceback.
@reszelaz
Copy link
Collaborator

I have performed some more work on this and introduced the following changes.

change return type of get_dependent_elements()

I change the return value of get_dependent_elements() to return dependent elements objects instead of names in order to extend use cases of this method e.g. allow to check the type of the returned dependent elements - see 146edfa

Fix case with meas group with undefined disabled channel

Disabled channels in the measurement group are considered as not members of the group. They neither appear in "element list" attribute nor the measurement group listens to their events. Hence it is possible to undefine an experimental channel that is disabled. This may lead to a situation when we try to enable again an undefined channel. Consider this case by not accepting a configuration that contains an undefined element. Also, allow to acquire with a measurement group that contains an undefined and disabled channel - see 6ac7aa6

Example:

Door/zreszela/1 [11]: get_meas_conf
ActiveMntGrp = mntgrp01
   Channel   Enabled   PlotType   PlotAxes
 --------- --------- ---------- ----------
      ct01      True         No        n/a
      ct02     False         No        n/a

Door/zreszela/1 [12]: udefelem ct02

Door/zreszela/1 [13]: get_meas_conf
ActiveMntGrp = mntgrp01
   Channel   Enabled   PlotType   PlotAxes
 --------- --------- ---------- ----------
      ct01      True         No        n/a
      ct02     False         No        n/a

Door/zreszela/1 [14]: ct
Wed Jul 21 11:32:31 2021

  ct01  =         1.0

Door/zreszela/1 [15]: set_meas_conf enabled True ct02
ActiveMntGrp = mntgrp01
An error occurred while running set_meas_conf:
PyDs_PythonError: ValueError: ct02 is not defined

Hint: in Spock execute `www`to get more details

Door/zreszela/1 [16]: ct
Wed Jul 21 11:32:55 2021

  ct01  =         1.0

Door/zreszela/1 [17]: defelem ct02 ctctrl01 2
Created ct02

Door/zreszela/1 [18]: set_meas_conf enabled True ct02
ActiveMntGrp = mntgrp01

Door/zreszela/1 [19]: ct
Wed Jul 21 11:33:09 2021

  ct01  =         1.0
  ct02  =         2.0

Fix gc.collect() of dependent elements

cycle-reference may exist between an element and a traceback stored in SardanaValue or SardanaAttribute as a consequence of getting sys.exc_info(). Keeping a list of dependent elements in the scope of Pool.delete_element() makes it impossible to gc.collect() to collect the cycled objects. Use has_dependent_elements() in order to determine if there are dependent elements and then call the gc.collect(). Also, do not call it conditionally only for the pseudo counters but for any element type - the cycle may be created between any element and a traceback - see 563e299

IMPORTANT: this last point should trigger a discussion if we should keep in Sardana references to the sys.exc_info() (traceback) objects.

I added a comment about the gc.collect() usage to the PR description @13bscsaamjad. Now it is well understood why we need to call it.

Thanks to all for your work and patience in this PR: @catunlock, @13bscsaamjad, @yimli and @dschick. I proceed with the merge!

@reszelaz reszelaz merged commit 33e06c9 into sardana-org:develop Jul 22, 2021
@yimli
Copy link
Collaborator

yimli commented Jul 23, 2021

Many thanks! @reszelaz I just have a follow-up question about undefining disabled channel. It is reasonable that we should be able to undefine a disabled experimental channel through 'udefelem', but that still cause the expconf to report channel missing error if we open expconf after undefining a disabled channel. Maybe even if the channel is disabled, it is better to first manually remove it from expconf? I am not sure about this.

@reszelaz
Copy link
Collaborator

Thanks for the tests with the expconf @yimli. I confirm what you observe. We get the following exception following the example from #1615 (comment)

Door/zreszela/1 [6]: expconf
MainThread     WARNING  2021-07-23 13:38:05,385 TaurusRootLogger: Problem with taurus.qt.editor (hint: is spyder >=3 installed?)

Door/zreszela/1 [7]: MainThread     INFO     2021-07-23 13:38:05,856 TaurusRootLogger: Using PyQt5 (v5.12.3 with Qt 5.12.9 and Python 3.9.1)
MainThread     INFO     2021-07-23 13:38:06,028 TaurusRootLogger: Plugin "taurus_pyqtgraph" lazy-loaded as "taurus.qt.qtgui.tpg"
MainThread     WARNING  2021-07-23 13:38:06,040 TaurusRootLogger: <frozen importlib._bootstrap>:228: DeprecationWarning: taurus.core.util.argparse is deprecated since 4.5.4. Use argparse or (better) click instead

MainThread     WARNING  2021-07-23 13:38:06,147 TaurusRootLogger: epics scheme not available: ModuleNotFoundError("No module named 'epics'")
MainThread     WARNING  2021-07-23 13:38:06,167 TaurusRootLogger: Problem with taurus.qt.editor (hint: is spyder >=3 installed?)
Traceback (most recent call last):
  File "/homelocal/zreszela/workspace/sardana/src/sardana/taurus/qt/qtgui/extra_sardana/measurementgroup.py", line 474, in flags
    ch_info = self.getAvailableChannels()[ch_name]
KeyError: 'tango://pt222.cells.es:10000/expchan/ctctrl01/2'

I will check if there is an easy way to prevent this exception in the expconf.

reszelaz added a commit to reszelaz/sardana that referenced this pull request Jul 23, 2021
sardana-org#1615 introduced bugs in expconf when a channel is undefined but still
present in the measurement groups configurations. Fix it by disabling all
cells of a disabled channels.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

udef checks dependencies
6 participants