Process not terminating after exception - python

I let a server run, which should communicate with a serial device. I wrote a init.d script which should automatically restarts the server if it crashs for some reason. However to achieve this, the python script has to terminate properly. Unfortunately my thread just stucks if a exception is raised (e.g. if i unplug my serial device) and never terminates.
This is the error:
Traceback (most recent call last):
File "/usr/lib/python2.7/threading.py", line 552, in __bootstrap_inner
self.run()
File "/usr/lib/python2.7/threading.py", line 505, in run
self.__target(*self.__args, **self.__kwargs)
File "RPiQuadroServer.py", line 87, in recv_thr
while pySerial.inWaiting() > 0:
File "/usr/lib/python2.7/dist-packages/serial/serialposix.py", line 431, in inWaiting
s = fcntl.ioctl(self.fd, TIOCINQ, TIOCM_zero_str)
IOError: [Errno 5] Input/output error
And this is the code. I removed some unimportant functions..
# Make this Python 2.7 script compatible to Python 3 standard
from __future__ import print_function
# For remote control
import socket
import json
import serial
# For sensor readout
import logging
import threading
# For system specific functions
import sys
from time import *
# Create a sensor log with date and time
layout = '%(asctime)s - %(levelname)s - %(message)s'
logging.basicConfig(filename='/tmp/RPiQuadrocopter.log', level=logging.INFO, format=layout)
# Socket for WiFi data transport
udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_sock.bind(('0.0.0.0', 7000))
client_adr = ""
# Thread lock for multi threading
THR_LOCK = threading.Lock()
#pySerial
pySerial = 0
# These functions shall run in separate threads
# recv_thr() is used to catch sensor data
def recv_thr():
global client_adr
ser_line = ""
while True:
# Lock while data in queue to get red
THR_LOCK.acquire()
while pySerial.inWaiting() > 0:
try:
# Remove newline character '\n'
ser_line = pySerial.readline().strip()
except serial.SerialTimeoutException as e:
logging.error("Read timeout on serial port '{}': {}".format(com_port, e))
return # never ends..
else:
try:
p = json.loads(ser_line)
except (ValueError, KeyError, TypeError):
# Print everything what is not valid json string to console
#print ("JSON format error: %s" % ser_line)
logging.debug("JSON format error: " + ser_line)
else:
logging.info(ser_line)
if client_adr != "":
bytes = udp_sock.sendto(ser_line, client_adr)
THR_LOCK.release()
# Main program for sending and receiving
# Working with two separate threads
def main():
# Start threads for receiving and transmitting
recv=threading.Thread(target=recv_thr)
recv.start()
# Start Program
bInitialized, pySerial = init_serial()
if not bInitialized:
print ("Could not open any serial port. Exit script.")
sys.exit()
main()

Not your program is terminating, just a thread of yours is terminating with an exception.
You need to check yourself if that thread is still running and if so, terminate.
Besides the proposal of radu.ciorba of polling the thread you could also catch all exceptions in the thread and in case it is failing with an exception, send a SIGTERM to your process; this will terminate all threads and thus the process.
Use os.kill(os.getpid(), 15) for that and place it in a general except clause:
def recv_thr(...): # add your arguments here
try:
... # add your code here
except:
os.kill(os.getpid(), 15)

you can check if the thread is still alive and exit if it's not:
import time
import sys
import threading
def spam():
raise AssertionError("spam")
t = threading.Thread(target=spam)
r = t.start()
while 1:
time.sleep(.5)
if not t.is_alive():
sys.exit(1)

Related

Cleanly stopping a multiprocessing.Process - KeyboardInterrupt escapes in Windows

