Как правильно закрыть файл с обработчиком сигнала - PullRequest
0 голосов
/ 19 декабря 2018

Я использую следующий код для контроля доступа к файлу из запущенного задания.
Когда задание остановлено, мой код получает SIGINT.
Поскольку это задание очень интенсивное, существует буферизованный ввод-вывод и яя не могу снять эти записи с буфера, и мне нужен точный журнал.
Поэтому я попытался перехватить SIGINT и очистить файл перед тем, как завершить работу моего сценария, в результате я получил:

RuntimeError: reentrant call inside <_io.BufferedWriter name=

.Из нескольких прочитанных статей я понимаю, что невозможно последовательно использовать команду write / print / flush, так как они не являются поточно-ориентированными в обработчике сигналов.

Мой вопрос: как я могу убедиться, что мой файл записан правильно перед тем, как завершить работу сценария?

Вот более простая версия моего скрипта:

import signal
import sys
import os
import time
from time import strftime
import inotify.adapters

separator = ';'
jump = '\n'
logfile_pointer = open("path/to/log/file", 'w')

#Try to close nicely everything
def signal_handler(signal, frame):
    logfile_pointer.flush()
    logfile_pointer.close()
    sys.exit(0)

#Register signal handler
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGHUP, signal_handler)

eventHandler = inotify.adapters.InotifyTrees(["/folder/one","/folder/two"])

for event in eventHandler.event_gen():
    if event is not None:
        (_, type_names, path, filename) = event
        try:
            timestamp = '%.2f'%(time.time())
            filepath=path +'/'+ filename
            logfile_pointer.write ("{}{}{}{}{}{}{}{}".format(timestamp, separator, filepath , separator , type_names[0] ,separator, os.path.getsize(filepath) , jump )
        except os.error as e:
            pass

Ответы [ 2 ]

0 голосов
/ 26 декабря 2018

У меня было несколько проблем, одна из которых заключалась в том, чтобы не допустить запуска моего скрипта, поскольку у Python есть какая-то странная концепция потоков, вот мое решение:
определить поток, который будет наблюдателем inotify:

import os
import sys
import time
import signal
import argparse
import inotify.adapters
from time import strftime
from threading import Thread
from argparse import RawTextHelpFormatter


class EventMonitor(Thread):
    separator = ';'
    jump = '\n'
    def __init__(self, folders, logfile):
        Thread.__init__(self)
        check_message=''
        self.eventHandler = None
        self.stop = False
        self.logfile = open(logfile,'w',buffering=bufferSize)
        self.line_count = 0
        self.alive=True
        self.eventHandler = inotify.adapters.InotifyTrees(folders)


    def run(self):
        while not self.stop:
            for event in self.eventHandler.event_gen( timeout_s = 3 ):
                try:
                    if event is not None:
                        (_, type_names, path, filename) = event
                        timestamp = '%.2f'%(time.time())
                        filepath=path +'/'+ filename
                        self.logfile.write ("{}{}{}{}{}{}{}{}".format(timestamp, self.separator, filepath , self.separator , type_names[0] ,self.separator, os.path.getsize(filepath) , self.jump ))
                except os.error as e:
                    pass

        for event in self.eventHandler.event_gen( timeout_s = 1 ):
            try:
                if event is not None:
                    (_, type_names, path, filename) = event
                    timestamp = '%.2f'%(time.time())
                    filepath=path +'/'+ filename
                    self.logfile.write ("{}{}{}{}{}{}{}{}".format(timestamp, self.separator, filepath , self.separator , type_names[0] ,self.separator, os.path.getsize(filepath) , self.jump ))
            except os.error as e:
                pass
        self.logfile.flush()
        self.logfile.close()
        self.alive=False


    def stopped(self):
        if not self.stop:
            self.stop = True
        else:
            print("Event Monitoring is already disabled")

    def isAlive(self):
        return self.alive

Тогда в моем основном скрипте:

import os
import sys
import time
import signal
import argparse
import traceback
from time import strftime
from CPUMonitor import CPUMonitor
from EventMonitor import EventMonitor
from argparse import RawTextHelpFormatter


#define argument
parser = argparse.ArgumentParser(description='attache spies on multiple folders in argument and generate a csv log file containing a list of event on files.File is formatted like this:  \ntimestamp;fullpath;event;size\n123456897.25;/path/file;IN_OPEN;0\n/123456899.25;path/file;IN_CLOSE;1234\n.....\nFor more info about inotify events => `man inotify`',formatter_class=RawTextHelpFormatter)
parser.add_argument("-l", "--log-folder",type=str, help="Destination folder for the logs. If no value /tmp is used", default='/tmp')
parser.add_argument("-e", "--event", help="enable file event watch ",action="store_true")
parser.add_argument( 'folders', metavar='folderpath', type=str ,help='a list of folder path to spy on if -e is not set this will be ignore.', nargs = '*', default=[os.getcwd()])
args = parser.parse_args()

#Try to close nicely everything
def signal_handler(signal, frame):
    if CPU_thread is not None:
        CPU_thread.stopped()
    if Event_thread is not None:
        Event_thread.stopped()
    print('Kill signal receive.{}CPU and Event monitoring stopped.{}'.format(jump,jump))
    sys.exit(0)
#Register signal handler
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGHUP, signal_handler)

