Raise when a device is used before DeviceImpl.__init__

TL;DR

  • Using a device in its __init__ before the base DeviceImpl.__init__ has run now raises RuntimeError: Tango device used before DeviceImpl.__init__() was called: call the base class __init__ first. Previously the call ran on unconstructed memory and returned a wrong value, raised an unrelated error (MemoryError, UnicodeDecodeError, ValueError) or segfaulted.
  • Applies to every low-level device class and to the high-level tango.server.Device.
  • The repr() of such a device shows it as <Class>(unknown) instead of raising.

Closes #792 (closed)

Key Facts

  • Root cause: when an instance has no C++ object yet, pybind11's generic caster lazily allocates one with the type's operator_new and passes that raw, unconstructed memory to the bound function as the device. pybind11 only checks that the base __init__ was called after the subclass __init__ returns.
  • The fix replaces operator_new in pybind11's per-type info (py::detail::get_type_info(...)->operator_new) with a function that throws, for DeviceImpl and Device_2Impl..Device_6Impl. pybind11 reads this field only for that lazy allocation, so there is no cost for constructed devices. It relies on a pybind11::detail field, in the same way fix_dynamic_attr_dealloc already does. Review hotspot.
  • The check is on the device argument, not on individual methods. Bound methods, Python helpers that call into them (e.g. get_device_properties(), which calls get_name()), and bound functions taking a device argument (e.g. AutoTangoMonitor(dev)) all raise the same error.
  • The allocation that used to leak in this situation is no longer made, so the instance stays empty. A device that catches the error and then calls the base __init__ starts normally (covered by a test).
  • DServer is not covered: it has no Python constructor, so Python cannot create an instance without a C++ object.

Caveats

  • This does not make get_device_properties() before the base __init__ work: such code now gets the RuntimeError instead of MemoryError. Restoring the 10.3.1 behaviour for classes without device properties is handled separately in !1087 (merged) (#791 (closed)).

Merge request reports

Loading
Loading