EPICS Controls Argonne National Laboratory

Experimental Physics and
Industrial Control System

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: RFC: add a mechanism to call epicsExit when signals are received
From: Érico Nogueira Rolim via Tech-talk <tech-talk at aps.anl.gov>
To: "tech-talk at aps.anl.gov" <tech-talk at aps.anl.gov>
Date: Wed, 2 Apr 2025 20:42:09 +0000
Hi folks!

Right now, soft IOCs running on general purpose OSs can be terminated
using synchronous mechanisms (e.g. exit in iocsh or Restart PV from
IOCStats), or asynchronous mechanisms (usually signals, either from
procServ, systemd, or a container runtime). For synchronous termination,
epicsExit is run, and executes functions registered with epicsAtExit;
this means that drivers which have cleanup requirements (usually
removing some file or relinquishing some handle) can register those
cleanup routines, and they run during synchronous termination. [1]
mentions such a need, and I know that when using ADAravis, it's best to
relinquish write-access to a camera before exiting (though that isn't
done right now IIRC), otherwise it's necessary to wait for it to
timeout; I have also observed this pattern on some internal device drivers.

[1] https://epics.anl.gov/tech-talk/2009/msg01000.php 

This creates a problem for driver developers which want to cover all
cases, because not only do they need to register cleanup functions with
epicsAtExit, but also add signal handlers (which means that they now
"own" that signal handler, which also creates problems if we wish to
have more than one device driver on a single IOC). They must also
implement these signal handlers safely: very few functions are
async-safe and can be executed in a signal handler, and epicsExit
definitely isn't; so their signal handlers should basically only trigger
an event or set a flag that their normal program flow can handle
synchronously in order to then call epicsExit. If they implement signal
handling in such a manner, they have the advantage that they only need
to call epicsExit, because their atExit functions will be already be called.

Given this scenario, I would like to propose adding a function to
epics-base which launches a thread listening to an exitEvent before
calling epicsExit, and registers a signal handler to trigger this event.
I have attached an initial implementation, which implements the function
below. It only launches the thread if a signal handler is registered, so
users don't have to pay the cost if it's not needed.


/** Registers a signal handler to safely exit the application using
epicsExit()
  * when the signal identified by signum is received. Returns 0 on
success. */
int epicsExitOnSignal(int signum);


One option would be to allow driver support code to call
epicsExitOnSignal, but I don't think drivers actually know what sort of
signal a program may receive (there are many ways to launch and stop
IOCs, after all). Another option, which I prefer over the previous one,
would be to add an iocsh function, which can simply be called in the
relevant startup scripts. Alternatively, signal handlers could be
registered for all relevant signals (SIGTERM, SIGINT?) by default, and
an iocsh function or variable could exist to disable this functionality.

I am comfortable with suggesting this mechanism as a default
functionality because it runs epicsExit on a thread that's out of
control of device drivers, which is exactly the same thing that happens
when one runs "exit" in iocsh, so all drivers should already be ready
for such situations.

In summary, this change would simplify the implementation of cleanup
code, and avoid footguns with signal handling, by having a single path
for process termination; and it would do so using the same program flow
as the battle-tested exit command from iocsh. I don't understand process
lifecycles on Windows, and that's not addressed by this proposal at all.
I don't know if it only makes sense to expose this functionality on
POSIX, or if it could somehow be shared.

I would love to hear your thoughts on this idea, if it fits inside
epics-base, and opinions on how it should be used (by driver developers,
by users in startup scripts, or by default).


Cheers,

Érico

Aviso Legal: Esta mensagem e seus anexos podem conter informações confidenciais e/ou de uso restrito. Observe atentamente seu conteúdo e considere eventual consulta ao remetente antes de copiá-la, divulgá-la ou distribuí-la. Se você recebeu esta mensagem por engano, por favor avise o remetente e apague-a imediatamente.

Disclaimer: This email and its attachments may contain confidential and/or privileged information. Observe its content carefully and consider possible querying to the sender before copying, disclosing or distributing it. If you have received this email by mistake, please notify the sender and delete it immediately.
#include <signal.h>

#include <epicsEvent.h>
#include <epicsExit.h>
#include <epicsStdio.h>
#include <epicsThread.h>

#include <epicsExport.h>

class epicsSignalExit: epicsThreadRunable {
    epicsEvent exitEvent;
    epicsThread thread;

    void run() override;
    epicsSignalExit();

public:
    void trigger();
    static epicsSignalExit *getInstance();
};

/* priority must be high, at this point we want the application to exit */
epicsSignalExit::epicsSignalExit():
    thread(*this, "signalExit", epicsThreadStackMedium, epicsThreadPriorityHigh)
{
    thread.start();
}

void epicsSignalExit::run()
{
    exitEvent.wait();
    fprintf(stderr, "signal received, calling epicsExit...\n");
    epicsExit(0);
}

void epicsSignalExit::trigger()
{
    exitEvent.trigger();
}

epicsSignalExit *epicsSignalExit::getInstance()
{
    static epicsSignalExit signalExit{};
    return &signalExit;
}

static void epicsSignalExitHandler(int signum)
{
    epicsSignalExit::getInstance()->trigger();
}

int epicsExitOnSignal(int signum)
{
    /* make sure the signalExit object is created */
    epicsSignalExit::getInstance();

    return signal(signum, epicsSignalExitHandler) == SIG_ERR;
}

Replies:
Re: RFC: add a mechanism to call epicsExit when signals are received Michael Davidsaver via Tech-talk
Re: RFC: add a mechanism to call epicsExit when signals are received Ralph Lange via Tech-talk

Navigate by Date:
Prev: Stream2 support in ADEiger Mark Rivers via Tech-talk
Next: Re: RFC: add a mechanism to call epicsExit when signals are received Michael Davidsaver via Tech-talk
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
Navigate by Thread:
Prev: Re: Stream2 support in ADEiger Yendell, Gary (DLSLtd, RAL, LSCI) via Tech-talk
Next: Re: RFC: add a mechanism to call epicsExit when signals are received Michael Davidsaver via Tech-talk
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
ANJ, 03 Apr 2025 Valid HTML 4.01! · Home · News · About · Base · Modules · Extensions · Distributions ·
· Download · Search · IRMIS · Talk · Documents · Links · Licensing ·