【问题标题】:Setting stdout to non-blocking in python在python中将stdout设置为非阻塞
【发布时间】:2017-12-10 10:18:40
【问题描述】:

事先警告:出于好奇,我在这里闲逛。我没有具体的理由去做我在下面做的事情!

以下是在Python 2.7.13MacOS 10.12.5上完成的

我正在使用 python 进行黑客攻击,我想看看如果我让 stdout 非阻塞会发生什么会很有趣

fcntl.fcntl(sys.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)

fcntl 的调用肯定是成功的。然后我尝试写入大量数据(大于 OSX 上管道的最大缓冲区大小 - 即 65536 字节)。我以多种方式做到这一点并得到不同的结果,有时是例外,有时似乎是一个艰难的失败。

案例 1

fcntl.fcntl(sys.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
try:
    sys.stdout.write("A" * 65537)
except Exception as e:
    time.sleep(1)
    print "Caught: {}".format(e)

# Safety sleep to prevent quick exit
time.sleep(1)

这总是抛出异常Caught: [Errno 35] Resource temporarily unavailable。我觉得有道理。更高级别的文件对象包装器告诉我写调用失败。

案例 2

fcntl.fcntl(sys.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
try:
    sys.stdout.write("A" * 65537)
except Exception as e:
    print "Caught: {}".format(e)

# Safety sleep to prevent quick exit
time.sleep(1)

这有时会抛出异常 Caught: [Errno 35] Resource temporarily unavailable 或者有时没有捕获到异常,我看到以下输出:

close failed in file object destructor:
sys.excepthook is missing
lost sys.stderr

案例 3

fcntl.fcntl(sys.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
try:
    sys.stdout.write("A" * 65537)
except Exception as e:
    print "Caught: {}".format(e)

# Safety sleep to prevent quick exit
time.sleep(1)

print "Slept"

这有时会引发异常 Caught: [Errno 35] Resource temporarily unavailable 或者有时没有捕获到异常,我只看到“睡眠”。看来printing "Slept" 我没有收到案例 2 的错误消息。

案例 4

fcntl.fcntl(sys.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
try:
    os.write(sys.stdout.fileno(), "A" * 65537)
except Exception as e:
    print "Caught: {}".format(e)

# Safety sleep to prevent quick exit
time.sleep(1)

总是好的!

案例 5

fcntl.fcntl(sys.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
try:
    print os.write(sys.stdout.fileno(), "A" * 65537)
except Exception as e:
    print "Caught: {}".format(e)

# Safety sleep to prevent quick exit
time.sleep(1)

这有时可以,有时会打印close failed in file object destructor 错误消息。

我的问题是,为什么在 python 中会这样失败?我在这里做的事情从根本上是不好的 - 无论是使用 python 还是在系统级别?

似乎以某种方式在写入已经失败时过早写入标准输出会导致错误消息。该错误似乎不是异常。不知道它来自哪里。

注意我可以用 C 编写等效的程序,并且可以正常工作:

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <sys/fcntl.h>
#include <unistd.h>

int main(int argc, const char * argv[])
{
    const size_t NUM_CHARS = 65537;
    char buf[NUM_CHARS];

    // Set stdout non-blocking
    fcntl(fileno(stdout), F_SETFL, O_NONBLOCK);

    // Try to write a large amount of data
    memset(buf, 65, NUM_CHARS);
    size_t written = fwrite(buf, 1, NUM_CHARS, stdout);

    // Wait briefly to give stdout a chance to be read from
    usleep(1000);

    // This will be written correctly
    sprintf(buf, "\nI wrote %zd bytes\n", written);
    fwrite(buf, 1, strlen(buf), stdout);
    return 0;
}

【问题讨论】:

  • 认为的问题是,对于非阻塞 IO,对os.write 的调用可以在写入完成之前返回,并且由于这结束了您的简单测试程序, sys.stdout 也可以在写入完成之前关闭。请注意,您的 C 程序会等待 (usleep(1000)),而您的 Python 代码则不会。
  • 感谢您的回复。我应该发布更完整的 python 代码 sn-p。写电话后我有一个time.sleep(1),这没什么区别:(
  • 你的操作系统是什么?您可以尝试在 strace 之类的名称下运行 Python,然后查看执行了哪些系统调用。
  • 让我更新一下帖子,因为我可以澄清一些事情。各种调用的行为略有不同。
  • @Andrew Parker 使用延迟而不是 fflush。实际上有趣的想法。它来自一些 arduino 经验吗? ://

标签: python c posix nonblocking fcntl


【解决方案1】:

这很有趣。到目前为止,我发现了一些东西:

案例 1

这是因为sys.stdout.write 将写入所有字符串或抛出异常,这不是使用O_NONBLOCK 时所期望的行为。当对write 的底层调用返回EAGAIN(OS X 上的Errno 35)时,应该使用剩余的数据再次尝试。应该改用os.write,并检查返回值以确保所有数据都已写入。

此代码按预期工作:

fcntl.fcntl(sys.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)

def stdout_write(s):
    written = 0
    while written < len(s):
        try:
            written = written + os.write(sys.stdout.fileno(), s[written:])
        except OSError as e:
            pass

stdout_write("A" * 65537)

案例 2

我怀疑此错误消息是由于 https://bugs.python.org/issue11380:

close failed in file object destructor:
sys.excepthook is missing
lost sys.stderr

我不确定为什么有时会调用它。这可能是因为except 语句中有一个print 试图使用写入失败的相同stdout

案例 3

这与案例 1 类似。此代码对我始终有效:

fcntl.fcntl(sys.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)

def stdout_write(s):
    written = 0
    while written < len(s):
        try:
            written = written + os.write(sys.stdout.fileno(), s[written:])
        except OSError as e:
            pass

stdout_write("A" * 65537)

time.sleep(1)

print "Slept"

案例 4

确保检查os.write的返回值,我怀疑65537个字节没有被成功写入。

案例 5

这与案例 2 类似。

【讨论】:

  • 感谢@Tim 的输入。在过去的一周里,我没有机会回到这个话题。我对这个问题进行了更长时间的研究。简而言之,来自sys.stdout.write 的异常是完全可以预料的(查看了源代码)。如果写入的字节数少于预期或ferror 在底层 FILE 句柄上返回非零值,file.write 将引发异常。所以谜团当然是围绕着错误信息。我查看了源代码并调试了自己的构建,但我尚未 100% 确定根本原因(将继续下一条评论)。
  • 错误发生在解释器关闭期间,当解释器关闭 std 文件句柄时(它按 1、0、2 的顺序执行,即 stdout、stdin、stderr)。这是标准输出的关闭失败(不足为奇)。特别是fclose 失败。当stdout 的底层FILE 句柄的_flags 设置了__SERR(这是OSX 的libc 实现细节)时,就会发生这种情况。我认为这可能是在关闭时的同花顺调用期间,但似乎不是。无论如何,python 的行为“很好”,因为它在无法正确关闭 stdout 时打印错误。然而,...
  • 我不完全明白是什么让FILE 句柄处于这种状态。我试图在 C 中重现,但没有运气。我也在解释器中四处乱窜,但没有一个更聪明。我想我可以找到问题,但我现在已经深入兔子洞,所以需要暂时移开视线:)我可能会出于好奇而回到这个问题。在这里,python 可能存在一个奇怪的边缘案例错误,但即使有,也不太可能有人会这样做!
猜你喜欢
  • 1970-01-01
  • 2011-12-19
  • 2011-01-10
  • 2015-09-22
  • 2020-09-21
  • 2015-08-17
  • 2011-07-15
  • 2012-04-30
  • 2013-05-24
相关资源
最近更新 更多