Skip to content

Commit 9eb29d0

Browse files
Add ability to disable PVA server in IOC
There seems to be no need to load the DBD and DSO early, so doing it just before iocInit allows a programmatic switch to enable/disable it.
1 parent e1f104a commit 9eb29d0

File tree

6 files changed

+116
-10
lines changed

6 files changed

+116
-10
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Added:
1616
- `Add int64In/Out record support <../../pull/161>`_
1717
- `Enable setting alarm status of Out records <../../pull/157>`_
1818
- `Adding the non_interactive_ioc function <../../pull/156>`_
19+
- `Allow starting IOC without PVA <../../pull/186>`_
1920

2021
Removed:
2122

Pipfile.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[build-system]
2-
requires = ["setuptools", "wheel", "setuptools_dso>=2.1", "epicscorelibs>=7.0.7.99.1.1a3"]
2+
requires = ["setuptools", "wheel", "setuptools_dso>=2.1", "epicscorelibs>=7.0.7.99.1.2a1"]
33
build-backend = "setuptools.build_meta:__legacy__"

softioc/__init__.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,9 @@
2222
iocshRegisterCommon()
2323
base_dbd_path = os.path.join(epicscorelibs.path.base_path, 'dbd')
2424
dbLoadDatabase('base.dbd', base_dbd_path, None)
25-
dbLoadDatabase('pvxsIoc.dbd', pvxslibs.path.dbd_path, None)
2625
iocStats = os.path.join(os.path.dirname(__file__), "iocStats", "devIocStats")
2726
dbLoadDatabase('devIocStats.dbd', iocStats, None)
2827

29-
ctypes.CDLL(find_dso('pvxslibs.lib.pvxsIoc'), ctypes.RTLD_GLOBAL)
30-
os.environ.setdefault('PVXS_QSRV_ENABLE', 'YES')
3128
if registerRecordDeviceDriver(pdbbase):
3229
raise RuntimeError('Error registering')
3330

softioc/softioc.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
import ctypes
12
import os
23
import sys
34
import atexit
45
from ctypes import *
56
from tempfile import NamedTemporaryFile
67

8+
import pvxslibs.path
9+
from epicscorelibs.ioc import registerRecordDeviceDriver, pdbbase
10+
from setuptools_dso.runtime import find_dso
11+
712
from . import autosave, imports, device
813
from . import cothread_dispatcher
914

@@ -16,14 +21,15 @@ def epicsAtPyExit():
1621
imports.epicsExitCallAtExits()
1722

1823

19-
def iocInit(dispatcher=None):
24+
def iocInit(dispatcher=None, enable_pva=True):
2025
'''This must be called exactly once after loading all EPICS database files.
2126
After this point the EPICS IOC is running and serving PVs.
2227
2328
Args:
2429
dispatcher: A callable with signature ``dispatcher(func, *args)``. Will
25-
be called in response to caput on a record. If not supplied use
26-
`cothread` as a dispatcher.
30+
be called in response to caput on a record. If not supplied uses
31+
``cothread`` as the dispatcher.
32+
enable_pva: Specify whether to enable the PV Access Server in this IOC.
2733
2834
See Also:
2935
`softioc.asyncio_dispatcher` is a dispatcher for `asyncio` applications
@@ -33,6 +39,14 @@ def iocInit(dispatcher=None):
3339
dispatcher = cothread_dispatcher.CothreadDispatcher()
3440
# Set the dispatcher for record processing callbacks
3541
device.dispatcher = dispatcher
42+
43+
if enable_pva:
44+
dbLoadDatabase('pvxsIoc.dbd', pvxslibs.path.dbd_path, None)
45+
ctypes.CDLL(find_dso('pvxslibs.lib.pvxsIoc'), ctypes.RTLD_GLOBAL)
46+
47+
if registerRecordDeviceDriver(pdbbase):
48+
raise RuntimeError('Error registering')
49+
3650
imports.iocInit()
3751
autosave.start_autosave_thread()
3852

tests/test_pvaccess.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import asyncio
2+
from contextlib import nullcontext
3+
4+
import pytest
5+
6+
from conftest import (
7+
aioca_cleanup,
8+
log,
9+
create_random_prefix,
10+
TIMEOUT,
11+
select_and_recv,
12+
get_multiprocessing_context,
13+
)
14+
15+
from softioc import builder, softioc
16+
17+
18+
class TestPVAccess:
19+
"""Tests related to PVAccess"""
20+
21+
record_name = "PVA_AOut"
22+
record_value = 10
23+
24+
def pva_test_func(self, device_name, conn, use_pva):
25+
builder.SetDeviceName(device_name)
26+
27+
builder.aOut(self.record_name, initial_value=self.record_value)
28+
29+
builder.LoadDatabase()
30+
softioc.iocInit(enable_pva=use_pva)
31+
32+
conn.send("R") # "Ready"
33+
log("CHILD: Sent R over Connection to Parent")
34+
35+
# Keep process alive while main thread works.
36+
while (True):
37+
if conn.poll(TIMEOUT):
38+
val = conn.recv()
39+
if val == "D": # "Done"
40+
break
41+
42+
@pytest.mark.asyncio
43+
@pytest.mark.parametrize(
44+
"use_pva,expectation",
45+
[
46+
(True, nullcontext()),
47+
(False, pytest.raises(asyncio.TimeoutError))
48+
]
49+
)
50+
async def test_pva_enable_disable(self, use_pva, expectation):
51+
"""Test that we can enable and disable PVA, perform PVAccess requests
52+
when enabled, and that we can always do Channel Access requests"""
53+
ctx = get_multiprocessing_context()
54+
parent_conn, child_conn = ctx.Pipe()
55+
56+
device_name = create_random_prefix()
57+
58+
process = ctx.Process(
59+
target=self.pva_test_func,
60+
args=(device_name, child_conn, use_pva),
61+
)
62+
63+
process.start()
64+
65+
from aioca import caget
66+
from p4p.client.asyncio import Context
67+
try:
68+
# Wait for message that IOC has started
69+
select_and_recv(parent_conn, "R")
70+
71+
record_full_name = device_name + ":" + self.record_name
72+
73+
ret_val = await caget(record_full_name, timeout=TIMEOUT)
74+
75+
assert ret_val == self.record_value
76+
77+
with expectation as _:
78+
with Context("pva") as ctx:
79+
# Short timeout as, if the above CA connection has happened
80+
# there's no need to wait a very long time for the PVA
81+
# connection
82+
pva_val = await asyncio.wait_for(
83+
ctx.get(record_full_name),
84+
timeout=2
85+
)
86+
assert pva_val == self.record_value
87+
88+
89+
finally:
90+
# Clear the cache before stopping the IOC stops
91+
# "channel disconnected" error messages
92+
aioca_cleanup()
93+
parent_conn.send("D") # "Done"
94+
process.join(timeout=TIMEOUT)

0 commit comments

Comments
 (0)