try:
    #define variable
    separator = ';'
    jump = '\n'
    logDest = ''
    go = True
    Event_logfile = None
    Event_logfile_debug = None
    Event_thread = None
    jobname = ''
    check_message=''

    if not os.path.isdir(args.log_folder):
        go=False
        check_message = check_message + "/!\ Log folder {} is not a directory. Monitoring won't start{}".format(args.log_folder,jump)
    elif not os.access(args.log_folder, os.W_OK | os.X_OK) :
        go=False
        check_message = check_message + "/!\ Log folder {} is not writable. Monitoring won't start{}".format(args.log_folder,jump)
    else:
        check_message = check_message + "Log folder is a proper directory and can be RW. {}".format(jump)

    if not go :
        print(check_message)
        sys.exit(-2)

    if go :
        event_logfile = args.log_folder + '/Event_'+os.environ['JOB_ID'] + '_' + strftime("%Y-%m-%d_%H:%M:%S") + '-log.txt'
        print('Event logfile: {}{}'.format(event_logfile,jump) )
        print( 'Start monitoring of the event on: {} {}'.format( args.folders, jump ))
        Event_thread = EventMonitor(args.folders, event_logfile)
        Event_thread.start()
    else:
        print(("Error detected, monitoring hasn't started{}".format(jump)))
        sys.exit(-4)

    while Event_thread is not None and Event_thread.isAlive() :
        time.sleep(5)

    if Event_thread is not None:
        Event_thread.join()

except Exception as error:
    traceback.print_exc()
    print(str(error))
    sys.exit(-5)  

В потоке, пока поток не остановлен, он будет искать события и записывать их в файл.
Когда вызывается stopped()цикл отключится через 3 секунды без события, затем я запускаю цикл событий в последний раз с более коротким тайм-аутом, равным 1 секунде, после обработки всех событий поток останавливается и isAlive() возвращает значение False.
В основной программекогда SIGINT или SIGHUP получено, он просит поток остановиться, и скрипт python останавливается только после правильной остановки потока.
Этот код работает как в Python 2.7.15 и 3.6.7 и выше;однако имейте в виду, что это упрощенная версия моего кода, и она может не работать как есть и может нуждаться в некоторой корректировке.

PS: благодаря ответу Стивена, который мне очень помогает.

0 голосов
/ 19 декабря 2018

Типичный подход здесь состоит в том, чтобы обработчик сигнала установил флаг и возвращался без выхода.Основной цикл проверяет флаг, а когда он установлен, очищается и завершается.

В этом конкретном случае это означает, что вам нужно регулярно производить выдачу события;с PyInotify вы можете сделать это, установив короткий тайм-аут.В конечном итоге это выглядело бы как

[...]

exit_requested = False

def signal_handler(signal, frame):
    # Perhaps check which signal was received...
    exit_requested = True

[...]

for event in eventHandler.event_gen(timeout_s = 1):
    if exit_requested:
        # Clean up and exit
    if event:
        ...

Когда event_gen возвращает None из-за истечения времени ожидания, события inotify, которые происходят до следующего вызова event_gen, будут поставлены в очередь и не потеряны: события inotifyпотребляется, когда они читаются из дескриптора файла inotify, и обработчик событий здесь сохраняет это открытым.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...