diff --git a/src/sardana/macroserver/macros/test/test_scanct.py b/src/sardana/macroserver/macros/test/test_scanct.py index f9af22e44..07ccfa3e6 100644 --- a/src/sardana/macroserver/macros/test/test_scanct.py +++ b/src/sardana/macroserver/macros/test/test_scanct.py @@ -24,6 +24,7 @@ ############################################################################## """Tests for continuous scans (ct-like)""" +import json import time import PyTango import unittest @@ -343,6 +344,8 @@ def macro_stops(self, meas_config, macro_params, wait_timeout=None, ScanctTest.check_stopped(self) def tearDown(self): + for mg in self.pool.MotorGroupList: + self.pool.DeleteElement(json.loads(mg)['name']) ScanctTest.tearDown(self) unittest.TestCase.tearDown(self) @@ -398,5 +401,7 @@ def macro_stops(self, meas_config, macro_params, wait_timeout=None, ScanctTest.check_stopped(self) def tearDown(self): + for mg in self.pool.MotorGroupList: + self.pool.DeleteElement(json.loads(mg)['name']) ScanctTest.tearDown(self) unittest.TestCase.tearDown(self) diff --git a/src/sardana/pool/pool.py b/src/sardana/pool/pool.py index f44f0cf85..e3759adf8 100644 --- a/src/sardana/pool/pool.py +++ b/src/sardana/pool/pool.py @@ -32,6 +32,7 @@ __docformat__ = 'restructuredtext' +import gc import os.path import logging.handlers @@ -559,8 +560,25 @@ def delete_element(self, name): elem = self.get_element(full_name=name) except: raise Exception("There is no element with name '%s'" % name) - - elem_type = elem.get_type() + + # cycle-reference may exist between the element and a traceback + # stored in SardanaValue or SardanaAttribute as a consequence of + # getting sys.exc_info() - try to delete them with gc.collect() + if elem.has_dependent_elements(): + gc.collect() + + dependent_elements = elem.get_dependent_elements() + if len(dependent_elements) > 0: + names = [elem.name for elem in dependent_elements] + raise Exception( + "The element {} can't be deleted because {} depend on it." + "\n\nIf the name of the dependent element starts with " + "'_mg_ms_*' it means that are motor groups, execute " + "DeleteElement() command on the Pool e.g. " + "Pool_demo1_1.DeleteElement('_mg_ms_20671_1') in Spock." + .format(name, ", ".join(names))) + + elem_type = elem.get_type() if elem_type == ElementType.Controller: if len(elem.get_elements()) > 0: raise Exception("Cannot delete controller with elements. " diff --git a/src/sardana/pool/poolacquisition.py b/src/sardana/pool/poolacquisition.py index 4f7e287d2..02a1d5342 100644 --- a/src/sardana/pool/poolacquisition.py +++ b/src/sardana/pool/poolacquisition.py @@ -561,7 +561,7 @@ def run(self, *args, **kwargs): # clean also the pseudo counters, even the ones that do not # participate directly in the acquisition for pseudo_elem in elem.get_pseudo_elements(): - pseudo_elem.clear_value_buffer() + pseudo_elem().clear_value_buffer() if self._hw_acq_args is not None: self._hw_acq._wait() diff --git a/src/sardana/pool/poolbasechannel.py b/src/sardana/pool/poolbasechannel.py index 075237d49..2bb5dab1c 100644 --- a/src/sardana/pool/poolbasechannel.py +++ b/src/sardana/pool/poolbasechannel.py @@ -30,6 +30,8 @@ __docformat__ = 'restructuredtext' +import weakref + from sardana.sardanadefs import AttrQuality, ElementType from sardana.sardanaattribute import SardanaAttribute from sardana.sardanabuffer import SardanaBuffer @@ -41,6 +43,7 @@ from sardana.sardanaevent import EventType from sardana.pool import AcqSynch, AcqMode + class ValueBuffer(SardanaBuffer): def is_value_required(self, idx): @@ -53,7 +56,7 @@ def is_value_required(self, idx): :rtype: bool """ for element in self.obj.get_pseudo_elements(): - if element.get_value_buffer().next_idx <= idx: + if element().get_value_buffer().next_idx <= idx: return True return False @@ -135,8 +138,8 @@ def get_pseudo_elements(self): """Returns list of pseudo elements e.g. pseudo counters that this channel belongs to. - :return: pseudo elements - :rtype: seq<:class:`~sardana.pool.poolpseudocounter.PoolPseudoCounter`> + :return: weak references to pseudo elements + :rtype: seq<:class:`weakref.ref`> """ return self._pseudo_elements @@ -150,7 +153,7 @@ def add_pseudo_element(self, element): """ if not self.has_pseudo_elements(): self.get_value_buffer().persistent = True - self._pseudo_elements.append(element) + self._pseudo_elements.append(weakref.ref(element)) def remove_pseudo_element(self, element): """Removes pseudo element e.g. pseudo counters that this channel @@ -160,10 +163,17 @@ def remove_pseudo_element(self, element): :type element: :class:`~sardana.pool.poolpseudocounter.PoolPseudoCounter` """ - - self._pseudo_elements.remove(element) - if not self.has_pseudo_elements(): - self.get_value_buffer().persistent = False + for pseudo_element in self._pseudo_elements: + if pseudo_element() == element: + self._pseudo_elements.remove(pseudo_element) + if not self.has_pseudo_elements(): + self.get_value_buffer().persistent = False + break + else: + raise ValueError( + "{} is not a pseudo element of {}".format( + element.name, self.name) + ) def get_value_attribute(self): """Returns the value attribute object for this experiment channel diff --git a/src/sardana/pool/poolbaseelement.py b/src/sardana/pool/poolbaseelement.py index 2a852b52f..9b5346ed3 100644 --- a/src/sardana/pool/poolbaseelement.py +++ b/src/sardana/pool/poolbaseelement.py @@ -100,6 +100,12 @@ def serialize(self, *args, **kwargs): ret = PoolObject.serialize(self, *args, **kwargs) return ret + def get_dependent_elements(self): + return [] + + def has_dependent_elements(self): + return False + # -------------------------------------------------------------------------- # simulation mode # -------------------------------------------------------------------------- diff --git a/src/sardana/pool/poolcontroller.py b/src/sardana/pool/poolcontroller.py index ad3bb1036..1e204ce59 100644 --- a/src/sardana/pool/poolcontroller.py +++ b/src/sardana/pool/poolcontroller.py @@ -442,10 +442,10 @@ def set_operator(self, operator): :param operator: the new operator object :type operator: object""" - self._operator = operator + self._operator = weakref.ref(operator) def get_operator(self): - return self._operator + return self._operator() operator = property(fget=get_operator, fset=set_operator, doc="current controller operator") diff --git a/src/sardana/pool/poolelement.py b/src/sardana/pool/poolelement.py index 2574c9fe4..d4706bcb2 100644 --- a/src/sardana/pool/poolelement.py +++ b/src/sardana/pool/poolelement.py @@ -88,6 +88,29 @@ def set_action_cache(self, action_cache): def get_source(self): return "{0}/{1}".format(self.full_name, self.get_default_acquisition_channel()) + def get_dependent_elements(self): + """Get elements which depend on this element. + + Get elements e.g. pseudo elements or groups, which depend on this + element. + + :return: dependent elements + :rtype: seq + """ + dependent_elements = [] + for listener in self.get_listeners(): + try: + elem = listener().__self__ + except AttributeError: + continue + if isinstance(elem, PoolBaseElement): + dependent_elements.append(elem) + + return dependent_elements + + def has_dependent_elements(self): + return len(self.get_dependent_elements()) > 0 + # -------------------------------------------------------------------------- # instrument # -------------------------------------------------------------------------- diff --git a/src/sardana/pool/poolmeasurementgroup.py b/src/sardana/pool/poolmeasurementgroup.py index 67c9ffe27..c335b16f7 100644 --- a/src/sardana/pool/poolmeasurementgroup.py +++ b/src/sardana/pool/poolmeasurementgroup.py @@ -800,7 +800,12 @@ def set_configuration_from_user(self, cfg): params['pool'] = pool channel = PoolExternalObject(**params) else: - channel = pool.get_element_by_full_name(ch_name) + try: + channel = pool.get_element_by_full_name(ch_name) + except KeyError: + raise ValueError( + '{} is not defined'.format(ch_data['name'])) + ch_data = self._fill_channel_data(channel, ch_data) user_config_channel[ch_name] = ch_data ch_item = ChannelConfiguration(channel, ch_data) @@ -958,7 +963,11 @@ def set_configuration_from_user(self, cfg): for conf_synch in conf_synch_ctrl.get_channels(enabled=True): user_elem_ids_list.append(conf_synch.id) self._parent.set_user_element_ids(user_elem_ids_list) - + # force assignment of user elements to the measurement group + # in order to update the list of dependent elements (listeners) + # of the element e.g. to prevent undefinition of an element added + # to the measurement group. + self._parent.get_user_elements() self.changed = True def _fill_channel_data(self, channel, channel_data): @@ -1352,16 +1361,13 @@ def prepare(self): value = self._get_value() self._pending_starts = self.nb_starts - kwargs = {'head': self} - self.acquisition.prepare(self.configuration, self.acquisition_mode, value, self._synch_description, self._moveable_obj, self.sw_synch_initial_domain, - self.nb_starts, - **kwargs) + self.nb_starts) def start_acquisition(self, value=None): """Start measurement. diff --git a/src/sardana/sardanaevent.py b/src/sardana/sardanaevent.py index 1348affdb..f0e5e6150 100644 --- a/src/sardana/sardanaevent.py +++ b/src/sardana/sardanaevent.py @@ -105,6 +105,9 @@ def has_listeners(self): return False return len(self._listeners) > 0 + def get_listeners(self): + return self._listeners + def fire_event(self, event_type, event_value, listeners=None): self.flush_queue() self._fire_event(event_type, event_value, listeners=listeners) diff --git a/src/sardana/tango/pool/Controller.py b/src/sardana/tango/pool/Controller.py index d72199923..c8c3dafec 100644 --- a/src/sardana/tango/pool/Controller.py +++ b/src/sardana/tango/pool/Controller.py @@ -72,6 +72,7 @@ def set_ctrl(self, ctrl): @DebugIt() def delete_device(self): PoolDevice.delete_device(self) + self.ctrl = None @DebugIt() def init_device(self): diff --git a/src/sardana/tango/pool/MeasurementGroup.py b/src/sardana/tango/pool/MeasurementGroup.py index cfe1c38d3..c6439d55c 100644 --- a/src/sardana/tango/pool/MeasurementGroup.py +++ b/src/sardana/tango/pool/MeasurementGroup.py @@ -68,6 +68,7 @@ def delete_device(self): mg = self.measurement_group if mg is not None: mg.remove_listener(self.on_measurement_group_changed) + self.measurement_group = None @DebugIt() def init_device(self): diff --git a/src/sardana/tango/pool/MotorGroup.py b/src/sardana/tango/pool/MotorGroup.py index fdf63a6c6..5478a4e0b 100644 --- a/src/sardana/tango/pool/MotorGroup.py +++ b/src/sardana/tango/pool/MotorGroup.py @@ -70,6 +70,7 @@ def delete_device(self): motor_group = self.motor_group if motor_group is not None: motor_group.remove_listener(self.on_motor_group_changed) + self.motor_group = None @DebugIt() def init_device(self): diff --git a/src/sardana/tango/pool/PseudoCounter.py b/src/sardana/tango/pool/PseudoCounter.py index 9293d9cf6..f2220c30c 100644 --- a/src/sardana/tango/pool/PseudoCounter.py +++ b/src/sardana/tango/pool/PseudoCounter.py @@ -71,6 +71,7 @@ def delete_device(self): pseudo_counter = self.pseudo_counter if pseudo_counter is not None: pseudo_counter.remove_listener(self.on_pseudo_counter_changed) + self.pseudo_counter = None @DebugIt() def init_device(self): diff --git a/src/sardana/tango/pool/PseudoMotor.py b/src/sardana/tango/pool/PseudoMotor.py index 562b36b16..5e6220aa1 100644 --- a/src/sardana/tango/pool/PseudoMotor.py +++ b/src/sardana/tango/pool/PseudoMotor.py @@ -73,6 +73,7 @@ def delete_device(self): pseudo_motor = self.pseudo_motor if pseudo_motor is not None: pseudo_motor.remove_listener(self.on_pseudo_motor_changed) + self.pseudo_motor = None @DebugIt() def init_device(self): diff --git a/src/sardana/tango/pool/test/base_sartest.py b/src/sardana/tango/pool/test/base_sartest.py index da3445655..739519e75 100644 --- a/src/sardana/tango/pool/test/base_sartest.py +++ b/src/sardana/tango/pool/test/base_sartest.py @@ -95,6 +95,8 @@ def setUp(self, pool_properties=None): self.ctrl_list = [] self.elem_list = [] + self.pseudo_ctrl_list = [] + self.pseudo_elem_list = [] try: # physical controllers and elements for sar_type, lib, cls, prefix, postfix, nelem in self.cls_list: @@ -138,58 +140,68 @@ def setUp(self, pool_properties=None): print(e) msg = 'Impossible to create ctrl: "%s"' % (ctrl_name) raise Exception('Aborting SartestTestCase: %s' % (msg)) - self.ctrl_list.append(ctrl_name) + self.pseudo_ctrl_list.append(ctrl_name) for role in roles: elem = role.split("=")[1] if elem not in self.elem_list: - self.elem_list.append(elem) + self.pseudo_elem_list.append(elem) except Exception as e: # force tearDown in order to eliminate the Pool BasePoolTestCase.tearDown(self) print(e) + def _delete_elem(self, elem_name): + # Cleanup eventual taurus devices. This is especially important + # if the sardana-taurus extensions are in use since this + # devices are created and destroyed within the testsuite. + # Persisting taurus device may react on API_EventTimeouts, enabled + # polling, etc. + if elem_name in self.f.tango_alias_devs: + _cleanup_device(elem_name) + try: + if os.name != "nt": + self.pool.DeleteElement(elem_name) + print(elem_name) + except Exception as e: + print(e) + self.dirty_elems.append(elem_name) + + def _delete_ctrl(self, ctrl_name): + # Cleanup eventual taurus devices. This is especially important + # if the sardana-taurus extensions are in use since this + # devices are created and destroyed within the testsuite. + # Persisting taurus device may react on API_EventTimeouts, enabled + # polling, etc. + if ctrl_name in self.f.tango_alias_devs: + _cleanup_device(ctrl_name) + try: + if os.name != "nt": + self.pool.DeleteElement(ctrl_name) + print(ctrl_name) + except: + self.dirty_ctrls.append(ctrl_name) + def tearDown(self): """Remove the elements and the controllers """ - dirty_elems = [] - dirty_ctrls = [] - f = taurus.Factory() + self.dirty_elems = [] + self.dirty_ctrls = [] + self.f = taurus.Factory() + for elem_name in self.pseudo_elem_list: + self._delete_elem(elem_name) + for ctrl_name in self.pseudo_ctrl_list: + self._delete_ctrl(ctrl_name) for elem_name in self.elem_list: - # Cleanup eventual taurus devices. This is especially important - # if the sardana-taurus extensions are in use since this - # devices are created and destroyed within the testsuite. - # Persisting taurus device may react on API_EventTimeouts, enabled - # polling, etc. - if elem_name in f.tango_alias_devs: - _cleanup_device(elem_name) - try: - if os.name != "nt": - self.pool.DeleteElement(elem_name) - except Exception as e: - print(e) - dirty_elems.append(elem_name) - + self._delete_elem(elem_name) for ctrl_name in self.ctrl_list: - # Cleanup eventual taurus devices. This is especially important - # if the sardana-taurus extensions are in use since this - # devices are created and destroyed within the testsuite. - # Persisting taurus device may react on API_EventTimeouts, enabled - # polling, etc. - if ctrl_name in f.tango_alias_devs: - _cleanup_device(ctrl_name) - try: - if os.name != "nt": - self.pool.DeleteElement(ctrl_name) - except: - dirty_ctrls.append(ctrl_name) - + self._delete_ctrl(ctrl_name) _cleanup_device(self.pool_name) BasePoolTestCase.tearDown(self) - if dirty_elems or dirty_ctrls: + if self.dirty_elems or self.dirty_ctrls: msg = "Cleanup failed. Database may be left dirty." + \ - "\n\tCtrls : %s\n\tElems : %s" % (dirty_ctrls, dirty_elems) + "\n\tCtrls : %s\n\tElems : %s" % (self.dirty_ctrls, self.dirty_elems) raise Exception(msg) diff --git a/src/sardana/taurus/core/tango/sardana/pool.py b/src/sardana/taurus/core/tango/sardana/pool.py index 45a5e7821..6483fc626 100644 --- a/src/sardana/taurus/core/tango/sardana/pool.py +++ b/src/sardana/taurus/core/tango/sardana/pool.py @@ -1525,6 +1525,8 @@ def _build(self): tg_attr_validator = TangoAttributeNameValidator() for channel_name, channel_data in list(self.channels.items()): cache[channel_name] = None + if not channel_data.get("enabled", True): + continue data_source = channel_data['source'] params = tg_attr_validator.getUriGroups(data_source) if params is None: