os.listdir 可以挂在网络驱动器上吗?它使用什么系统调用?

Can os.listdir hang with network drives? What system call does it use?

os.listdir 在内部执行什么系统调用,是否有可能 Python 进程因 os.listdir 挂载在已安装的网络驱动器上而挂起?

我们怀疑我们的应用服务器存在问题,因为 os.listdir 试图列出安装在 linux 机器上的 samba 共享。显然,在我们遇到此问题时,samba 共享的 DNS 已经发生了变化。我们仍在尝试复制这种情况,但谁能告诉我它是如何工作的?像 ls 这样的命令也会像这样挂起吗?

我们有什么方法可以在 user-space 处处理这个问题吗?

CPython的implementation of os.listdir uses platform-specific C library calls to read the contents of a directory. On Unix-like platforms those are opendir(3) and readdir(3), and on Windows it uses FindFirstFile and FindNextFile.

这些调用在存在无法访问的网络文件系统时的行为方式将取决于操作系统。当使用 Linux 或 Windows 时,它们肯定会在 ls 等系统命令挂起的情况下挂起。为了防止任意长的暂停,可以使用专门的框架,例如 asyncio and twisted,它使用非阻塞 IO。但是,这些框架的使用可能令人生畏,并且通常需要在整个应用程序和整个程序中使用它们以实现事件驱动模型。

确保 IO 系统调用在存在网络文件系统时不会阻塞的一种更简单且对初学者更友好的方法是使用线程。例如,这里有一个 safe_listdir 函数,它 returns 目录内容,或者 None 如果调用时间超过指定的超时时间:

import os, threading

def safe_listdir(directory, timeout):
    contents = []
    t = threading.Thread(target=lambda: contents.extend(os.listdir(directory)))
    t.daemon = True  # don't delay program's exit
    t.start()
    t.join(timeout)
    if t.is_alive():
        return None  # timeout
    return contents

在 Python 3 中,可以使用优秀的 concurrent.futures 包。它不仅简化了实现,如果 safe_listdir 被多次调用,它会自动限制创建线程的数量,并确保 os.listdir 中引发的异常被正确传播给调用者:

import os, concurrent.futures
pool = concurrent.futures.ThreadPoolExecutor()

def safe_listdir(directory, timeout):
    future = pool.submit(os.listdir, directory)
    try:
        return future.result(timeout)
    except concurrent.futures.TimeoutError:
        return None  # timeout