1

I am working with a Python script that runs in a infinite while loop and acquires data from a remote instrument using a socket (Remoteserver) connection over vx11. I want to be able to terminate the script gracefully using the CTRL+C command (SIGINT), which should trigger a routine to acquire the remaining data before breaking the process.

I am aware that I can handle the keyboard interrupt with try: ... while Loop... except KeyboardInterrupt: or finally:, or by using signal.signal(signal.SIGINT, sigint_handle), but the problem I am facing is that when I use CTRL+C to terminate the script, the socket connection to the remote host gets disconnected immediately, preventing me from sending any further commands to the instrument in the acquisition routine.

Is there a way to prevent the disconnection from happening while still being able to terminate the script with CTRL+C and acquire the remaining data? I suspect that CTRL+C is being sent to all running jobs, and I would like to have a kind of discipline where only the "foreground" process is terminated first.

This is not working, the Remotehost (vx11) is broke within the first line of sigint_handler already:

import signal
import time
import vxi11

class Program:
    def __init__(self, ip_host):
        self.instr= vxi11.Instrument(ip_host)
        signal.signal(signal.SIGINT, self.sigint_handler)

    def run(self):
        while self.instr.ask("some commmand..") == True
            print("Running...")
            time.sleep(1)

        data = self.instr.ask("some commmand..")
        # etc. ... save data
        print("Programm end...")

    def sigint_handler(self, signum, frame):
        print("CTRL+C recieved.")
        data = self.instr.ask("some commmand..")
        # etc. ... save data
        print("Programm end...")
            

if __name__ == '__main__':
    ip_host = "1.1.1.1"  
    program = Program(ip_host)
    program.run()
Lion
  • 11
  • 2
  • Why do you need to keep control c as a way to gracefully close it? Control c should be used to kill it in case of an error, you could listen for some other input and terminate gracefully on that instead. – SupaMaggie70 b Mar 30 '23 at 13:31
  • Thank you for the response! I know that real gracefully closure is done differently. But I'm integrating the script as a smaller subprocess, and in a larger parent process, other scripts are closed using subprocess.send_command(signal.SIGINT). My question was whether I can do the same with the above example, or if I need to adjust all other subprocesses as well. What would be the best termination signal, SIGTERM? – Lion Mar 30 '23 at 13:57
  • This may be able to help you in that case: https://stackoverflow.com/questions/1112343/how-do-i-capture-sigint-in-python#1112350 – SupaMaggie70 b Mar 30 '23 at 14:30

2 Answers2

0

From How do I capture SIGINT in Python?:

import sys

def signal_handler(sig, frame):
    print('You pressed Ctrl+C!')
    sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)
print('Press Ctrl+C')
signal.pause()

Using python’s signal library, you can register your own handler/listener for the interrupt signal.

SupaMaggie70 b
  • 322
  • 3
  • 8
0

From a little playing around, it seems like the signal handler basically gets called wherever you are in the code when the signal is raised. If you try to call ask() from the signal handler when you were actually in an ask() call already, then you're making a recursive call to ask(), which may be why your version is failing.

You could try to set a flag value, like so:

import signal
import time
from multiprocessing import Process, Event


class Program:
    def __init__(self, ip_host):
        self.instr = iter(range(20))
        self.time_to_leave = False

    def run(self):
        signal.signal(signal.SIGINT, self.sigint_handler)

        x = next(self.instr)
        while x < 10 and not self.time_to_leave:
            print("Running...", x)
            time.sleep(1)
            x = next(self.instr)

        x = next(self.instr)
        # etc. ... save data
        print("Programm end...", x)

    def sigint_handler(self, signum, frame):
        self.time_to_leave = True


if __name__ == "__main__":
    ip_host = "1.1.1.1"
    program = Program(ip_host)
    program.run()

If that doesn't work, you could use multiprocessing to make a child process that ignores SIGINT entirely and exits when the parent tells it to. It would look something like this:

import signal
import time
from multiprocessing import Process, Event


class Program:
    def __init__(self, ip_host):
        self.instr = iter(range(20))
        self.event = Event()

    def run(self):
        # Ignore SIGINT in the child
        signal.signal(signal.SIGINT, signal.SIG_IGN)

        x = next(self.instr)
        while x < 10 and not self.event.is_set():
            print("Running...", x)
            time.sleep(1)
            x = next(self.instr)

        x = next(self.instr)
        # etc. ... save data
        print("Programm end...", x)


if __name__ == "__main__":
    ip_host = "1.1.1.1"
    program = Program(ip_host)
    process = Process(target=program.run)

    def sigint_handler(signum, frame):
        print("CTRL+C recieved.")
        program.event.set()

    signal.signal(signal.SIGINT, sigint_handler)

    process.start()
    process.join()
FiddleStix
  • 3,016
  • 20
  • 21