【发布时间】: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: 只是再次调用该函数。这样,该函数将在KeyBoardInterrupt 或EOFError 事件中重新调用自身,直到关键代码完成。
我认为我不会遇到太多麻烦,因为我只捕获用户提供的退出命令,即便如此,也只有两到三行代码。从理论上讲,如果可以足够快地引发这些异常,我想我可以得到“超出最大递归深度”错误,但这似乎很遥远。
还有其他问题吗?
伪代码:
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 可用于我的大部分程序,但在某些时候我必须(用数据库术语)“提交”或“回滚”更改。这些是原子操作。 -
对不起。 “重试是绝对强制性的”是错误的。我认为递归是不明智的。