|
Hi,
I'm also having trouble with signals handling. I want to create a Tango device for launching subprocesses and controling them.
I start my process from a command with Popen When launched from a console, this process can catch a SIGINT signal for calling a shutdown function. When launched from the Tango device, and when the SIGINT signal is sent from a device command emergency, the process don't catch it.
Here is the code to illustrate:
import os,sys,logging,psutil
if sys.platform == "win32":
from win32process import DETACHED_PROCESS, CREATE_NEW_PROCESS_GROUP
from subprocess import PIPE, Popen
import signal
from PyTango import DevState
from PyTango.server import Device, DeviceMeta, run, device_property, command,\
attribute
"""Launcher device server"""
class Launcher(Device):
''' Tango Device Class for Python script Launcher
'''
__metaclass__ = DeviceMeta
scriptPath = device_property(dtype=str)
def init_device(self):
Device.init_device(self)
self.process = None
self._returnCode = 0
self._stdout = ''
self._stderr = ''
if self.scriptPath != None and os.path.isfile(self.scriptPath):
self.set_state(DevState.ON)
else:
self.set_state(DevState.FAULT)
logging.error("Error finding device corresponding script. scriptPath = {}".format(self.scriptPath))
@attribute(label="Return code",dtype=int)
def returnCode(self):
if self.process != None:
return self.process.returncode
@attribute(label="Stdout",dtype=str)
def stdout(self):
if self.get_state() == DevState.ON:
for line in self.process.stdout.readlines():
self._stdout = self._stdout + line
return self._stdout
@attribute(label="Stderr",dtype=str)
def stderr(self):
if self.get_state() == DevState.ON:
for line in self.process.stderr.readlines():
self._stderr = self._stderr + line
return self._stderr
@attribute(label="Return code", dtype=bool)
def isRunning(self):
try:
if self.process == None: return False
self.process.poll()
if self.process.returncode == None: # process still running_in_idle
p = psutil.Process(self.process.pid)
if p.status() == 'running':
self.set_state(DevState.RUNNING)
elif p.status() == 'stopped':
self.set_state(DevState.OFF)
else:
for line in self.process.stdout.readlines():
self._stdout = self._stdout + line
for line in self.process.stderr.readlines():
self._stderr = self._stderr + line
self.set_state(DevState.ON)
logging.debug("returnCode = {}".format(self.process.returncode))
return self.process.returncode == None
except Exception as e:
logging.warning(e)
@command()
def run(self):
logging.info("Starting script …")
try:
if sys.platform == "win32":
self.process = Popen(['python',self.scriptPath], bufsize=0, shell=True, # universal_newlines=True,
creationflags=DETACHED_PROCESS, stdout=PIPE, stderr=PIPE)
else:
self.process = Popen(['python',self.scriptPath],
stdout=PIPE, stderr=PIPE)
self._stdout = ''
self._stderr = ''
self.set_state(DevState.RUNNING)
except: # Exiting, device server can't be started
raise
self.set_state(DevState.FAULT)
def is_run_allowed(self):
return (self.process == None or self.process.returncode != None) and self.get_state() == DevState.ON
@command()
def emergency(self):
logging.info("Emergency shutdown… Process {}".format(self.process.pid))
try:
p = psutil.Process(self.process.pid)
p.send_signal(signal.SIGINT)
except Exception as e:
logging.error(e)
self.set_state(DevState.FAULT)
def main():
run([Launcher])
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)
sys.argv.append("DsTestsUnitaires")
main()
And here is the class, interruptible, launched by the process:
import signal
from time import sleep
import logging
import sys
class Iterator(object):
'''
Interruptible iterator
'''
def __init__(self, lstSequence, fctCallback=None):
'''
Constructor
:param lstSequence: sequence to iterate
:type lstSequence: list
'''
self.lstSequence = lstSequence
self.fctCallback = fctCallback
self.bInterrupted = False
self.iLastValue = None
signal.signal(signal.SIGINT, self.signal_handler)
def signal_handler(self, signum, frame):
logging.debug("Signal caught")
self.bInterrupted = True
if self.fctCallback != None:
self.fctCallback()
def next(self):
# Pop a value when called, start a shutdown sequence if flag bInterrupted is set
try:
if not self.bInterrupted:
self.iLastValue = self.lstSequence.pop(0)
else:
i = len(self.lstSequence)
while self.lstSequence[i-1] < self.lstSequence[i-2]:
i -= 1
self.lstSequence = self.lstSequence[i-1:]
self.bInterrupted = False
self.iLastValue = self.lstSequence.pop(0)
except IndexError:
self.iLastValue = None
return self.iLastValue
if __name__ == "__main__": # pragma: no cover
def essaiCallback():
print "Emergency shutdown callback started"
compteur = Iterator(range(0,30,2)+range(20,0,-5), essaiCallback)
value=compteur.next()
while value != None:
print value
try:
sleep(1)
except IOError:
pass
value=compteur.next()
Have you found answers to your problem ?
Thanks by advance for your help !
|