I'm using multiprocessing to spawn a task (multiprocessing.Process) that can be stopped (without cooperation from the task itself, e.g.: without using something like multiprocessing.Event to signal the task to gracefully stop).
Since .terminate() (or .kill()) won't stop it cleanly (the finally: clause won't execute), I thought I would use os.kill() to emulate a CTRL+C event:
from multiprocessing import Process
from time import sleep
import os
import signal
def task(n):
try:
for i in range(n):
sleep(1)
print(f'task: i={i}')
finally:
print('task: finally clause executed!')
return i
if __name__ == '__main__':
t = Process(target=task, args=(10,))
print(f'main: starting task...')
t.start()
sleep(5)
for i in ('CTRL_C_EVENT', 'SIGINT'):
if hasattr(signal, i):
sig = getattr(signal, i)
break
print(f'main: attempt to stop task...')
os.kill(t.pid, sig)
The finally: clause executes on both Windows, macOS and Linux; hoever on Windows it additionally spits out the error:
Error in atexit._run_exitfuncs:
Traceback (most recent call last):
File "c:\Python38\lib\multiprocessing\util.py", line 357, in
_exit_function
p.join()
File "c:\Python38\lib\multiprocessing\process.py", line 149, in join
res = self._popen.wait(timeout)
File "c:\Python38\lib\multiprocessing\popen_spawn_win32.py", line 108, in wait
res = _winapi.WaitForSingleObject(int(self._handle), msecs)
KeyboardInterrupt
while on macOS and Linux it only print the messages meant to be printed.
It seems CTRL_C_EVENT in Windows is also propagated from the child process to the parent process. See for example this related question.
I added some book keeping code and a try...except block to the code. It shows what happens, and that the KeyboardInterrupt needs to be caught on parent process as well.
from multiprocessing import Process
from time import sleep
import os
import signal
def task(n):
try:
for i in range(n):
sleep(1)
print(f'task: i={i}')
except KeyboardInterrupt:
print("task: caught KeyboardInterrupt")
finally:
print('task: finally clause executed!')
return i
if __name__ == '__main__':
try:
t = Process(target=task, args=(10,))
print(f'main: starting task...')
t.start()
sleep(5)
for i in ('CTRL_C_EVENT', 'SIGINT'):
if hasattr(signal, i):
sig = getattr(signal, i)
break
print(f'main: attempt to stop task...')
os.kill(t.pid, sig)
finally:
try:
print("main: finally in main process. Waiting for 3 seconds")
sleep(3)
except KeyboardInterrupt:
print("main: caught KeyboardInterrupt in finally block")
It prevents the error and produces the following output:
main: starting task...
task: i=0
task: i=1
task: i=2
task: i=3
main: attempt to stop task...
main: finally in main process. Waiting for 3 seconds
task: caught KeyboardInterrupt
main: caught KeyboardInterrupt in finally block
task: finally clause executed!

Unable to use GPIO PWM when multiple threads are available

