signal.pause() with signal.alarm() causes RecursionError in non-sleeping program - python

Single-threaded python program, intending to be responsive to events from raspberry pi button presses also wants to wake every minute to update an LCD display.
Main function:
btn_1 = 21
GPIO.setup(btn_1, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.add_event_detect(btn_1, GPIO.FALLING, callback=btn_1_press_callback, bouncetime=100)
lcd.display()
lcd.messsage("text to display on lcd"
The previous code runs the btn_1_press_callback function whenever a physical button is pressed. The rest of the main function, instead of sleeping in a busy loop, does this:
signal.signal(signal.SIGALRM, wake_every_min)
signal.alarm(60)
signal.pause()
This way button presses are signaled immediately. The wake_every_minute() function simply refreshes the display with the currently displayed data (updating from the data source), so it updates every minute regardless of a button press:
def wake_every_min(sig, frame):
lcd.clear()
lcd.message("new string here")
signal.alarm(60)
signal.pause()
And then it calls signal.pause() to sleep / but listen for signals again. This works perfectly, except for the fact that after some time, I get RecursionError: maximum recursion depth exceeded while calling a Python object
Funny enough it's always at the same time, meaning "previous line repeated 482 times" is alway 482:
Traceback (most recent call last):
File "./info.py", line 129, in <module>
main()
File "./info.py", line 126, in main
signal.pause()
File "./info.py", line 111, in wake_every_min
signal.pause()
File "./info.py", line 111, in wake_every_min
signal.pause()
File "./info.py", line 111, in wake_every_min
signal.pause()
[Previous line repeated 482 more times]
Is there another way to accomplish this without a while True loop with a time.sleep()? If I do that, button presses aren't responsive, as there is always a potential for a 1.9999 minute delay, worst-case.

Update: I was thinking about this wrong. time.sleep() will not prevent a signal from happening -- the signals will interrupt sleep().
The correct solution is to sleep in the main loop, and never call signal.pause(). With a SIGINT handler, you can also exit immediately when ^c is pressed:
signal.signal(signal.SIGALRM, wake_every_min)
signal.alarm(60)
signal.signal(signal.SIGINT, int_signal_handler)
while True:
# Sleep and wait for a signal.
sleep(10)
signal.alarm(60)
Moving the re-setting of the alarm into the main loop prevents RecursionError, as new signals aren't piling up from the handler call stack. If anyone is curious what this was for, it's a crypto LCD ticker: https://github.com/manos/crypto_lcd/blob/master/info.py

Related

PySimpleGUI Multithreaded Implementation for OpevCV Workflow

I've been tooling around with PySimpleGUI for just a week now. I have not been able to decipher what DemoProgram(s) will help achieve my goal. Skills are novice/tinkerer.
Project: RasberryPi OpenCV workflow using:
HDMI bridge, initiated with shell script when external image source is configured
Capture shell script, grabbing frames from HDMI bridge into data folder
Watcher.py script, watches data folder and processes new images via OpenCV script
Dashboard, displayed using Chromium
Goal: GUI with button(s) to launch each step (1-4) of the process and give Real-time Output for the watcher.py script + ability to exit capture and watcher scripts which are long running
Attempted Solutions from https://github.com/PySimpleGUI/PySimpleGUI/tree/master/DemoPrograms
Demo_Desktop_Floating_Toolbar.py
Demo_Script_Launcher_Realtime_Output.py
Demo_Multithreaded_Animated_Shell_Command.py
Demo_Multithreaded_Multiple_Threads.py
Demo_Multithreaded_Different_Threads.py
My attempts so far have yielded mixed results. I have been able to get individual steps to launch but I have not been able to put a combination of calls, triggers, and an event loop together that works.
My hunch is that using Demo_Multithreaded_Multiple_Threads.py is where I need to be building but trying to combine that with Demo_Script_Launcher_Realtime_Output.py for just the watcher.py script has led to hanging.
#!/usr/bin/python3
import subprocess
import threading
import time
import PySimpleGUI as sg
"""
You want to look for 3 points in this code, marked with comment "LOCATION X".
1. Where you put your call that takes a long time
2. Where the trigger to make the call takes place in the event loop
3. Where the completion of the call is indicated in the event loop
Demo on how to add a long-running item to your PySimpleGUI Event Loop
If you want to do something that takes a long time, and you do it in the
main event loop, you'll quickly begin to see messages from windows that your
program has hung, asking if you want to kill it.
The problem is not that your problem is hung, the problem is that you are
not calling Read or Refresh often enough.
One way through this, shown here, is to put your long work into a thread that
is spun off, allowed to work, and then gets back to the GUI when it's done working
on that task.
Every time you start up one of these long-running functions, you'll give it an "ID".
When the function completes, it will send to the GUI Event Loop a message with
the format:
work_id ::: done
This makes it easy to parse out your original work ID
You can hard code these IDs to make your code more readable. For example, maybe
you have a function named "update_user_list()". You can call the work ID "user list".
Then check for the message coming back later from the work task to see if it starts
with "user list". If so, then that long-running task is over.
"""
# ############################# User callable CPU intensive code #############################
# Put your long running code inside this "wrapper"
# NEVER make calls to PySimpleGUI from this thread (or any thread)!
# Create one of these functions for EVERY long-running call you want to make
def long_function_wrapper(work_id, window):
# LOCATION 1
# this is our "long running function call"
# sleep for a while as a simulation of a long-running computation
def process_thread():
global proc
proc = subprocess.Popen('python watcher.py data', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
def main():
thread = threading.Thread(target=process_thread, daemon=True)
thread.start()
while True:
sg.popup_animated(sg.DEFAULT_BASE64_LOADING_GIF, 'Watcher is Running', time_between_frames=100)
thread.join(timeout=.1)
if not thread.is_alive():
break
sg.popup_animated(None)
output = proc.__str__().replace('\\r\\n', '\n')
sg.popup_scrolled(output, font='Courier 10')
if __name__ == '__main__':
main()
# at the end of the work, before exiting, send a message back to the GUI indicating end
window.write_event_value('-THREAD DONE-', work_id)
# at this point, the thread exits
return
############################# Begin GUI code #############################
def the_gui():
sg.theme('Light Brown 3')
layout = [[sg.Text('Multithreaded Work Example')],
[sg.Text('Click Go to start a long-running function call')],
[sg.Text(size=(25, 1), key='-OUTPUT-')],
[sg.Text(size=(25, 1), key='-OUTPUT2-')],
[sg.Text('?', text_color='blue', key=i, pad=(0,0), font='Default 14') for i in range(4)],
[sg.Button('Go'), sg.Button('Popup'), sg.Button('Exit')], ]
window = sg.Window('Multithreaded Window', layout)
# --------------------- EVENT LOOP ---------------------
work_id = 0
while True:
# wait for up to 100 ms for a GUI event
event, values = window.read()
if event in (sg.WIN_CLOSED, 'Exit'):
break
if event == 'Go': # clicking "Go" starts a long running work item by starting thread
window['-OUTPUT-'].update('Starting long work %s' % work_id)
window[work_id].update(text_color='red')
# LOCATION 2
# STARTING long run by starting a thread
thread_id = threading.Thread(
target=long_function_wrapper,
args=(work_id, window,),
daemon=True)
thread_id.start()
work_id = work_id+1 if work_id < 19 else 0
# if message received from queue, then some work was completed
if event == '-THREAD DONE-':
# LOCATION 3
# this is the place you would execute code at ENDING of long running task
# You can check the completed_work_id variable
# to see exactly which long-running function completed
completed_work_id = values[event]
window['-OUTPUT2-'].update(
'Complete Work ID "{}"'.format(completed_work_id))
window[completed_work_id].update(text_color='green')
if event == 'Popup':
sg.popup_non_blocking('This is a popup showing that the GUI is running', grab_anywhere=True)
# if user exits the window, then close the window and exit the GUI func
window.close()
############################# Main #############################
if __name__ == '__main__':
the_gui()
print('Exiting Program')
(cv) pi#raspberrypi:~/issgrab $ python GUIDE_GUI_master.py
*** Faking timeout ***
Exception in thread Thread-1:
Traceback (most recent call last):
File "/usr/lib/python3.5/threading.py", line 914, in _bootstrap_inner
self.run()
File "/usr/lib/python3.5/threading.py", line 862, in run
self._target(*self._args, **self._kwargs)
File "GUIDE_GUI_master.py", line 68, in long_function_wrapper
main()
File "GUIDE_GUI_master.py", line 57, in main
sg.popup_animated(sg.DEFAULT_BASE64_LOADING_GIF, 'Watcher is Running', time_between_frames=100)
File "/home/pi/.virtualenvs/cv/lib/python3.5/site-packages/PySimpleGUI/PySimpleGUI.py", line 15797, in PopupAnimated
transparent_color=transparent_color, finalize=True, element_justification='c', icon=icon)
File "/home/pi/.virtualenvs/cv/lib/python3.5/site-packages/PySimpleGUI/PySimpleGUI.py", line 6985, in __init__
self.Finalize()
File "/home/pi/.virtualenvs/cv/lib/python3.5/site-packages/PySimpleGUI/PySimpleGUI.py", line 7510, in Finalize
self.Read(timeout=1)
File "/home/pi/.virtualenvs/cv/lib/python3.5/site-packages/PySimpleGUI/PySimpleGUI.py", line 7325, in Read
results = self._read(timeout=timeout, timeout_key=timeout_key)
File "/home/pi/.virtualenvs/cv/lib/python3.5/site-packages/PySimpleGUI/PySimpleGUI.py", line 7365, in _read
self._Show()
File "/home/pi/.virtualenvs/cv/lib/python3.5/site-packages/PySimpleGUI/PySimpleGUI.py", line 7199, in _Show
StartupTK(self)
File "/home/pi/.virtualenvs/cv/lib/python3.5/site-packages/PySimpleGUI/PySimpleGUI.py", line 12100, in StartupTK
my_flex_form.TKroot.mainloop()
File "/usr/lib/python3.5/tkinter/__init__.py", line 1143, in mainloop
self.tk.mainloop(n)
RuntimeError: Calling Tcl from different appartment
Exiting Program

How do I trigger a callback from a keyboard press? Includes example with error

I'm working on a raspberry pi project and I'd like to be able to set a button press, specifically '0', to respond with zeroing out a number. I have a callback set using the signal library to break a loop and end the program with SIGINT, but I can't figure out a way to capture specific keyboard presses without using a while loop. I tried the below, which I saw somewhere else on this site, on my windows laptop (not yet on the Pi), but I get a Type Error.
import keyboard
import time
num_val = 10
def here():
global num_val
num_val = 0
print('Keyboard Press Triggered')
keyboard.on_press_key('f', here)
while num_val is not 0:
time.sleep(0.5)
print('End')
I get this error when I press 'f'
Exception in thread Thread-2:
Traceback (most recent call last):
File "C:\Somestuff\Python\Python37-32\lib\threading.py", line 917, in _bootstrap_inner
self.run()
File "C:\Somestuff\Python\Python37-32\lib\threading.py", line 865, in run
self._target(*self._args, **self._kwargs)
File "C:\Somestuff\Python\Python37-32\lib\site-packages\keyboard\_generic.py", line 58, in process
if self.pre_process_event(event):
File "C:\Somestuff\Python\Python37-32\lib\site-packages\keyboard\__init__.py", line 213, in pre_process_event
key_hook(event)
File "C:\Somestuff\Python\Python37-32\lib\site-packages\keyboard\__init__.py", line 510, in <lambda>
return hook_key(key, lambda e: e.event_type == KEY_UP or callback(e), suppress=suppress)
TypeError: here() takes 0 positional arguments but 1 was given
What is wrong with the above program? Why is 'here' taking an argument? Is there a better way to trigger a function on keyboard press using callbacks? I don't want to make a while loop specifically for monitoring a button press since I already have a while loop where I am performing some actions in my Pi program. I'd much rather have a callback that just changes a value when '0' is pressed. Is there a way I could pass my num_val to the function instead of using 'global num_val'?
See the docs
The event passed to the callback is of type keyboard.KeyboardEvent, with the following
name: an Unicode representation of the character (e.g. "&") or description (e.g. "space"). The name is always lower-case.
scan_code: number representing the physical key, e.g. 55.
time: timestamp of the time the event occurred, with as much precision as given by the OS.
Basically, the callback sends more data about the event to your function. If you don't care about that data, you can just accept it in the function definition but not do anything with it, i.e.:
def here(event = None):
global num_val
num_val = 0
print('Keyboard Press Triggered')

Python "shutdown / reboot raspberry pi" script, using a single button

I got a piece of code in Python from here:
http://www.raspberry-pi-geek.com/Archive/2013/01/Adding-an-On-Off-switch-to-your-Raspberry-Pi
And I wanted to improve on it.
Since this is the first time I work with Python, I got stuck on understanding what actually happens.
Here is the code:
# Import the modules to send commands to the system and access GPIO pins
from subprocess import call
import RPi.GPIO as gpio
from time import sleep
gpio.setmode(gpio.BCM) # Set pin numbering to board numbering
gpio.setup(22, gpio.IN) # Set up pin 22 as an input
rebootBool = 0
# Define a function to keep script running
def main(pin):
while True:
#gpio.remove_event_detect(pin)
gpio.add_event_detect(22, gpio.RISING, callback=confirmation, bouncetime=200) # Set up an interrupt to look for button presses
sleep(5000000)
def confirmation(pin):
gpio.remove_event_detect(pin)
gpio.add_event_detect(22, gpio.RISING, callback=shutdown, bouncetime=200)
sleep(3) # if button has been pressed again within 3 seconds, shut down will happen
main(22)
def reboot(pin):
rebootBool = 1
call('reboot', shell=False)
exit(0)
# Define a function to run when an interrupt is called
def shutdown(pin):
gpio.remove_event_detect(pin)
gpio.add_event_detect(22, gpio.RISING, callback=reboot, bouncetime=200)
sleep(3) # if the button has been pressed for a third time, within 3 seconds, Pi will reboot
if rebootBool == 0: # Just to make sure a halt is not called after the 3 seconds have passed, if reboot is called
call('halt', shell=False)
exit(0)
main(22) # Run the loop function to keep script running
What I want to do is this:
If button is pressed once, a confirmation function is executed, in which it resets the button to call the shutdown function, if it's pressed within 3 seconds.
Else, resume with the main loop
In the shutdown function, if it's pressed again (within 3 seconds), it resets to call the reboot function.
Else go on, and shut down
What happens is this:
If I press the button twice or three times, it tells me that gpio.add_event_detect is already defined, when it tries to define it in main().
So it doesn't change it, and if I press it one more time, it calls the shutdown function.
What I don't understand is:
Why does it want to define the gpio event in main, when the actual function is either reboot or shutdown (and it should call either reboot or shutdown)?
Because a callback function is running in a separate thread. This means, for example, when you call any callback function in the main loop it still works(main loop), and thus it happens when you call a function gpio.add_event_detect from a callback function(thread #1) and from the main loop(thread #2) at the same time. It's typical race condition.
You can check it by running this code:
#!/usr/bin/python
# Import the modules to send commands to the system and access GPIO pins
from subprocess import call
import RPi.GPIO as gpio
from time import sleep
gpio.setmode(gpio.BCM) # Set pin numbering to board numbering
gpio.setup(22, gpio.IN, pull_up_down=gpio.PUD_DOWN) # Set up pin 22 as an input
rebootBool = 0
# Define a function to keep script running
def main(pin):
while True:
print "main loop"
gpio.remove_event_detect(22)
gpio.add_event_detect(22, gpio.RISING, callback=confirmation, bouncetime=200) # Set up an interrupt to look for button presses
sleep(1)
#raw_input()
def confirmation(pin):
print "confirmation1"
sleep(5)
print "confirmation2"
gpio.remove_event_detect(22)
gpio.add_event_detect(22, gpio.RISING, callback=shutdown, bouncetime=200)
sleep(5) # if button has been pressed again within 3 seconds, shut down will happen
gpio.remove_event_detect(22)
main(22)
def reboot(pin):
rebootBool = 1
print "reboot"
#call('reboot', shell=False)
exit(0)
# Define a function to run when an interrupt is called
def shutdown(pin):
print "shutdown"
gpio.remove_event_detect(pin)
gpio.add_event_detect(22, gpio.RISING, callback=reboot, bouncetime=200)
sleep(3) # if the button has been pressed for a third time, within 3 seconds, Pi will reboot
if rebootBool == 0: # Just to make sure a halt is not called after the 3 seconds have passed, if reboot is called
#call('halt', shell=False)
print "halt"
exit(0)
main(22) # Run the loop function to keep script running
So, after Anton Glukhov explained me what to look out for, I found out how to solve every issue, and now it's working flawlessly.
This is the link that better explains what I did:
http://sourceforge.net/p/raspberry-gpio-python/wiki/Inputs/
Unfortunately, I wasn't able to do it any other way than use a while loop in the main function.
But since there is nothing critical going on, a sleep of 1 second has been added to each loop, ensuring minimal CPU usage.
I am sharing the final code here, in hopes that it might help others:
# Import the modules to send commands to the system and access GPIO pins
from subprocess import call
import RPi.GPIO as gpio
from time import sleep
from os import system
gpio.setmode(gpio.BCM) # Set pin numbering to BCM numbering
gpio.setup(22, gpio.IN) # Set up pin 22 as an input
def confirmation(pin):
#print "Confirmation Function on pin %d" % pin
system('echo Press again to confirm Shut down menu! | wall -n') # let all logged in users know
gpio.remove_event_detect(pin)
gpio.add_event_detect(pin, gpio.RISING, bouncetime=200)
sleep(2) # if button has been pressed again within 2 seconds, shut down function is called
if gpio.event_detected(pin):
shutdown(pin)
else:
#print "canceled"
system('echo Shut down canceled! | wall -n') # let all logged in users know
main()
def resetPin(pin):
#print "Pin %d has been reset" % pin
gpio.remove_event_detect(pin)
gpio.add_event_detect(pin, gpio.RISING, bouncetime=200)
def reboot(pin):
#print "Reboot Function on pin %d" % pin
call('reboot', shell=False)
exit(0)
# Define a function to run when an interrupt is called
def shutdown(pin):
#print "ShutDown function on pin %d" % pin
system('echo Press again if you want to reboot instead of shut down! | wall -n') # let all logged in users know
gpio.remove_event_detect(pin)
gpio.add_event_detect(pin, gpio.RISING, bouncetime=200)
sleep(2) # if the button has been pressed for a third time, within 2 seconds, Pi will reboot
if gpio.event_detected(pin):
reboot(pin)
else:
call('halt', shell=False)
exit(0)
# Define a function to keep script running
def main():
#print "Main Function"
resetPin(22)
while True:
sleep(1)
if gpio.event_detected(22):
confirmation(22)
main() # Run the main function to keep script running

Python LIRC blocking Signal workaround not working

I've been having trouble with the Python LIRC function lirc.nextcode(). I turned off blocking, which allows the code lirc.nextcode() to be skipped if the LIRC queue is empty, by initializing with lirc.init("program", blocking=False) and tried lirc.set_blocking(False, sockid). Neither worked and the code would always hang, waiting for a button press, when it should continue on.
I found this workaround that puts a time limit on raw_input('prompt'). So even if my lirc.nextcodde() waits for a button press, an alarm will go off after 5 seconds if no button has been pressed to deactivate the alarm, and skips the code anyway:
import signal
class AlarmException(Exception):
pass
def alarmHandler(signum, frame):
raise AlarmException
def nonBlockingRawInput(prompt='', timeout=20):
signal.signal(signal.SIGALRM, alarmHandler)
signal.alarm(timeout)
try:
text = raw_input(prompt)
signal.alarm(0)
return text
except AlarmException:
print '\nPrompt timeout. Continuing...'
signal.signal(signal.SIGALRM, signal.SIG_IGN)
return ''
Then changed it to fit my needs:
import signal
import lirc
sockid = lirc.init('weather', blocking=False)
class AlarmException(Exception):
pass
def alarmHandler(signum, frame):
raise AlarmException
def nonBlockingRawInput(prompt='', timeout=5):
signal.signal(signal.SIGALRM, alarmHandler)
signal.alarm(timeout)
try:
text = lirc.nextcode()
signal.alarm(0)
print text
return text
except AlarmException:
print '\nPrompt timeout. Continuing...'
signal.signal(signal.SIGALRM, signal.SIG_IGN)
print 'timed out'
return ''
nonBlockingRawInput()
What I want to happen: If a button has been pressed and an IR code is in the LIRC queue, it should print the button that was pressed. If no button has been pressed and the LIRC queue is empty, it should print "Prompt timeout. Continuing..." and "timed out".
What actually happens: If a button has been pressed and an IR code is in the LIRC queue it prints the button, but if no button has been pressed and the queue is empty it hangs until I close it.
It works exactly as intended until I change text = raw_input(prompt) to text = lirc.nextcode(), then it hangs on that function until it closes and gives this error:
Traceback (most recent call last):
File "/home/pi/time.py", line 27, in <module>
nonBlockingRawInput()
File "/home/pi/time.py", line 16, in nonBlockingRawInput
text = lirc.nextcode()
File "/home/pi/time.py", line 10, in alarmHandler
raise AlarmException
__main__.AlarmException
So not only does turning off blocking for lirc.nextcode() not work, but it also prevents the Signal alarm code workaround from continuing as well.
Here is a link "Python Lirc blocks code even when blocking is off" to my original question regarding LIRC blocking, which is what this workaround was for. I'll gladly accept an answer for either.
Thanks ahead of time for any help, it's really appreciated.
Switching to Pylirc2 and using pylirc.blocking(0) fixed it.

Removing atexit tracebakc - atextit loops when it shouldn't

How can I remove the trackback from the command output, and how can I stop this looping in the atexit function?
!/usr/bin/python
import time, atexit, sys
import RPi.GPIO as GPIO
#16 = Motor 1 - Forward
#18 = Motor 1 - Back
#11 = Motor 2 - Forward
#13 = Motor 2 - Back
#6 = Ground
GPIO.setmode(GPIO.BOARD)
GPIO.setup(16, GPIO.OUT)
GPIO.setup(18, GPIO.OUT)
GPIO.setup(11, GPIO.OUT)
GPIO.setup(13, GPIO.OUT)
def exit_handler():
GPIO.cleanup()
print 'STOPPED'
while True:
GPIO.output(11, True)
GPIO.output(16, True)
print "Forward"
time.sleep(5)
atexit.register(exit_handler)
It seems to work OK, and runs the motor controller on my Pi (just - due to dodgy wiring
)
But when I run it, I get this as the output:
[wilf#Pi MovementCommands]$ 1/forward.py
Forward
Forward
Forward
Forward
Forward
Forward
Forward
Forward
Forward
Forward
Forward
Forward
Forward
Forward
Forward
Forward
Forward
Forward
Forward
^CTraceback (most recent call last):
File "1/forward.py", line 17, in <module>
time.sleep(5)
KeyboardInterrupt
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
STOPPED
[wilf#Pi MovementCommands]$
For some reason it seems to be looping the exit_handler nearly as many times as the main bit loops - how can I stop it doing this - it can take ages to exit if it has be running for a while, as it loops the cleanup command. (The main bit does not have to loop, but it is useful as visual indication that it is doing something - could I possibly just loop the print bit...).
I also seem to be unable to stop the traceback of atexit being displayed, without having to use except KeyboardInterrupt. This would be useful anyway, but I may later want to automate it by running it from a Bash script (yes I could integrate it into it, but I don't want to).
You are re-registering the atexit handler each time your loop iterates. Python calls the function as many times as it has been registered.
Register the function just once. You don't need to call sys.exit(0) in your atexit function either, you are already exiting your program.
Corrected program:
import time, atexit
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD)
def exit_handler():
GPIO.cleanup()
print 'STOPPED'
atexit.register(exit_handler)
while True:
GPIO.output(11, True)
GPIO.output(16, True)
print "Forward"
time.sleep(5)
Alternatively, catch the KeyboardInterrupt and clean up in the exception handler:
import time
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD)
try:
while True:
GPIO.output(11, True)
GPIO.output(16, True)
print "Forward"
time.sleep(5)
except KeyboardInterrupt:
pass # silence the interrupt
finally:
GPIO.cleanup()
print 'STOPPED'
The finally block calls GPIO.cleanup() regardless of what exception occurred, the except KeyboardInterrupt just silences the keyboard interrupt exception.
Note that the traceback was never in the atexit() function, it is the reason the program is exiting and why atexit() is called at all. In other words, the exception preceeds the atexit() function call.

Categories