Reputation: 5933
I've been working on a monitoring script for a raspberry pi that i'm running as a headless server. As part of that I want it to react to a shutdown event.
I tried using the signal
module, and it does react and call my shutdown routine, however it happens very late into the shutdown routine, I'd like to try and find a way to make it react very quickly after the shutdown request is issued, rather than waiting for the operating system to ask python to exit.
this is running on a raspberry pi 1 B, using the latest jessie lite image I'm using python 3 and my python script itself is the init script:
#!/usr/bin/python3
### BEGIN INIT INFO
# Provides: monitor
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Start the monitor daemon
# Description: Start the monitor daemon during system boot
### END INIT INFO
import os, psutil, socket, sys, time
from daemon import Daemon
from RPLCD import CharLCD
from subprocess import Popen, PIPE
import RPi.GPIO as GPIO
GPIO.setwarnings(False)
def get_cpu_temperature():
process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE)
output, _error = process.communicate()
output = output.decode('utf8')
return float(output[output.index('=') + 1:output.rindex("'")])
class MyDaemon(Daemon):
def run(self):
lcd = CharLCD(pin_rs=7, pin_rw=4, pin_e=8, pins_data=[25, 24, 23, 18], numbering_mode=GPIO.BCM, cols=40, rows=2, dotsize=8)
while not self.exitflag:
gw = os.popen("ip -4 route show default").read().split()
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.connect((gw[2], 0))
ipaddr = s.getsockname()[0]
lcd.cursor_pos = (0, 0)
lcd.write_string("IP:" + ipaddr)
gateway = gw[2]
lcd.cursor_pos = (1, 0)
lcd.write_string("GW:" + gateway)
except IndexError:
lcd.cursor_pos = (0, 0)
lcd.write_string("IP:No Network")
lcd.cursor_pos = (1, 0)
lcd.write_string("GW:No Network")
host = socket.gethostname()
lcd.cursor_pos = (0, 20)
lcd.write_string("Host:" + host)
for num in range(10):
temp = get_cpu_temperature()
perc = psutil.cpu_percent()
lcd.cursor_pos = (1, 20)
lcd.write_string("CPU :{:5.1f}% {:4.1f}\u00DFC".format(perc, temp))
if (self.exitflag):
break
time.sleep(2)
lcd.clear()
## lcd.cursor_pos = (13, 0)
lcd.write_string("Shutting Down")
if __name__ == "__main__":
daemon = MyDaemon('/var/run/monitor.pid')
if len(sys.argv) == 2:
if 'start' == sys.argv[1]:
daemon.start()
elif 'stop' == sys.argv[1]:
daemon.stop()
elif 'restart' == sys.argv[1]:
daemon.restart()
elif 'run' == sys.argv[1]:
daemon.run()
else:
print("Unknown command")
sys.exit(2)
sys.exit(0)
else:
print("usage: %s start|stop|restart" % sys.argv[0])
sys.exit(2)
"""Generic linux daemon base class for python 3.x."""
import sys, os, time, signal
class Daemon:
"""A generic daemon class.
Usage: subclass the daemon class and override the run() method."""
def __init__(self, pidfile):
self.pidfile = pidfile
self.exitflag = False
signal.signal(signal.SIGINT, self.exit_signal)
signal.signal(signal.SIGTERM, self.exit_signal)
def daemonize(self):
"""Deamonize class. UNIX double fork mechanism."""
try:
pid = os.fork()
if pid > 0:
# exit first parent
sys.exit(0)
except OSError as err:
sys.stderr.write('fork #1 failed: {0}\n'.format(err))
sys.exit(1)
# decouple from parent environment
os.chdir('/')
os.setsid()
os.umask(0)
# do second fork
try:
pid = os.fork()
if pid > 0:
# exit from second parent
sys.exit(0)
except OSError as err:
sys.stderr.write('fork #2 failed: {0}\n'.format(err))
sys.exit(1)
# redirect standard file descriptors
sys.stdout.flush()
sys.stderr.flush()
si = open(os.devnull, 'r')
so = open(os.devnull, 'a+')
se = open(os.devnull, 'a+')
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
pid = str(os.getpid())
with open(self.pidfile,'w+') as f:
f.write(pid + '\n')
def start(self):
"""Start the daemon."""
# Check for a pidfile to see if the daemon already runs
try:
with open(self.pidfile,'r') as pf:
pid = int(pf.read().strip())
except IOError:
pid = None
if pid:
message = "pidfile {0} already exist. Daemon already running?\n"
sys.stderr.write(message.format(self.pidfile))
sys.exit(1)
# Start the daemon
self.daemonize()
self.run()
def stop(self):
"""Stop the daemon."""
# Get the pid from the pidfile
try:
with open(self.pidfile,'r') as pf:
pid = int(pf.read().strip())
except IOError:
pid = None
if not pid:
message = "pidfile {0} does not exist. Daemon not running?\n"
sys.stderr.write(message.format(self.pidfile))
return # not an error in a restart
# Try killing the daemon process
try:
while 1:
os.kill(pid, signal.SIGTERM)
time.sleep(0.1)
except OSError as err:
e = str(err.args)
if e.find("No such process") > 0:
if os.path.exists(self.pidfile):
os.remove(self.pidfile)
else:
print (str(err.args))
sys.exit(1)
def restart(self):
"""Restart the daemon."""
self.stop()
self.start()
def exit_signal(self, sig, stack):
self.exitflag = True
try:
os.remove(self.pidfile)
except FileNotFoundError:
pass
def run(self):
"""You should override this method when you subclass Daemon.
It will be called after the process has been daemonized by
start() or restart()."""
so in short is there any way i can detect a shutdown even as early as possible in the shutdown no matter how its called, and preferably able to detect a reboot aswell from within python
Upvotes: 0
Views: 1651
Reputation: 36346
Don't react. Schedule.
Unixoid systems have well-established mechanisms for starting and stopping services when starting up and shutting down. Just add one of these to be stopped when your system shuts down; you can typically even define an order at which these shutdown scripts can be called.
Now, which of these systems your Linux uses is not known to me. Chances are that you're using either
In either way, there's a lot of examples of such service files on your system; if you're running systemd, a systemctl
will show which services are loaded currently, and shows which files you should look into copying and adding as your own service. If you're running a SysV-Style init, look into /etc/init.d for a lot of scripts.
You'll find a lot of information how to add and enable init scripts or systemd service files for specific runlevels/system targets.
Upvotes: 1