paulscherrerinstitute / pcaspy

Portable Channel Access Server in Python
BSD 3-Clause "New" or "Revised" License
32 stars 24 forks source link

PVs multithreading damaging (but not breaking completely) IOC initialization #74

Closed MarcoMontevechi closed 2 years ago

MarcoMontevechi commented 2 years ago

Good morning. Im creating an IOC for control of a motor controller equipment called MCPS36. The read and write methods of the Driver class have been modified to call other methods according to which PV i want to caget or caput. The methods have been tested in a sequentially executed script and work well. All PVs associated with the IOC have the scan property set to 0 so that multithreading is avoided and finally a PV called UpdateAll with the scan property set to 1 is defined so that its update function (triggered by the scan property) updates all PVs one by one with the SetParam method.

This avoids multithreading problems during execution but still.. in the IOC initialization i get errors like:

Exception in thread Thread-3: Traceback (most recent call last): File "/usr/lib/python3.5/threading.py", line 914, in _bootstrap_inner self.run() File "/usr/lib/python3.5/threading.py", line 862, in run self._target(*self._args, **self._kwargs) File "/usr/local/lib/python3.5/dist-packages/pcaspy/driver.py", line 515, in scan newValue = driver.read(self.info.reason) File "/usr/local/epics-nfs/apps/specs-mcps36-pcaspy/master/MCPS36.py", line 106, in read returner = eval(function) File "", line 1, in File "/usr/local/epics-nfs/apps/specs-mcps36-pcaspy/master/MCPS36.py", line 576, in GetUpdateAll self.setParam(reason=new_reason,value=returner) File "/usr/local/lib/python3.5/dist-packages/pcaspy/driver.py", line 157, in setParam pv = manager.pvs[self.port][reason] KeyError: 'BL:H:EQ:AX:RawOffset'

As if the pv BL:H:EQ:AX:RawOffset didnt exist. But soon after the initialization i can caget and caput into the PV normally and it works just fine. Is the PV being processed before its initialization? If yes, why?

If it helps: the server is initialized by the following lines of code:

def main(prefix, ip, port, translator): pvdb = read_yaml(os.path.join(sys.path[0], "pvdb.yaml")) motors = read_yaml(translator) server = pcaspy.SimpleServer()

for motor in motors:
    suffix = {"{}:{}".format(motor, k):v for k,v in pvdb.items()}
    server.createPV(prefix, suffix)

MCPS36.MCPS36Driver(ip, port, motors)
while True:
    server.process(0.1)

One more question since im already here: is there a specific advantage of multithreading the PVs? Why doesnt the code make just one thread per database like EPICS?

xiaoqiangwang commented 2 years ago

One more question since im already here: is there a specific advantage of multithreading the PVs? Why doesnt the code make just one thread per database like EPICS?

No. It was not a good pattern. And unfortunately it is used as an example in the tutorial, which often lead to the problem.

For your application, I would not use "scan" fields and leave out read method, but update the PVs in a thread,

def __init__(self):
    ....
    self.tid = threading.Thread(target=self.polling, daemon=True)
    self.tid.start()
    ....
def polling(self)
    while True:
         # poll controller
         # call self.setParam
         time.sleep(1)
MarcoMontevechi commented 2 years ago

It makes sense, thanks!

Im not used to using git as a community so im not sure how this is seen here, but if i manage to create a version of pcaspy which creates a single thread for the PVs, can i create a branch and push into it? Has someone tried to do this before?

xiaoqiangwang commented 2 years ago

It is not worth efforts to sort out the scan implementation. As practically, the pattern mentioned above is much more used.

In general, to contribute a git project, you can fork, modify and create a pull request in the end, see e..g https://www.dataschool.io/how-to-contribute-on-github