Hacker News 中文摘要

RSS订阅

RIP pthread_cancel -- RIP pthread_cancel

文章摘要

文章讨论了在curl 8.16.0版本中引入pthread_cancel以解决getaddrinfo()阻塞问题,但由于其导致线程管理复杂性和不可控性,最终决定在后续版本中移除该功能。

文章总结

标题:告别pthread_cancel

主要内容:

大约三周前,我发布了关于在curl中添加pthread_cancel使用的文章,并在curl 8.16.0版本中发布了这一功能,但结果却出乎意料地糟糕。现在,通过#18540,我们决定再次移除这一功能。究竟发生了什么?

简要回顾:

pthreads定义了“取消点”,即一系列POSIX函数,在这些函数中pthread可能会被取消。此外,还有一些函数可能是取消点,其中包括getaddrinfo()

getaddrinfo()正是我们在libcurl中感兴趣的函数。它会阻塞,直到解析完一个名称。这可能会导致长时间的挂起,而libcurl在此期间无法执行其他操作。因此,我们启动一个pthread来调用getaddrinfo(),这样libcurl可以在该线程运行时执行其他任务。

但最终,我们必须再次处理这个pthread。这意味着我们要么使用pthread_join()进行阻塞等待,要么调用pthread_detach(),后者会立即返回,但线程会继续运行。这两种方式在处理大量传输时都不理想。要么我们阻塞并停滞,要么让pthread以不受控制的方式堆积。

因此,我们添加了pthread_cancel()来中断正在运行的getaddrinfo()并释放不再需要的pthread。理论上这是可行的,经过一番努力,我们确实实现了这一功能。

取消成功,但内存泄漏也随之而来!

在发布curl 8.16.0后,我们在#18532中收到报告,称被取消的pthreads存在内存泄漏问题。

Image 1: modern times sigh

深入glibc源码后发现,有一个名为/etc/gai.conf的文件,它定义了getaddrinfo()应如何对返回的答案进行排序。

glibc的实现首先将名称解析为地址,这需要分配内存。然后,如果有多个地址,它需要对这些地址进行排序。为此,它需要读取/etc/gai.conf,并调用fopen()打开该文件。而fopen()可能是一个pthread“取消点”(如果不是,它肯定会调用open(),这是一个必需的取消点)。

因此,pthread可能在读取/etc/gai.conf时被取消,导致所有已分配的内存泄漏。如果它在那里被取消,下次解析多个地址时,它会再次尝试读取/etc/gai.conf

此时,我决定放弃整个pthread_cancel()策略。读取/etc/gai.conf是被取消的getaddrinfo()可能泄漏的一个点,可能还有其他点。显然,glibc并没有设计来防止这种泄漏(诚然,这并不容易)。

告别pthread_cancel

libcurl反复执行的操作中潜在的内存泄漏是不可接受的。我们宁愿承担等待长时间运行的getaddrinfo()的代价。

使用libcurl的应用程序可以通过使用c-ares来避免这个问题,它可以在不使用线程的情况下进行非阻塞解析。但c-ares无法实现glibc的所有功能。

DNS的使用依然充满挑战。

评论总结

评论主要围绕getaddrinfopthread_cancel的使用及其替代方案展开,以下是总结:

  1. pthread_cancel的批评

    • 多位评论者认为pthread_cancel设计不佳,难以使用且容易引发问题。例如,pizlonator表示:“pthread_cancel这样的API太难用了”,comex则指出:“pthread_cancel与正常的错误处理机制完全分离,设计不佳”。
  2. 替代方案建议

    • 一些评论者提出了异步处理或线程管理的替代方案。Aardwolf建议:“可以使用一些永久运行的线程来处理任务,无需取消”,throwaway81523提到:“可以通过io_uring实现异步getaddrinfo,或者让线程超时退出”。
  3. getaddrinfo的改进建议

    • 评论者认为getaddrinfo应支持超时设置和异步操作。yardstick指出:“为什么getaddrinfo没有标准化的超时设置?设置10秒超时会让事情简单很多”,albertzeyer建议:“为什么不使用getaddrinfo_a或其他异步DNS解析库?”。
  4. 对C库维护者的理解

    • 有评论者呼吁对C库维护者给予更多理解。jart表示:“取消操作对C库作者来说非常复杂,请给他们一些宽容,尤其是如果只是内存泄漏问题”。
  5. 历史背景与现状

    • 评论者提到DNS解析问题的历史背景。rwmj回忆:“Netscape曾经通过创建新线程来处理DNS查询,没想到30年后这仍然是个问题”。

总结:评论者普遍认为pthread_cancel设计不佳,建议采用异步处理或线程管理作为替代方案,同时呼吁对getaddrinfo进行改进,如支持超时设置和异步操作,并对C库维护者给予更多理解。