【问题标题】:Overriding basic signals (SIGINT, SIGQUIT, SIGKILL??) in Python在 Python 中覆盖基本信号(SIGINT、SIGQUIT、SIGKILL??)
【发布时间】:2011-04-19 04:53:24
【问题描述】:

我正在编写一个程序,根据我们公司的政策添加普通 UNIX 帐户(即修改 /etc/passwd、/etc/group 和 /etc/shadow)。它还做了一些稍微花哨的事情,比如向用户发送电子邮件。

我的所有代码都可以运行,但是有三段代码非常关键,它们更新了上面的三个文件。该代码已经相当健壮,因为它会锁定这些文件(例如 /etc/passwd.lock),写入临时文件(例如 /etc/passwd.tmp),然后用临时文件覆盖原始文件。我很高兴它不会干扰我程序的其他运行版本或系统 useradd、usermod、passwd 等程序。

我最担心的是这些部分中间有一个杂散的 ctrl+c、ctrl+d 或 kill 命令。这使我进入了信号模块,它似乎正是我想要的:在“关键”区域忽略某些信号。 我使用的是旧版本的 Python,它没有 signal.SIG_IGN,所以我有一个很棒的“通过”功能:

def passer(*a):
    pass

我看到的问题是信号处理程序没有按我预期的方式工作。 给定以下测试代码:

def passer(a=None, b=None):
pass

def signalhander(enable):
    signallist = (signal.SIGINT, signal.SIGQUIT, signal.SIGABRT, signal.SIGPIPE,       signal.SIGALRM, signal.SIGTERM, signal.SIGKILL)
    if enable:
        for i in signallist:
            signal.signal(i, passer)
    else:
        for i in signallist:
            signal.signal(i, abort)
    return


def abort(a=None, b=None):
    sys.exit('\nAccount was not created.\n')
    return
signalhander(True)                                                                                                                                                                                                                 
print('Enabled')
time.sleep(10)   # ^C during this sleep

此代码的问题在于 time.sleep(10) 调用期间的 ^C (SIGINT) 会导致该函数停止,然后,我的信号处理程序会根据需要接管。但是,这并不能解决我上面的“关键”区域问题,因为我不能容忍任何语句遇到失败的信号。

我需要某种完全忽略 SIGINT 和 SIGQUIT 的信号处理程序。 Fedora/RH 命令“yum”是用 Python 编写的,基本上完全符合我的要求。如果您在安装任何东西时执行 ^C,它将打印一条消息,例如“在两秒钟内按 ^C 强制终止”。否则,^C 将被忽略。我真的不在乎两秒钟的警告,因为我的程序在几分之一秒内完成。

有人可以帮我为 CPython 2.3 实现一个信号处理程序,它不会导致当前语句/函数在信号被忽略之前取消吗?

一如既往,提前致谢。


编辑:经过 S.Lott 的回答,我决定放弃信号模块。

我只是要回到try: except: 块。查看我的代码,每个无法中止的关键区域都会发生两件事:用 file.tmp 覆盖文件并在完成后删除锁(否则其他工具将无法修改文件,直到手动删除它)。我已经将它们中的每一个都放在了try: 块内的它们自己的函数中,except: 只是再次调用该函数。这样,该函数将在KeyBoardInterruptEOFError 事件中重新调用自身,直到关键代码完成。 我认为我不会遇到太多麻烦,因为我只捕获用户提供的退出命令,即便如此,也只有两到三行代码。从理论上讲,如果可以足够快地引发这些异常,我想我可以得到“超出最大递归深度”错误,但这似乎很遥远。

还有其他问题吗?

伪代码:

def criticalRemoveLock(file):
    try:
        if os.path.isFile(file):
            os.remove(file)
        else:
            return True
    except (KeyboardInterrupt, EOFError):
        return criticalRemoveLock(file)
def criticalOverwrite(tmp, file):
    try:
        if os.path.isFile(tmp):
            shutil.copy2(tmp, file)
            os.remove(tmp)
        else:
            return True
     except (KeyboardInterrupt, EOFError):
        return criticalOverwrite(tmp, file)

【问题讨论】:

  • 重试是一个非常糟糕的主意。您想撤消所有工作并将事物重置为开始时的状态。在存在键盘中断的情况下强制完成操作会使您的程序表现不佳。清理并提供一个很好的“没有完成”错误要好得多。为什么不像其他尝试关键事情的程序一样进行清理?
  • @S.Lott。在两种情况下重试是绝对必要的:删除锁定和 tmp 文件。您根本不能留下这些文件,否则用户下次将无法正常运行该程序。那就是清理。我更进一步,因为清理 (rm file.tmp file.lock) 不会比 mv file.tmp 文件花费更长的时间; rm 文件.lock。对于大多数应用程序,我都有“未创建帐户”。只是这三个关键部分。一旦应用程序到达那里,我想将修改视为原子。即使我决定恢复,也必须重试直到成功。
  • 对不起。 “重试是绝对强制性的”是错误的。重试是您想做的事情。许多应用程序崩溃,留下锁和临时文件,应用程序在下次运行时清理它们 - 或报告错误,表明应用程序仍在运行或已崩溃。我已经从崩溃的应用程序中删除了很多锁定文件。通常,这些应用程序会编写一个 PID 文件来帮助查找它是否仍在运行。删除工作文件远非“强制性”。这只是可取的,而且是不好的愿望。
  • @S.Lott。我认为你根本不明白我在做什么。当然,如果应用程序正常崩溃或收到无法解释的信号 (SIGKILL),则您无法可靠地执行任何操作,并且可能会陷入无限循环。如果我在得到IOError 之后继续重试os.remove() 操作,那将是一个错误。但是,我正在捕捉 ^C 和 ^D。有许多程序使这些信号不可用。 ^C,^D 可用于我的大部分程序,但在某些时候我必须(用数据库术语)“提交”或“回滚”更改。这些是原子操作。
  • 对不起。 “重试是绝对强制性的”是错误的。我认为递归是不明智的。

标签: python linux signals


【解决方案1】:

没有真正的方法可以让您的脚本真正保存。当然,您可以使用try: except: 忽略信号并捕获键盘中断,但是您的应用程序对此类中断具有幂等性,并且它必须能够在处理某种保存点的中断后恢复操作。

您真正可以做的唯一事情是处理临时文件(而不是原始文件),并在完成工作后将它们移动到最终目的地。我认为从文件系统的角度来看,此类文件操作应该是“原子的”。否则在中断的情况下:从干净的数据开始重新开始处理。

【讨论】:

  • @S.Lott 感谢您的回复。我目前正在使用临时文件和锁定文件。用我的解决方案查看我上面的编辑。
猜你喜欢
  • 2015-01-02
  • 1970-01-01
  • 2016-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-25
相关资源
最近更新 更多