curl_multi_wakeup 似乎没有唤醒相关的 curl_multi_poll - Android(但可能不限于)

Pul*_*ulo 9 c c++ android curl

卷曲版本:7.71.0 带 c-ares

背景

我们正在构建一个集成到移动应用程序中的库。我们同时针对 iOS 和 Android。Curl 初始化发生在库内的静态块中。

如果我没记错的话,该库的 iOS 版本被捆绑到一个框架中,该框架在应用程序启动时加载。该库的 Android 版本捆绑在一个模块中,该模块被延迟加载。(我知道这是一个问题,特别是因为我们链接到 OpenSSL,但这对于上下文可能很重要)。

我们使用 curl 构建了一个小型 HTTP 客户端,它允许用于从受信任的服务器下载一些数据 blob。

快速架构审查

HTTP 客户端在其自己的线程上运行。它持有 a curl_multi_handle,并且任何传输开始时都将 a 附加curl_easy_handle到它,并返回一个Response包含缓冲区的句柄以从中读取接收到的字节,并在需要时用于控制传输。

由于 cURL 句柄不是线程安全的,因此对该句柄的任何操作(从现在开始称为任务)都被分派到 HTTP 客户端的线程,并且 boost::shared_future返回 a(我们可能想阻止或不阻止,具体取决于用例)。

以下是主循环结构的粗略概念:

while (!done) {
    deal_with_transfer();
    check_transfer_status();
    cleanup_any_orphan_transfer();
    execute_all_queue_tasks();
    curl_multi_poll(multi, nullptr, 0, very_large_number, nullptr);
}
Run Code Online (Sandbox Code Playgroud)

附加到任务队列还执行 acurl_multi_wakeup(multi)以确保任务被执行(例如,添加新下载也是一个分派任务)。

问题

到目前为止,我们只在 Android 上进行了测试,并且我们看到在某些情况下,被阻塞的 HTTP 客户端任务有时永远不会返回。

日志和堆栈跟踪显示我们等待 HTTP 客户端正在执行的任务,但客户端仍在轮询。一切似乎都表明它在附加任务时没有被唤醒。

我似乎无法在设备上本地复制该问题,但它经常发生,足以成为一个阻止程序问题。

我在这里有点不知所措,我真的不知道从哪里开始寻找重现问题的方法,更不用说修复它了。

我希望我提供了足够的上下文来开始进行有根据的猜测,甚至找到错误的根源!

谢谢阅读!

jor*_*ski 1

后台进程网络活动的限制

与传统的 *nix 操作系统相比,Android 和 iOS 等移动操作系统对后台进程的调度策略不同。移动操作系统往往会导致后台进程处于饥饿状态,以节省电池时间。这被称为background process optimization并将在应用程序进入后台时应用于应用程序的进程/线程。

从 Android 7 开始,后台进程不再通过CONNECTIVITY_ACTION广播获知网络事件,除非它们在清单中注册想要接收事件。

尽管在android 本机代码lobcurl中使用,但从本机库创建的线程将受到应用程序在清单中声明的​​权利(需要授予)的约束。

可以尝试的解决方法

我知道阻塞问题是多么令人沮丧,因此我可以为您提供一个快速解决方法,供您尝试,直到问题得到解决。

curl_multi_poll()可以接收在代码中设置为 的超时very_large_number。此外,函数调用的最后一个参数是一个指向整数的指针numfds,该整数将填充池化时发生事件的文件描述符的数量curl_multi_pool()

您可以利用这一点,通过以下方式构建解决方法:

  1. 制作very_large_number一个reasonably_small_number
  2. 替换nullptr&numfds
  3. curl_multi_poll用一个do ... while环将其包围起来

所以你会得到这样的东西:

int numfds;
while (!done) {
    deal_with_transfer();
    check_transfer_status();
    cleanup_any_orphan_transfer();
    execute_all_queue_tasks();
    numfds = 0;
    do {
        curl_multi_poll(multi, nullptr, 0, reasonably_small_number, &numfds);
        numfds += check_for_other_conditions();
   } while ( numfds == 0 );
}
Run Code Online (Sandbox Code Playgroud)

选择一个合理的超时值(例如 1 秒、10 秒、60 秒……),这样您可以强力中断池化,同时又不会耗尽电池电量。

我添加了,check_for_other_conditions()以便您可以使用 is 来检查其他条件。以任务队列的大小为例,假设在某些情况下可能curl_multi_poll()会错过事件,尽管事件发生了,但这种额外的检查可以帮助打破循环并开始执行任务。