Crash if attribute read handler does nothing and is called after Attribute::set_value has been called
The following (very odd) pytango script crashes with a SIGSEGV during the read_attributes_5 call:
from tango.server import Device, run, attribute, command
from tango.test_context import DeviceTestContext
class MyDevice(Device):
@command
def SetValue(self) -> None:
self.get_device_attr().get_attr_by_name("attr").set_value(1.0)
@attribute(max_alarm=1.0)
def attr(self) -> float:
return None
with DeviceTestContext(MyDevice) as dp:
dp.SetValue()
print(dp.attr)
If we remove max_alarm=1.0 or the call to SetValue() then we get the expected API_AttrValueNotSet error.
If we return a value from attr() other than None, then we get the value returned without a crash.
Here is the relevant part of the stack trace:
#0 Tango::Attribute::general_check_alarm<double> (this=this@entry=0x7fffac031360,
alarm_type=alarm_type@entry=@0x7fffffffa6f4: Tango::ATTR_ALARM,
min_value=@0x7fffac031430: 7.7108650726560124e-43, max_value=@0x7fffac031438: 1)
at /src/cppTango/src/server/attribute.cpp:2297
#1 0x00007fffe8c266f0 in Tango::Attribute::check_level_alarm (
this=this@entry=0x7fffac031360) at /src/cppTango/src/server/attribute.cpp:2398
#2 0x00007fffe8c26a40 in Tango::Attribute::check_alarm (
this=this@entry=0x7fffac031360) at /src/cppTango/src/server/attribute.cpp:2198
#3 0x00007fffe8cb5c1c in Tango::Device_3Impl::read_attributes_no_except (
this=this@entry=0x7fffac02d020, names=..., aid=...,
second_try=second_try@entry=true, idx=std::vector of length 1, capacity 1 = {...})
at /src/cppTango/src/server/device_3.cpp:1031
#4 0x00007fffe8cd3235 in Tango::Device_5Impl::read_attributes_5 (
this=0x7fffac02d020, names=..., source=<optimized out>, cl_id=...)
at /src/cppTango/src/server/device_5.cpp:342
#5 0x00007fffe8bd0c8a in _0RL_lcfn_6fe2f94a21a10053_84000000 (cd=0x7fffffffb040,
s)vnt=<optimized out>)
at /src/cppTango/build/src/include/tango/idl/tangoSK.cpp:6638
It looks to me like read_attributes_5 is not detecting that the attribute value hasn't been set by the attribute read handler correctly and calling check_alarm when it shouldn't.
The following Catch2 test demonstrates the same behaviour:
template <class Base>
class AttrReadNoSet : public Base
{
public:
using Base::Base;
void init_device() override { }
void read_attr(Tango::Attribute &) override
{
/* Do nothing */
}
void set_value()
{
auto &att = Base::get_device_attr()->get_attr_by_name("attr_read_no_set");
value = 1.0;
att.set_value(&value);
}
static void attribute_factory(std::vector<Tango::Attr *> &attrs)
{
attrs.push_back(new TangoTest::AutoAttr<&AttrReadNoSet::read_attr>("attr_read_no_set", Tango::DEV_DOUBLE));
Tango::UserDefaultAttrProp props;
props.set_max_alarm("10.0");
attrs.back()->set_default_properties(props);
}
static void command_factory(std::vector<Tango::Command *> &cmds)
{
cmds.push_back(new TangoTest::AutoCommand<&AttrReadNoSet::set_value>("SetValue"));
}
private:
Tango::DevDouble value;
};
TANGO_TEST_AUTO_DEV_TMPL_INSTANTIATE(AttrReadNoSet, 6)
SCENARIO("Not setting a value")
{
int idlver = GENERATE(TangoTest::idlversion(6));
GIVEN("a device proxy to a simple IDLv" << idlver << " device")
{
TangoTest::Context ctx{"attr_read_no_set", "AttrReadNoSet", idlver};
auto device = ctx.get_proxy();
REQUIRE(idlver == device->get_idl_version());
WHEN("we read a do nothing attribute")
{
std::string att{"attr_read_no_set"};
Tango::DeviceAttribute da;
REQUIRE_NOTHROW(da = device->read_attribute(att));
THEN("we get a API_AttrValueNotSet exception")
{
using namespace TangoTest::Matchers;
using namespace Catch::Matchers;
double val_read{};
REQUIRE_THROWS_MATCHES(
da >> val_read, Tango::DevFailed, ErrorListMatches(AnyMatch(Reason(Tango::API_AttrValueNotSet))));
}
}
WHEN("we set an attribute value out-of-band")
{
REQUIRE_NOTHROW(device->command_inout("SetValue"));
AND_WHEN("we read the do nothing attribute")
{
std::string att{"attr_read_no_set"};
Tango::DeviceAttribute da;
REQUIRE_NOTHROW(da = device->read_attribute(att));
THEN("we get a API_AttrValueNotSet exception")
{
using namespace TangoTest::Matchers;
double val_read{};
REQUIRE_THROWS_MATCHES(
da >> val_read, Tango::DevFailed, FirstErrorMatches(Reason(Tango::API_AttrValueNotSet)));
}
}
}
}
}
Edited by Thomas Ives