I am working on a project which continuously waits for an user input on a screen. Once a user input was detected a servo mechanism needs to open and then close. The whole solution is written in such a way that there's one main thread and few others that are forked from the main one as follows:
The problem with this setup is the this one:
If I execute only the operations handled in the main thread -> BoxOpeningManager.py works as expected. It opens and closes the servo.
If I execute once an operation handled in the main thread and then operation handled in the forked thread, the next time I try to execute operation on the main thread BoxOpeningManager.py is not working. No error, no nothing. It is just not opening the servo. I need to restart the program.
If first I execute an operation handled in the forked thread and then try to execute one handled in the main thread, main thread is again not working.
The long story short, every time I execute something on the forked thread, I can not use the BoxOpeningManager.py in the main thread anymore.
Here is the code of both scripts:
from servoManager import ServoManager
from proximitySensorManager import ProximitySensorManager
from configurationWrapping import GlobalConfigurationWrapper
from loggingManager import Logger
import time
class BoxOpeningManager():
def __init__(self):
self.configuration = GlobalConfigurationWrapper()
self.timeToKeepTheBoxOpen = self.configuration.box_time_to_keep_the_box_open()
self.logger = Logger()
self.servoManager = ServoManager()
self.proximitySensorManager = ProximitySensorManager();
def start_box_opening_procedure(self):
try:
self.servoManager.open()
t_end = time.time() + (int)(self.timeToKeepTheBoxOpen)
while time.time() < t_end:
continue
while True:
if not self.proximitySensorManager.object_in_front() :
self.servoManager.close()
break;
else :
time.sleep(1)
except BaseException as e:
self.logger.log_critical('<BoxOpeningManager.start_box_opening_procedure> => ' + str(e))
Servo manager:
import RPi.GPIO as GPIO
from time import sleep
from patternImplementations import Singleton
from loggingManager import Logger
class ServoManager():
__metaclass__ = Singleton
__OUTPUT_GPIO_PIN_NUMBER = 12
def __init__(self):
GPIO.setmode(GPIO.BOARD)#Numbers GPIOs by physical location
GPIO.setup(self.__OUTPUT_GPIO_PIN_NUMBER, GPIO.OUT)
self.pwm=GPIO.PWM(self.__OUTPUT_GPIO_PIN_NUMBER,50)
self.pwm.start(0)
self.logger = Logger()
def open(self):
try:
self.__set_angle(13)
except BaseException as e:
self.logger.log_critical('<ServoManager.open> => ' + str(e))
def close(self):
try:
self.__set_angle(185)
except BaseException as e:
self.logger.log_critical('<ServoManager.close> => ' + str(e))
def __set_angle(self, angle):
duty = (angle/18 + 2)
GPIO.output(self.__OUTPUT_GPIO_PIN_NUMBER, True)
self.pwm.ChangeDutyCycle(duty)
sleep(1)
GPIO.output(self.__OUTPUT_GPIO_PIN_NUMBER, False)
self.pwm.ChangeDutyCycle(0)

How to register a SIGINT handler that will run as soon as Ctrl+C is pressed?

