#3520·trio

在 close() 之后调用 notify_closing 是否允许? 修复或记录

作者: arthur-tacca创建于 2026年9月21日更新于 2026年9月21日

(尽管我在某些地方使用了可疑的 LLMesque 格式,但这都是由人类写的。)

目前,notify_closing 的文档如下:

因此,要正确关闭某个对象,通常需要按照以下步骤执行:

  1. 明确标记对象为已关闭,以便任何新的尝试使用它都会在开始之前中止。
  2. 调用 notify_closing 来唤醒已存在的用户。
  3. 实际关闭对象。

也可以按照不同的顺序执行这些步骤,但前提是您确保在步骤之间没有任何检查点存在。

这来自于 pull request #3503 和 #3519,它们修复了 #3500;该问题的一部分是 notify_closing 在客户模式下出现了一个隐蔽的竞争条件,即使在正常顺序中调用也会失败。本问题更多地涉及 notify_closing (在错误的顺序) 仅在普通非客户模式下使用。

非常简短的摘要

事实证明,如果在 notify_closing 之前关闭对象,则会在 macOS/BSD 上失败(由于最近引入的且易于修复的一个错误),在 Windows 上失败(由于一个更复杂但可能可修复的错误),并且在 Linux 上似乎成功,但如果句柄已被 dup,则可能会在稍后产生虚假的等待唤醒(这无法修复)。 如果您只想知道如何处理此问题,可以跳到下面的“我的建议”部分!

背景:notify_closing 执行了什么?

这并不明显需要这样做:理想情况下,关闭套接字将导致底层操作系统等待(epoll/kqueue/IOCP)唤醒任何可读/可写等待,其中包含一个错误,表明套接字已关闭,我们可以在接收到该错误时将其转换为 ClosedResourceError。这并不是因为不同的 IO 管理器的原因。 kqueue 当您关闭文件描述符时,它会自动从 kqueue 中移除事件订阅。然而,移除是无声的:可读/可写等待者不会以任何方式被通知(没有“FD 已关闭”事件被发送给他们),因此 Trio wait_readable / wait_writable 将永远等待。notify_closing 调用做了两件事来解决这个问题:

  • 从轮询集中移除事件(_kqueue.control([kevent(fd, filter, KQ_EV_DELETE)], 0))。正如我刚才所说,关闭时会发生这种情况,因此这完全是多余的。

  • 对于该套接字上的任何等待者(wait_readable / wait_writable 的调用者),立即唤醒并返回 ClosedResourceErrorepoll 和 kqueue 一样,当您关闭文件描述符时,它会移除事件订阅,但如果套接字被 dup,则不会发生这种情况(参见下文)。即使移除了订阅,等待者也不会被通知。notify_closing 执行以下两个操作来解决这个问题:

  • 从轮询集中移除事件(_epoll.control([epoll_ctl(fd, EPOLL_CTL_DEL, 0)], 0))。正如我刚才所说,关闭时会发生这种情况,因此这完全是多余的。

  • 对于任何等待者(wait_readable / wait_writable 的调用者)在该套接字上,立即唤醒并返回 ClosedResourceError

内容来源: python-trio/trio