2017-07-06 35 views
5

预先警告:我在这里出于好奇而窃窃私语。我没有具体的理由去做我在下面做的事情!在python中设置标准输出为非阻塞

下面是对Python 2.7.13做了MacOS 10.12.5

我与蟒蛇黑客周围,我认为这会是有趣的,看看发生了什么,如果我做了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或有时没有发现异常,我只看到“睡眠”。看来,通过print ING“同床”我不从病例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还是在系统级?

这似乎是某种程度上,当写入失败导致出现错误消息时,写入过早来标准输出。该错误似乎不是一个例外。不知道它来自哪里。

N.B.我可以在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; 
} 
+0

I * *想到的问题是,与非阻塞IO,调用'os.write'可以返回前写的完成,并且由于这结束了您的简单测试程序,所以'sys.stdout'也可以在写入完成之前关闭。请注意,你的C程序等待('usleep(1000)')你的Python代码没有。 – chepner

+0

感谢您的回复。我应该发布一个更完整的Python代码片段。在写入调用之后,我有'time.sleep(1)',它没有什么区别:( –

+0

你的操作系统是什么?你可以尝试在'strace'之类的东西下运行Python,看看系统调用了什么。 –

回答

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

+0

感谢您输入@Tim。上周我没有机会回到这里。我已经花了很长时间看这个问题。总之,来自'sys.stdout.write'的异常是完全可以预料的(查看源代码)。如果写入的字节少于预期值,或者'ferror'在底层FILE句柄上返回非零值,'file.write'将引发异常。所以这个谜团当然是围绕着错误信息。我查看了源代码并调试了自己的版本,但我仍未100%确定根本原因(将继续下一个评论)。 –

+0

错误发生在解释器关闭期间,当解释器关闭std文件句柄(它按照顺序1,0,0,即stdout,stdin,stderr)。这是关闭标准输出失败(不出所料)。特别是'fclose'失败。它恰好发生在'stdout'的底层'FILE'句柄的'_flags'设置了__SERR'(这是OSX的libc实现细节)。我认为这可能是在结束通话期间,但似乎不是。无论如何,python表现得“很好”,因为它无法正确关闭'stdout'时会打印错误。然而,... –

+0

我不完全理解什么把'FILE'句柄置于该状态。我试图在C中重现,但没有运气。我也曾在口译员身边砍过,没有人更聪明。我想我可以找到问题,但我已经深入兔子洞,所以需要暂时离开一下:)我可能会因为好奇而回到这个问题。这里隐约可能有一个奇怪的边缘案例与python的bug,但即使有它是不太可能的人会这样做! –