I am writing a Python script that uses the PyObjC bindings for AppKit. The script registers an observer with the shared NSWorkspace's notificationCenter and then calls AppKit.CFRunLoopRun() in order for the notifications to be processed:
from __future__ import print_function
from AppKit import *
import signal
shared_workspace = NSWorkspace.sharedWorkspace()
def on_did_activate_application(notification):
print('on_did_activate_application(notification = %s)' % notification)
notification_center = shared_workspace.notificationCenter()
did_activate_application_observer = notification_center.addObserverForName_object_queue_usingBlock_(
NSWorkspaceDidActivateApplicationNotification,
None,
None,
on_did_activate_application)
def handle_interrupt(signum, frame):
notification_center.removeObserver_(did_activate_application_observer)
CFRunLoopStop(CFRunLoopGetCurrent())
signal.signal(signal.SIGINT, handle_interrupt)
CFRunLoopRun()
The problem I am experiencing (reproducible with the above MCVE) is that, when I press Ctrl+C on the terminal window running the script, handle_interrupt() is not executed immediately, but rather it executes the next time a NSWorkspaceDidActivateApplicationNotification notification is handled.
How do I respond to Ctrl+C / SIGINT just as soon as it occurs?
This can be accomplished by setting a "signal wakeup fd" to the write end of a pipe, and then creating a CFFileDescriptor that monitors the read end of the pipe for activity.
As mentioned at What’s New in Python 2.6, in Python 2.6, a new API was added to the signal module called set_wakeup_fd(). Whenever a signal is received, a NUL byte ('\0') is written to the fd.
If the wakeup fd is set to the write end of a pipe, then a CFFileDescriptor can be created to monitor for activity (availability of data) on the read end of the pipe, and callbacks on such activity can be configured to run on the CFRunLoop.
from __future__ import print_function
from AppKit import * # For development only. This takes a long time to complete as there are many symbols.
import fcntl
import os
import signal
shared_workspace = NSWorkspace.sharedWorkspace()
def on_did_activate_application(notification):
print('on_did_activate_application(notification = %s)' % notification)
notification_center = shared_workspace.notificationCenter()
did_activate_application_observer = notification_center.addObserverForName_object_queue_usingBlock_(
NSWorkspaceDidActivateApplicationNotification,
None,
None,
on_did_activate_application)
def handle_signal(signum, frame):
print('handle_signal(signum = %s, frame = <scrubbed>)' % signum)
if signum == signal.SIGCONT:
signal.signal(signal.SIGTSTP, handle_signal)
elif signum == signal.SIGINT:
notification_center.removeObserver_(did_activate_application_observer)
CFRunLoopStop(CFRunLoopGetCurrent())
else:
# https://stackoverflow.com/questions/13377773/proper-way-to-handle-signals-other-than-sigint-in-python
signal.signal(signum, signal.SIG_DFL)
os.kill(os.getpid(), signum)
r, w = os.pipe()
flags = fcntl.fcntl(r, fcntl.F_GETFL, 0)
fcntl.fcntl(r, fcntl.F_SETFL, flags | os.O_NONBLOCK)
def callout(f, call_back_types, info):
# Note: The signal handler will run first.
print('callout()')
# Clear the pipe of NUL bytes.
n = 0
while True:
try:
n += len(os.read(r, 100))
except OSError:
break
print('read %d byte(s)' % n)
# Per https://developer.apple.com/documentation/corefoundation/cffiledescriptor?language=objc
# "Each call back is one-shot, and must be re-enabled if you want to get another one."
# Thus we need to re-enable call backs.
CFFileDescriptorEnableCallBacks(f, kCFFileDescriptorReadCallBack)
file_descriptor = CFFileDescriptorCreate(None, r, True, callout, None)
CFFileDescriptorEnableCallBacks(file_descriptor, kCFFileDescriptorReadCallBack)
run_loop_source = CFFileDescriptorCreateRunLoopSource(None, file_descriptor, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source, kCFRunLoopDefaultMode)
signal.set_wakeup_fd(w)
signal.signal(signal.SIGCONT, handle_signal)
signal.signal(signal.SIGINT, handle_signal)
signal.signal(signal.SIGTSTP, handle_signal)
# For testing, configure a SIGALRM to be received every two seconds.
signal.signal(signal.SIGALRM, lambda _1, _2: print('SIGALRM'))
signal.setitimer(signal.ITIMER_REAL, 2, 2)
print('about to call CFRunLoopRun()')
CFRunLoopRun()
Acknowledgments
A big thank you to 'A GUEST' (whoever you are) for posting this paste on Pastebin; to Da_Blitz for writing the Fighting set_wakeup_fd article; and to the askers/answerers of “Proper” way to handle signals other than SIGINT in Python?, What's the difference between SIGSTOP and SIGTSTP?, and python Non-block read file on Stack Overflow.

How to let child thread deal with main process killed or key interrupt in Python?

