1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 <2021> 2022 2023 2024 2025 | Index | 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 <2021> 2022 2023 2024 2025 |
<== Date ==> | <== Thread ==> |
---|
Subject: | Re: Weird channel access problem within Python softioc process |
From: | "Cobb, Tom \(DLSLtd,RAL,LSCI\) via Tech-talk" <tech-talk at aps.anl.gov> |
To: | "tech-talk at aps.anl.gov" <tech-talk at aps.anl.gov>, Paul Richards <prichards at keck.hawaii.edu>, "Abbott, Michael (DLSLtd,RAL,LSCI)" <michael.abbott at diamond.ac.uk> |
Date: | Wed, 11 Aug 2021 12:23:56 +0000 |
Hi Paul,
Something to be aware of, the default in pythonSoftIOC is to use
cothread as its concurrency framework. It uses lightweight co-routines instead of OS threads. This means that you must be careful not to block the thread you call
softioc.iocInit() in, otherwise the callbacks from caput and myrecord.set() will end up not being processed. Functions like softioc.interactive_ioc() will co-operatively block, allowing cothread
to service callbacks in the background.
I can suggest a couple of things to try:
Please could you put responses on the
pythonSoftIOC issue that Michael created to track this.
Tom
From: Tech-talk <tech-talk-bounces at aps.anl.gov> on behalf of Paul Richards via Tech-talk <tech-talk at aps.anl.gov>
Sent: 11 August 2021 01:05 To: tech-talk at aps.anl.gov <tech-talk at aps.anl.gov> Subject: Weird channel access problem within Python softioc process All,
At Keck, we have been using the EPICS Python softioc with great success. The recent 3.1 release has made it even more popular and easier to use.
That said, I have found a very odd behavior that I can replicate, relating to the consumption of keywords inside the same process that runs the softioc. Sometimes it makes sense to do it this way, instead of finding a reference to the channel in the IOC and manipulating that.
A simplified example of the problem:
Today I found a “solution” to this that does not make sense: if I start the softioc (the call to softioc.iotInit()) in the first thread, and not in the main process, everything works as expected.
Our engineers are unable to explain this behavior. I hope there’s someone here on the list that might know why!
Example code that demonstrates the failure: --------------------
import os import time import threading
os.environ['EPICS_CA_SERVER_PORT'] = '5100' os.environ['EPICS_CA_ADDR_LIST'] = 'localhost:5100' os.environ['EPICS_CA_AUTO_ADDR_LIST'] = 'NO'
try: import epicscorelibs.path.pyepics except ImportError: pass import epics
from softioc import softioc, builder
# Create a long record for this demo long = builder.longOut('prefix:long', initial_value=1) builder.LoadDatabase() softioc.iocInit()
def thread1(): while True: val = long.get() long.set(val + 1) print(f'thread1: {val}') time.sleep(1)
def thread2(): pv = epics.PV('prefix:long')
while True: val = pv.get() print(f'thread2: {val}') time.sleep(1)
t1 = threading.Thread(target=thread1, daemon=True) t2 = threading.Thread(target=thread2, daemon=True)
t1.start() t2.start()
time.sleep(10)
Example that works; note the only change is the movement of the .iocInit() line down into thread1: --------------------
import os import time import threading
os.environ['EPICS_CA_SERVER_PORT'] = '5100' os.environ['EPICS_CA_ADDR_LIST'] = 'localhost:5100' os.environ['EPICS_CA_AUTO_ADDR_LIST'] = 'NO'
try: import epicscorelibs.path.pyepics except ImportError: pass import epics
from softioc import softioc, builder
# Create a long record for this demo long = builder.longOut('prefix:long', initial_value=1) builder.LoadDatabase()
def thread1(): softioc.iocInit()
while True: val = long.get() long.set(val + 1) print(f'thread1: {val}') time.sleep(1)
def thread2(): pv = epics.PV('prefix:long')
while True: val = pv.get() print(f'thread2: {val}') time.sleep(1)
t1 = threading.Thread(target=thread1, daemon=True) t2 = threading.Thread(target=thread2, daemon=True)
t1.start() t2.start()
time.sleep(10)
The output when it fails:
$ python3 ./demoFailingDaemon.py Starting iocInit ############################################################################ ## EPICS 7.0.6.0 ## Rev. 7.0.6.99.1.0 ############################################################################ iocRun: All initialization complete thread1: 1 thread1: 2 thread1: 3 thread2: None thread1: 4 thread1: 5 thread2: None thread1: 6 thread1: 7 thread1: 8 thread2: None thread1: 9 thread1: 10 thread1: 11
The output when it works:
$ python3 ./demoWorkingDaemon.py Starting iocInit ############################################################################ ## EPICS 7.0.6.0 ## Rev. 7.0.6.99.1.0 ############################################################################ iocRun: All initialization complete thread1: 1 thread2: 2 thread1: 2 thread2: 3 thread1: 3 thread2: 4 thread1: 4 thread2: 5 thread1: 5 thread2: 6 thread1: 6 thread2: 7 thread1: 7 thread2: 8 thread1: 8 thread2: 9 thread1: 9 thread2: 10 thread1: 10 thread2: 11
Paul
-- This e-mail and any attachments may contain confidential, copyright and or privileged material, and are for the use of the intended addressee only. If you are not the intended addressee or an authorised recipient of the addressee please notify us of receipt by returning the e-mail and do not use, copy, retain, distribute or disclose the information in or attached to the e-mail. |