Self-subscription is broken on asyncio device servers
Starting with pytango 9.5.0 subscription from a device server into one of its own attributes is broken when the server runs in asyncio green mode. While the act of subscribing yields a valid Subscription ID, no actual events flow, including the initial event that would present the current value.
The issue doesn't exist with device servers in Synchronous green mode. No other green modes have been tested.
No tests have been made trying to subscribe to a different device.
The following code (I'm sure there are ways to further simplify it) reproduces the problem:
```python
from multiprocessing import Pool
import tango
from tango.test_context import DeviceTestContext
from tango.server import Device, GreenMode, attribute, command
class MySyncDevice(Device):
def init_device(self):
super().init_device()
self.value = 0
self.set_change_event("myAttr", True)
self.this = None
self.sub_id = None
def delete_device(self):
if self.this is not None:
self.this.unsubscribe_event(self.sub_id)
self.this = None
self.sub_id = None
@attribute(abs_change=1)
def myAttr(self) -> int:
return self.value
@myAttr.write
def myAttr(self, value: int) -> None:
self.value = value
self.push_change_event("myAttr", self.value)
@command
def SelfSub(self) -> None:
self.this = tango.get_device_proxy(f"tango://127.0.0.1:{SYNC_DS_PORT}/{SYNC_DEVICE_NAME}#dbase=no")
self.sub_id = self.this.subscribe_event("myAttr", tango.EventType.CHANGE_EVENT, tango.utils.EventCallback())
self.info_stream("Subscriptoin ID is %s", self.sub_id)
class MyAsyncDevice(Device):
green_mode = GreenMode.Asyncio
async def init_device(self):
await super().init_device()
self.value = 0
self.set_change_event("myAttr", True)
self.this = None
self.sub_id = None
async def delete_device(self):
if self.this is not None:
await self.this.unsubscribe_event(self.sub_id)
self.this = None
self.sub_id = None
@attribute(abs_change=1)
def myAttr(self) -> int:
return self.value
@myAttr.write
def myAttr(self, value: int) -> None:
self.value = value
self.push_change_event("myAttr", self.value)
@command
async def SelfSub(self) -> None:
self.this = await tango.get_device_proxy(f"tango://127.0.0.1:{ASYNC_DS_PORT}/{ASYNC_DEVICE_NAME}#dbase=no", green_mode=GreenMode.Asyncio)
self.sub_id = await self.this.subscribe_event("myAttr", tango.EventType.CHANGE_EVENT, tango.utils.EventCallback())
self.info_stream("Subscriptoin ID is %s", self.sub_id)
SYNC_DEVICE_NAME = "test/nodb/mysyncdevice"
SYNC_DS_PORT = 8888
ASYNC_DEVICE_NAME = "test/nodb/myasyncdevice"
ASYNC_DS_PORT = 8889
DEVICE_INFOS = (
(MySyncDevice, SYNC_DEVICE_NAME, SYNC_DS_PORT),
(MyAsyncDevice, ASYNC_DEVICE_NAME, ASYNC_DS_PORT),
)
def run_example(device_info):
device, device_name, port = device_info
with DeviceTestContext(device, device_name=device_name, host='127.0.0.1', port=port) as proxy:
proxy.SelfSub()
proxy.myAttr = 13
if __name__ == "__main__":
print(tango.utils.info())
with Pool(processes=len(DEVICE_INFOS)) as pool:
pool.map(run_example, DEVICE_INFOS)
```
Following are the results from running this on different versions of pytango. Note that for convenience I also switched between different versions of python to use existing pytango/numpy binary wheels.
python 3.10 + pytango 9.4.2 + numpy 1.26: works as intended
```
PyTango 9.4.2 (9, 4, 2)
PyTango compiled with:
Python : 3.10.12
Numpy : 1.21.6
Tango : 9.4.2
Boost : 1.82.0
PyTango runtime is:
Python : 3.10.15
Numpy : 1.26.4
Tango : 9.4.2
...
2026-02-18 10:45:34.947684 TEST/NODB/MYSYNCDEVICE MYATTR#DBASE=NO CHANGE [ATTR_VALID] 0.0
2026-02-18 10:45:34.951010 TEST/NODB/MYASYNCDEVICE MYATTR#DBASE=NO CHANGE [ATTR_VALID] 0.0
2026-02-18T10:45:34,968776+0800 INFO (test.py:34) test/nodb/mysyncdevice Subscriptoin ID is 1
2026-02-18 10:45:34.969519 TEST/NODB/MYSYNCDEVICE MYATTR#DBASE=NO CHANGE [ATTR_VALID] 13.0
2026-02-18T10:45:34,971172+0800 INFO (test.py:67) test/nodb/myasyncdevice Subscriptoin ID is 1
2026-02-18 10:45:34.972530 TEST/NODB/MYASYNCDEVICE MYATTR#DBASE=NO CHANGE [ATTR_VALID] 13.0
```
python 3.10 + pytango 9.5.0 + numpy 1.26: already shows the problem
```
PyTango 9.5.0 (9, 5, 0)
PyTango compiled with:
Python : 3.10.13
Numpy : 1.21.6
Tango : 9.5.0
Boost : 1.82.0
PyTango runtime is:
Python : 3.10.15
Numpy : 1.26.4
Tango : 9.5.0
...
Ready to accept request
Ready to accept request
2026-02-18 10:46:42.639906 TEST/NODB/MYSYNCDEVICE MYATTR#DBASE=NO CHANGE [ATTR_VALID] 0
2026-02-18T10:46:42,657044+0800 INFO (test.py:34) test/nodb/mysyncdevice Subscriptoin ID is 1
2026-02-18T10:46:42,657688+0800 INFO (test.py:67) test/nodb/myasyncdevice Subscriptoin ID is 1
2026-02-18 10:46:42.657689 TEST/NODB/MYSYNCDEVICE MYATTR#DBASE=NO CHANGE [ATTR_VALID] 13
```
python 3.13 + pytango 10.1.3 + numpy 2.2.6 for bleeding edge: still doesn't work
```
PyTango 10.1.3 (10, 1, 3)
PyTango compiled with:
Python : 3.13.12
Numpy : 2.4.2
Tango : 10.1.2
pybind11 : 3.0.1
PyTango runtime is:
Python : 3.13.7
Numpy : 2.2.6
Tango : 10.1.2
PyTango running on:
uname_result(system='Linux', node='DEP66339.uniwa.uwa.edu.au', release='6.17.0-8-generic', version='#8-Ubuntu SMP PREEMPT_DYNAMIC Fri Nov 14 21:44:46 UTC 2025', machine='x86_64')
Ready to accept request
Ready to accept request
2026-02-18 10:42:28.311743 TEST/NODB/MYSYNCDEVICE MYATTR CHANGE SUBSUCCESS [ATTR_VALID] 0
2026-02-18T10:42:28,313580+0800 INFO (test.py:34) test/nodb/mysyncdevice Subscriptoin ID is 1
2026-02-18 10:42:28.314254 TEST/NODB/MYSYNCDEVICE MYATTR CHANGE UPDATE [ATTR_VALID] 13
2026-02-18T10:42:28,316007+0800 INFO (test.py:67) test/nodb/myasyncdevice Subscriptoin ID is 1
```
issue