In a multi-threaded design, I want to do some clean steps when the program exits abnormally. The running thread should clean up the current task and then quit, rather than be killed immediately and leave some dirty data. I found that using threading module could not catch KeyInterrupt exception.
Here is my test code:
#!/usr/bin/env python3
from time import sleep
def do_sth():
print("I'm doing something...")
sleep(10)
if __name__ == "__main__":
do_sth()
Python will raise KeyInterrupt exception when I press CTRL-c
$ Python3 test.py
I'm doing something ...
^C
Traceback (most recent call last):
File "test.py", line 10, in <module>
do_sth ()
File "test.py", line 7, in do_sth
sleep (10)
KeyboardInterrupt
So I can catch this exception.
def do_sth ():
try:
print ("I'm doing something ...")
sleep (10)
except (KeyboardInterrupt, SystemExit):
print ("I'm doing some clean steps and exit.")
But when I use threading module, this exception is not raised at all.
#!/usr/bin/env python3
from time import sleep
import threading
def do_sth():
print("I'm doing something...")
sleep(10)
if __name__ == '__main__':
t = threading.Thread(target=do_sth)
t.start()
t.join()
result:
$ python3 test.py
I'm doing something...
^C
The running thread has been killed directly and no exception is raised.
How do I deal with this?
One way is to handle KeyboardInterrupt exceptions.
Another thing to do in such scenarios is to manage the state of your application across all threads.
One of the solutions is to add support for Signals in your code. It allows graceful handling of the shutting down of your process.
Here's one simple setup for that:
class SignalHandler:
continue_running = True
def __init__(self):
signal.signal(signal.SIGUSR2, self.signal_handler)
signal.signal(signal.SIGTERM, self.signal_handler)
signal.signal(signal.SIGINT, self.signal_handler)
logging.info("SignalHandler::Init")
def signal_handler(self, num, stack):
logging.warning('Received signal %d in %s' % (num, threading.currentThread()))
SignalHandler.continue_running = False
logging.warning("Time to SHUT DOWN ALL MODULES")
All threads would try and utilise the status from SignalHandler.continue_running to ensure that they all know when to stop.
If somebody tried to kill this python process by calling kill -2 [PID] for example - all threads will come to know about the need to shut down.

Handling keyboard interrupt when using subproccess

