WIP state for Hugo to potentially fix a segfault

This commit is contained in:
2026-04-20 14:24:50 +02:00
parent 87003df25c
commit 28e5cd4ec4
7 changed files with 771 additions and 163 deletions
+19 -4
View File
@@ -1,10 +1,25 @@
record(longin, "TEST:INT") {
field(DESC, "Example record")
record(longin, "MEPICSCA:TEST:LONGIN") {
field(VAL, 42)
field(HOPR, 100)
field(LOPR, 0)
field(PINI, "YES")
}
record(ai, "TEST:DOUBLE") {
field(DESC, "Example record")
record(ai, "MEPICSCA:TEST:AI") {
field(VAL, 84)
}
record(stringin, "MEPICSCA:TEST:STRINGIN") {
field(VAL, "MyTestString")
}
record(waveform, "MEPICSCA:TEST:WAVEFORM") {
field(FTVL, "CHAR")
field(NELM, "200")
}
record(bo, "MEPICSCA:TEST:BO") {
field(VAL, 1)
field(ZNAM, "OFF")
field(ONAM, "ON")
}
+3
View File
@@ -3,3 +3,6 @@
dbLoadRecords("$(IOCDIR)/record.db")
iocInit()
# Initialize the waveform record
dbpf("MEPICSCA:TEST:WAVEFORM", "ABC")
+2 -1
View File
@@ -7,4 +7,5 @@ export EPICS_CA_ADDR_LIST=127.0.0.1
cd "$(dirname "$0")"
export IOCDIR=$(pwd)
./st.cmd
exec ./st.cmd
+324 -51
View File
@@ -2,7 +2,10 @@
#include "m_epics_ca.h"
#include <cassert>
#include <chrono>
#include <cstring>
#include <fcntl.h>
#include <iostream>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
@@ -10,7 +13,7 @@
#include <sys/wait.h>
#include <unistd.h>
#define CHECK(cond) \
#define ASSERT(cond) \
do { \
if (!(cond)) { \
std::cerr << "Test failed: " #cond << std::endl; \
@@ -21,9 +24,9 @@
#define EQUAL(left, right) \
do { \
if (!(left == right)) { \
std::cerr << "\nTest failed: " << (left) << " (" << #left \
<< ") != " << (right) << " (" << #right << *")" \
<< std::endl; \
std::cerr << "\nTest failed at line " << __LINE__ << ": " \
<< (left) << " (" << #left << ") != " << (right) << " (" \
<< #right << *")" << std::endl; \
return 1; \
} \
} while (0)
@@ -31,12 +34,24 @@
class IocProcess {
public:
explicit IocProcess(const std::string &path) {
/*
Important note: DO NOT USE setpgid HERE!
Even though it looks "cleaner" than the pkill solution in the destructor
below (because you could just terminate the group), this seems to cause
trouble deep within EPICS, causing the server not to be reachable
anymore with caget.
*/
_pid = fork();
if (_pid == 0) {
// Hide IOC output
int devnull = open("/dev/null", O_WRONLY);
dup2(devnull, STDOUT_FILENO);
close(devnull);
// Start the IOC from the child process
setpgid(0, 0);
execl(path.c_str(), path.c_str(), (char *)nullptr);
execl(path.c_str(), path.c_str(), (char *)NULL);
perror("execl failed");
_exit(1);
}
@@ -44,17 +59,13 @@ class IocProcess {
if (_pid < 0) {
throw std::runtime_error("fork failed");
}
// Set the ID of the entire process group so it can be torn down in the
// destructor later. This ensures IOC teardown.
setpgid(_pid, _pid);
}
~IocProcess() {
if (_pid <= 0)
return;
// Kill the entire process group due to the -1
kill(-_pid, SIGTERM);
kill(_pid, SIGTERM);
// Give the IOC 50 * 100 * 1000 = 5 seconds time for graceful
// termination.
@@ -66,25 +77,78 @@ class IocProcess {
usleep(100 * 1000);
}
// Force kill group if needed
kill(-_pid, SIGKILL);
waitpid(_pid, nullptr, 0);
kill(_pid, SIGKILL);
}
private:
pid_t _pid = -1;
};
void handler(int sig) { printf("Received signal: %d\n", sig); }
int main() {
using clock = std::chrono::steady_clock;
signal(SIGPIPE, handler);
signal(SIGTERM, handler);
signal(SIGINT, handler);
signal(SIGHUP, handler);
// Client setting for local testing
setenv("EPICS_CA_AUTO_ADDR_LIST", "NO", 1);
setenv("EPICS_CA_ADDR_LIST", "127.0.0.255", 1);
setenv("EPICS_CA_ADDR_LIST", "127.0.0.1", 1);
// Create a new instance of the mEpicsCa bus driver and use it to
// interact with the spawned IOC.
auto int_ca = mEpicsCa<int>("MEPICSCA:TEST:LONGIN", false);
auto int_hopr_ca = mEpicsCa<int>("MEPICSCA:TEST:LONGIN.HOPR", false);
auto double_ca = mEpicsCa<double>("MEPICSCA:TEST:AI", false);
auto string_ca = mEpicsCa<std::string>("MEPICSCA:TEST:STRINGIN", false);
auto waveform_ca = mEpicsCa<std::string>("MEPICSCA:TEST:WAVEFORM", false);
auto bo_ca = mEpicsCa<uint16_t>("MEPICSCA:TEST:BO", false);
// Ending s indicates subscription mechanism is active
auto int_cas = mEpicsCa<int>("MEPICSCA:TEST:LONGIN", true);
// Generic status variable which is reused for the error codes throughout
// the tests.
int status = CM_SUCCESS;
{
// =====================================================================
// Tests before IOC is running
// =====================================================================
// Drivers are not connected
ASSERT((!int_ca.connected()));
// Cached values are invalid
ASSERT((!int_cas.cached().has_value()));
// Attempting to write or read a value results in an error. stdout is
// suppressed for the duration of this test
int saved_stdout = dup(STDOUT_FILENO);
int devnull = open("/dev/null", O_WRONLY);
dup2(devnull, STDOUT_FILENO);
close(devnull);
int int_val = 0;
status = int_ca.get(&int_val);
EQUAL(status, FE_ERR_DRIVER);
double new_double_val = 4.2;
status = double_ca.put(&new_double_val);
EQUAL(status, FE_ERR_DRIVER);
// Restore stdout
dup2(saved_stdout, STDOUT_FILENO);
close(saved_stdout);
}
// Scope everything so the IOC gets torn down by the IocProcess destructor
// before reporting that the tests were successfull.
{
using clock = std::chrono::steady_clock;
// Start the test IOC
std::string path = __FILE__;
@@ -92,11 +156,6 @@ int main() {
std::string ioc_exe = dir + "/ioc/startioc";
IocProcess proc = IocProcess(ioc_exe);
// Create a new instance of the mEpicsCa bus driver and use it to
// interact with the spawned IOC.
mEpicsCa<int> int_ca = mEpicsCa<int>("TEST:INT");
mEpicsCa<double> double_ca = mEpicsCa<double>("TEST:DOUBLE");
// Wait for the IOC to become available
double timeout = 20.0;
@@ -111,46 +170,260 @@ int main() {
auto now = clock::now();
double elapsed = std::chrono::duration<double>(now - start).count();
if (elapsed > timeout) {
printf(
"Could not connect to IOC in %.3lf seconds. The IOC likely "
"failed to start.\n",
timeout);
printf("Could not connect to IOC in %.3lf seconds. The IOC "
"likely "
"failed to start.\n",
timeout);
return -1;
}
};
int status = ECA_NORMAL;
// =====================================================================
// No subscription
// =====================================================================
// Read an integer from a record
int int_val = 0;
status = int_ca.get(&int_val);
EQUAL(status, ECA_NORMAL);
EQUAL(int_val, 42);
{
// -----------------------------------------------------------------
// Test get
// -----------------------------------------------------------------
// Read a double from a record
double double_val = 0.0;
status = double_ca.get(&double_val);
EQUAL(status, ECA_NORMAL);
EQUAL(double_val, 84.0);
// Read an integer from a record
int int_val = 0;
status = int_ca.get(&int_val);
EQUAL(status, CM_SUCCESS);
EQUAL(int_val, 42);
// Write an integer to a record and read it to check
int new_int_val = 21;
status = int_ca.put(&new_int_val);
EQUAL(status, ECA_NORMAL);
status = int_ca.get(&int_val);
EQUAL(status, ECA_NORMAL);
EQUAL(int_val, new_int_val);
// Read from a different field
status = int_hopr_ca.get(&int_val);
EQUAL(status, CM_SUCCESS);
EQUAL(int_val, 100);
// Write a double to a record and read it to check
double new_double_val = 4.2;
status = double_ca.put(&new_double_val);
EQUAL(status, ECA_NORMAL);
status = double_ca.get(&double_val);
EQUAL(status, ECA_NORMAL);
EQUAL(double_val, new_double_val);
// Read a double from a record
double double_val = 0.0;
status = double_ca.get(&double_val);
EQUAL(status, CM_SUCCESS);
EQUAL(double_val, 84.0);
// Read a string from a stringin record using a string
std::string string_val = "";
status = string_ca.get(&string_val);
EQUAL(status, CM_SUCCESS);
EQUAL(string_val, "MyTestString");
// Read a string from a stringin record using a char array
char char_arr_val[40] = {0};
status = string_ca.get(char_arr_val, sizeof(char_arr_val));
EQUAL(status, CM_SUCCESS);
EQUAL(std::string(char_arr_val), "MyTestString");
// Read a string from a waveform record
status = waveform_ca.get(&string_val);
EQUAL(status, CM_SUCCESS);
EQUAL(string_val, "ABC");
// Read an enum as a value and as a string
uint16_t short_uint_val = 0;
status = bo_ca.get(&short_uint_val);
EQUAL(status, CM_SUCCESS);
EQUAL(short_uint_val, 1);
status = bo_ca.get(&string_val);
EQUAL(status, CM_SUCCESS);
EQUAL(string_val, "ON");
// -----------------------------------------------------------------
// Test put
// -----------------------------------------------------------------
// Write an integer to a record and read it to check
int new_int_val = 21;
status = int_ca.put(&new_int_val);
EQUAL(status, CM_SUCCESS);
status = int_ca.get(&int_val);
EQUAL(status, CM_SUCCESS);
EQUAL(int_val, new_int_val);
// Write to the limit field of the longin record
new_int_val = 40;
status = int_hopr_ca.put(&new_int_val);
EQUAL(status, CM_SUCCESS);
int_val = 0; // Overwrite with other value
status = int_hopr_ca.get(&int_val);
EQUAL(status, CM_SUCCESS);
EQUAL(int_val, new_int_val);
// Write a double to a record and read it to check
double new_double_val = 4.2;
status = double_ca.put(&new_double_val);
EQUAL(status, CM_SUCCESS);
double_val = 0; // Overwrite with other value
status = double_ca.get(&double_val);
EQUAL(status, CM_SUCCESS);
EQUAL(double_val, new_double_val);
// Write a string to the stringin record using a string
std::string new_string_val = "NewStringValue";
status = string_ca.put(&new_string_val);
EQUAL(status, CM_SUCCESS);
string_val = ""; // Overwrite with other value
status = string_ca.get(&string_val);
EQUAL(status, CM_SUCCESS);
EQUAL(string_val, new_string_val);
// Write a string to the stringin record using a char array
char new_char_arr_val[40] = "NewCharArrValue";
status = string_ca.put(new_char_arr_val,
(u_long)strlen(new_char_arr_val));
EQUAL(status, CM_SUCCESS);
char_arr_val[0] = 0; // Overwrite with other value
status = string_ca.get(char_arr_val, (u_long)strlen(char_arr_val));
EQUAL(status, CM_SUCCESS);
EQUAL(std::string(char_arr_val), std::string(new_char_arr_val));
// Write to an enum with an integer value
short_uint_val = 0;
status = bo_ca.put(&short_uint_val);
EQUAL(status, CM_SUCCESS);
short_uint_val = 2; // Overwrite with other value
status = bo_ca.get(&short_uint_val);
EQUAL(status, CM_SUCCESS);
EQUAL(short_uint_val, 0);
status = bo_ca.get(&string_val);
EQUAL(status, CM_SUCCESS);
EQUAL(string_val, "OFF");
// Write to an enum with a string
std::string new_enum_string = "ON";
status = bo_ca.put(&new_enum_string);
EQUAL(status, CM_SUCCESS);
status = bo_ca.get(&short_uint_val);
EQUAL(status, CM_SUCCESS);
EQUAL(short_uint_val, 1);
status = bo_ca.get(&string_val);
EQUAL(status, CM_SUCCESS);
EQUAL(string_val, "ON");
// Attempt to write an invalid string. stdout is suppressed for the
// duration of this test
int saved_stdout = dup(STDOUT_FILENO);
int devnull = open("/dev/null", O_WRONLY);
dup2(devnull, STDOUT_FILENO);
close(devnull);
new_enum_string = "INVALID";
status = bo_ca.put(&new_enum_string);
EQUAL(status, FE_ERR_DRIVER); // Attempted to write invalid variant!
// Assert that nothing has changed in the record
status = bo_ca.get(&short_uint_val);
EQUAL(status, CM_SUCCESS);
EQUAL(short_uint_val, 1);
status = bo_ca.get(&string_val);
EQUAL(status, CM_SUCCESS);
EQUAL(string_val, "ON");
// Restore stdout
dup2(saved_stdout, STDOUT_FILENO);
close(saved_stdout);
}
// =====================================================================
// With subscription
// =====================================================================
{
int int_val = 0;
int new_int_val = 13;
// Initial status of cached
ASSERT(int_cas.cached().has_value());
status = int_cas.get(&int_val);
EQUAL(status, CM_SUCCESS);
EQUAL(int_val, 21); // Value which was put in the record by int_ca
status = int_cas.put(&new_int_val);
EQUAL(status, CM_SUCCESS);
// Wait a bit to allow the subscription thread to update
double timeout = 5.0;
auto start = clock::now();
while (true) {
// Assert that the cached value has changed
status = int_cas.get(&int_val);
EQUAL(status, CM_SUCCESS);
if (int_val == new_int_val)
break;
ca_pend_event(0.1);
auto now = clock::now();
double elapsed =
std::chrono::duration<double>(now - start).count();
if (elapsed > timeout) {
printf("Failed to update int_cas value in %lf seconds.\n",
timeout);
return -1;
}
};
}
}
printf("\nTest was successful\n");
// Wait for the IOC to go offline
double timeout = 20.0;
auto start = clock::now();
while (true) {
if (!int_ca.connected()) {
break;
}
ca_pend_event(0.1);
auto now = clock::now();
double elapsed = std::chrono::duration<double>(now - start).count();
if (elapsed > timeout) {
printf("Could not connect to IOC in %.3lf seconds. The IOC "
"likely "
"failed to start.\n",
timeout);
return -1;
}
};
{
// =====================================================================
// Tests after IOC has stopped
// =====================================================================
// Drivers are not connected
ASSERT((!int_ca.connected()));
// Cached values are invalid
ASSERT((!int_cas.cached().has_value()));
// Attempting to write or read a value results in an error. stdout
// is suppressed for the duration of this test
int saved_stdout = dup(STDOUT_FILENO);
int devnull = open("/dev/null", O_WRONLY);
dup2(devnull, STDOUT_FILENO);
close(devnull);
int int_val = 0;
status = int_ca.get(&int_val);
EQUAL(status, FE_ERR_DRIVER);
double new_double_val = 4.2;
status = double_ca.put(&new_double_val);
EQUAL(status, FE_ERR_DRIVER);
// Restore stdout
dup2(saved_stdout, STDOUT_FILENO);
close(saved_stdout);
}
printf("Test was successful.\n");
return 0;
}