Перехват события shutdown в Python

Я опубликовал вопрос о том, как поймать событие «sudo shutdown-r 2» в Python. Я был отправлен в этот поток: выполнить код в скрипте python по сигналу завершения работы .

Я запускаю Raspberry Pi v2 с Джесси.

Я читал о

сигнал

и попытался следовать идеям в приведенном выше потоке, но до сих пор мне не удалось. Вот мой код:

import time
import signal
import sys
def CloseAll(Code, Frame):
    f = open('/mnt/usbdrive/output/TestSignal.txt','a')
    f.write('Signal Code:' + Code)
    f.write('Signal Frame:' + Frame)
    f.write('rn')
    f.close()
    sys.exit(0)

signal.signal(signal.SIGTERM,CloseAll)
print('Program is running')
try:
  while True:
#get readings from sensors every 15 seconds 
    time.sleep(15)

    f = open('/mnt/usbdrive/output/TestSignal.txt','a')
    f.write('Hello ')
    f.write('rn')
    f.close()

except KeyboardInterrupt:
     f = open('/mnt/usbdrive/output/TestSignal.txt','a')
     f.write('Done')
     f.write('rn')
     f.close()

Программа запускается в сеансе/окне» экран «и реагирует, как ожидалось, на CNTL-C. Однако, когда я выхожу из сеанса экрана, оставляя программу работающей, и введите» sudo shutdown-r 2″, Pi перезагружается, как ожидалось, через 2 минуты, но TestSignal.txt-файл не показывает, что сигнал.Обработано событие SIGTERM.

Что я делаю не так? Или еще лучше, как я могу поймать событие выключения, обычно инициируемое заданием cron, и закрыть мою программу Python, работающую в сеансе экрана изящно?

1 ответ

  1. Когда вы не пытаетесь ожидать такого события, но в параллельном сеансе отправляете SIGTERMэтому процессу (например, вызывая kill -15 $PIDидентификатор процесса $PIDзапущенного скрипта python) , вы должны увидеть поучительное сообщение об ошибке 😉

    Кроме того, комментарий о точке монтирования должен представлять интерес после исправления ошибок python (TypeError: cannot concatenate 'str' and 'int' objects).

    Попробуйте что-то вроде:

    import time
    import signal
    import sys
    
    LOG_PATH = '/mnt/usbdrive/output/TestSignal.txt'
    
    
    def CloseAll(Code, Frame):
        f = open(LOG_PATH, 'a')
        f.write('Signal Code:' + str(Code) + ' ')
        f.write('Signal Frame:' + str(Frame))
        f.write('\r\n')
        f.close()
        sys.exit(0)
    
    signal.signal(signal.SIGTERM, CloseAll)
    print('Program is running')
    try:
        while True:
            # get readings from sensors every 15 seconds
            time.sleep(15)
    
            f = open(LOG_PATH, 'a')
            f.write('Hello ')
            f.write('\r\n')
            f.close()
    
    except KeyboardInterrupt:
        f = open(LOG_PATH, 'a')
        f.write('Done')
        f.write('\r\n')
        f.close()
    

    в качестве отправной точки. Если это работает как-то на вашей системе, почему бы не переписать некоторые части, как:

    # ... 8< - - -
    def close_all(signum, frame):
        with open(LOG_PATH, 'a') as f:
            f.write('Signal Code:%d Signal Frame:%s\r\n' % (signum, frame))
        sys.exit(0)
    
    signal.signal(signal.SIGTERM, close_all)
    # 8< - - - ...
    

    Edit: чтобы еще больше изолировать ошибку и адаптировать ее к производственному режиму, можно переписать код следующим образом (учитывая, что системный журнал работает на машине, что и должно быть, но я никогда не работал на устройствах такого рода):

    #! /usr/bin/env python
    import datetime as dt
    import time
    import signal
    import sys
    import syslog
    
    LOG_PATH = 'foobarbaz.log'  # '/mnt/usbdrive/output/TestSignal.txt'
    
    
    def close_all(signum, frame):
        """Log to system log. Do not spend too much time after receipt of TERM."""
        syslog.syslog(syslog.LOG_CRIT, 'Signal Number:%d {%s}' % (signum, frame))
        sys.exit(0)
    
    # register handler for SIGTERM(15) signal
    signal.signal(signal.SIGTERM, close_all)
    
    
    def get_sensor_readings_every(seconds):
        """Mock for sensor readings every seconds seconds."""
        time.sleep(seconds)
        return dt.datetime.now()
    
    
    def main():
        """Main loop - maybe check usage patterns for file resources."""
        syslog.syslog(syslog.LOG_USER, 'Program %s is running' % (__file__,))
        try:
            with open(LOG_PATH, 'a') as f:
                while True:
                    f.write('Hello at %s\r\n' % (
                        get_sensor_readings_every(15),))
        except KeyboardInterrupt:
            with open(LOG_PATH, 'a') as f:
                f.write('Done at %s\r\n' % (dt.datetime.now(),))
    
    if __name__ == '__main__':
        sys.exit(main())
    

    Пункты, котор нужно заметить:

    1. файл журнала для фактических измерений отделен от канала регистрации для оперативных оповещений
    2. дескриптор файла журнала защищен в блоках управления контекстом и в обычной операции просто остается открытым
    3. для оповещения syslogиспользуется канал.
    4. в качестве примера для маршрутизации сообщений syslog.LOG_USERon my system (OS X) предоставляет мне во всех терминалах сообщение, в то время syslog.LOG_ERRкак приоритетное сообщение в обработчике сигналов предназначено только для системного журнала.
    5. должно быть больше к пункту во время перебранки выключения (не раскрывая файл, ЕТК.)

    Последний пункт (5.) важно в случае, если все процессы получают SIGTERMво время завершения работы, т. е. все хотят что-то сделать (замедление), возможноscreen, также не принимает никакой буферизованный вход больше (или не смывает), обратите stdoutвнимание, что блок буферизован, а не строка буферизована.

    Развязка выходных каналов также должна облегчить возможное исчезновение точки монтирования файла журнала измерений.