I have python script called monitiq_install.py which calls other scripts (or modules) using the subprocess python module. However, if the user sends a keyboard interrupt (CTRL + C) it exits, but with an exception. I want it to exit, but nicely.
My Code:
import os
import sys
from os import listdir
from os.path import isfile, join
from subprocess import Popen, PIPE
import json
# Run a module and capture output and exit code
def runModule(module):
try:
# Run Module
process = Popen(os.path.dirname(os.path.realpath(__file__)) + "/modules/" + module, shell=True, stdout=PIPE, bufsize=1)
for line in iter(process.stdout.readline, b''):
print line,
process.communicate()
exit_code = process.wait();
return exit_code;
except KeyboardInterrupt:
print "Got keyboard interupt!";
sys.exit(0);
The error I'm getting is below:
python monitiq_install.py -a
Invalid module filename: create_db_user_v0_0_0.pyc
Not Running Module: '3parssh_install' as it is already installed
######################################
Running Module: 'create_db_user' Version: '0.0.3'
Choose username for Monitiq DB User [MONITIQ]
^CTraceback (most recent call last):
File "/opt/monitiq-universal/install/modules/create_db_user-v0_0_3.py", line 132, in <module>
inputVal = raw_input("");
Traceback (most recent call last):
File "monitiq_install.py", line 40, in <module>
KeyboardInterrupt
module_install.runModules();
File "/opt/monitiq-universal/install/module_install.py", line 86, in runModules
exit_code = runModule(module);
File "/opt/monitiq-universal/install/module_install.py", line 19, in runModule
for line in iter(process.stdout.readline, b''):
KeyboardInterrupt
A solution or some pointers would be helpful :)
--EDIT
With try catch
Running Module: 'create_db_user' Version: '0.0.0'
Choose username for Monitiq DB User [MONITIQ]
^CGot keyboard interupt!
Traceback (most recent call last):
File "monitiq_install.py", line 36, in <module>
module_install.runModules();
File "/opt/monitiq-universal/install/module_install.py", line 90, in runModules
exit_code = runModule(module);
File "/opt/monitiq-universal/install/module_install.py", line 29, in runModule
sys.exit(0);
NameError: global name 'sys' is not defined
Traceback (most recent call last):
File "/opt/monitiq-universal/install/modules/create_db_user-v0_0_0.py", line 132, in <module>
inputVal = raw_input("");
KeyboardInterrupt
If you press Ctrl + C in a terminal then SIGINT is sent to all processes within the process group. See child process receives parent's SIGINT.
That is why you see the traceback from the child process despite try/except KeyboardInterrupt in the parent.
You could suppress the stderr output from the child process: stderr=DEVNULL. Or start it in a new process group: start_new_session=True:
import sys
from subprocess import call
try:
call([sys.executable, 'child.py'], start_new_session=True)
except KeyboardInterrupt:
print('Ctrl C')
else:
print('no exception')
If you remove start_new_session=True in the above example then KeyboardInterrupt may be raised in the child too and you might get the traceback.
If subprocess.DEVNULL is not available; you could use DEVNULL = open(os.devnull, 'r+b', 0). If start_new_session parameter is not available; you could use preexec_fn=os.setsid on POSIX.
You can do this using try and except as below:
import subprocess
try:
proc = subprocess.Popen("dir /S", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while proc.poll() is None:
print proc.stdout.readline()
except KeyboardInterrupt:
print "Got Keyboard interrupt"
You could avoid shell=True in your execution as best security practice.
This code spawns a child process and hands signals like SIGINT, ... to them just like shells (bash, zsh, ...) do it.
This means KeyboardInterrupt is no longer seen by the Python process, but the child receives this and is killed correctly.
It works by running the process in a new foreground process group set by Python.
import os
import signal
import subprocess
import sys
import termios
def run_as_fg_process(*args, **kwargs):
"""
the "correct" way of spawning a new subprocess:
signals like C-c must only go
to the child process, and not to this python.
the args are the same as subprocess.Popen
returns Popen().wait() value
Some side-info about "how ctrl-c works":
https://unix.stackexchange.com/a/149756/1321
fun fact: this function took a whole night
to be figured out.
"""
old_pgrp = os.tcgetpgrp(sys.stdin.fileno())
old_attr = termios.tcgetattr(sys.stdin.fileno())
user_preexec_fn = kwargs.pop("preexec_fn", None)
def new_pgid():
if user_preexec_fn:
user_preexec_fn()
# set a new process group id
os.setpgid(os.getpid(), os.getpid())
# generally, the child process should stop itself
# before exec so the parent can set its new pgid.
# (setting pgid has to be done before the child execs).
# however, Python 'guarantee' that `preexec_fn`
# is run before `Popen` returns.
# this is because `Popen` waits for the closure of
# the error relay pipe '`errpipe_write`',
# which happens at child's exec.
# this is also the reason the child can't stop itself
# in Python's `Popen`, since the `Popen` call would never
# terminate then.
# `os.kill(os.getpid(), signal.SIGSTOP)`
try:
# fork the child
child = subprocess.Popen(*args, preexec_fn=new_pgid,
**kwargs)
# we can't set the process group id from the parent since the child
# will already have exec'd. and we can't SIGSTOP it before exec,
# see above.
# `os.setpgid(child.pid, child.pid)`
# set the child's process group as new foreground
os.tcsetpgrp(sys.stdin.fileno(), child.pid)
# revive the child,
# because it may have been stopped due to SIGTTOU or
# SIGTTIN when it tried using stdout/stdin
# after setpgid was called, and before we made it
# forward process by tcsetpgrp.
os.kill(child.pid, signal.SIGCONT)
# wait for the child to terminate
ret = child.wait()
finally:
# we have to mask SIGTTOU because tcsetpgrp
# raises SIGTTOU to all current background
# process group members (i.e. us) when switching tty's pgrp
# it we didn't do that, we'd get SIGSTOP'd
hdlr = signal.signal(signal.SIGTTOU, signal.SIG_IGN)
# make us tty's foreground again
os.tcsetpgrp(sys.stdin.fileno(), old_pgrp)
# now restore the handler
signal.signal(signal.SIGTTOU, hdlr)
# restore terminal attributes
termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old_attr)
return ret
# example:
run_as_fg_process(['openage', 'edit', '-f', 'random_map.rms'])

Categories

Resources