简体   繁体   English

在 iOS 中使用 NSUrlSession 发送 SPDY 请求会导致“请求超时”错误

[英]Sending SPDY requests results in "The request timed out" errors with NSUrlSession in iOS

My iOS app loads images from an nginx HTTP server.我的 iOS 应用程序从 nginx HTTP 服务器加载图像。 After I send 400+ such requests the networking 'gets stuck' and all subsequent HTTP requests result in "The request timed out" error.在我发送了 400 多个这样的请求后,网络“卡住了”,所有后续的 HTTP 请求都会导致“请求超时”错误。 I can make the images load again only when I restart the app.只有当我重新启动应用程序时,我才能再次加载图像。

Details:细节:

  1. I am using NSURLSession.sharedSession().dataTaskWithURL to send four hundred HTTP GET requests to jpeg files.我正在使用NSURLSession.sharedSession().dataTaskWithURL向 jpeg 文件发送四百个 HTTP GET 请求。
  2. Requests are sent sequentially, one after another.请求按顺序发送,一个接一个。 The interval between requests is 10 ms.请求之间的间隔为 10 毫秒。
  3. Each previous unfinished request is cancelled with cancel() method of NSURLSessionDataTask object.使用NSURLSessionDataTask对象的cancel()方法取消每个先前未完成的请求。

Interestingly:有趣的是:

  1. I can only have this issue with HTTPS requests and when SPDY is enabled on the server.我只能在 HTTPS 请求和服务器上启用 SPDY 时遇到这个问题。
  2. Non-secure HTTP requests work fine.非安全 HTTP 请求工作正常。
  3. Non-SPDY HTTPS requests work fine.非 SPDY HTTPS 请求工作正常。 I tested it by turning SPDY off on the server side, in the nginx config.我通过在服务器端的 nginx 配置中关闭 SPDY 来测试它。
  4. Problem appears both on iOS 8 and 9, on physical device and in the simulator.问题出现在 iOS 8 和 9、物理设备和模拟器上。 Both on Wi-Fi and LTE.在 Wi-Fi 和 LTE 上。
  5. When I look at nginx access logs, I can still see the 'stuck' requests coming in. Important nuance: the request log record appears at the exact moment when the iOS app is giving up on it after the time out period ends.当我查看 nginx 访问日志时,我仍然可以看到进入的“卡住”请求。重要的细微差别:请求日志记录出现在 iOS 应用程序在超时期限结束后放弃它的确切时刻。
  6. I was hoping to analyze HTTP requests with Charles Proxy but the problem cures itself when requests go through Charles.我希望使用Charles Proxy分析 HTTP 请求,但是当请求通过 Charles 时问题会自行解决。 That is - everything works with Charles, much like effect in quantum mechanics when the fact of looking influences the outcome.也就是说 - 一切都与查尔斯一起工作,就像量子力学中的效应,当观察的事实影响结果时。
  7. I was able to reproduce the issue when the iOS app connected to two different servers with vastly different nginx configurations.当 iOS 应用程序连接到两个具有截然不同的 nginx 配置的不同服务器时,我能够重现该问题。 This probably means that the issue is not related to a particular nginx setup.这可能意味着该问题与特定的 nginx 设置无关。
  8. I analyzed the app using "Activity Monitor" instrument.我使用“活动监视器”工具分析了该应用程序。 The number of threads it is using during the bulk HTTP requests jumps from 5 to 10. In comparison, when I send just a single HTTP requests the number of threads jumps to 8. CPU load rarely goes above 30%.它在批量 HTTP 请求期间使用的线程数从 5 跳到 10。相比之下,当我只发送一个 HTTP 请求时,线程数会跳到 8。CPU 负载很少超过 30%。

What can be the cause of the issue?问题的原因是什么? Can anyone recommend other ways or tools for analysing and debugging it?任何人都可以推荐其他方法或工具来分析和调试它吗?

Analysing with scheduling instrument用调度工具分析

在此处输入图片说明

Demo app演示应用

This demo app reproduces the issue 100% of the time for me.这个演示应用程序 100% 地为我重现了这个问题。

https://github.com/exchangegroup/ImageLoadDemo https://github.com/exchangegroup/ImageLoadDemo

Versions and settings版本和设置

My nginx config: http://pastebin.com/pYYjdxfP我的 nginx 配置: http : //pastebin.com/pYYjdxfP

OS X : 10.10.4 (14E46), iOS : 8 and 9, Xcode : 7.0 (7A218), nginx : 1.9.4 OS X :10.10.4 (14E46), iOS :8 和 9, Xcode :7.0 (7A218), nginx :1.9.4

Not ideal workaround不理想的解决方法

I managed to keep requests working only if I create a new NSURLSession for each individual request and clear the previous session with finishTasksAndInvalidate or invalidateAndCancel .只有当我为每个单独的请求创建一个新的 NSURLSession 并使用finishTasksAndInvalidateinvalidateAndCancel清除上一个会话时,我才设法保持请求工作。

// Request 1

let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()     
let session = NSURLSession(configuration: configuration)
session.dataTaskWithURL ...

// Request 2

// clear the previous request
session.finishTasksAndInvalidate()
let session2 = NSURLSession(configuration: configuration)
session2.dataTaskWithURL ...

One possibility is that iOS started sending the request, and then packet loss prevented the headers and request body from being fully delivered.一种可能是 iOS 开始发送请求,然后丢包导致 headers 和 request body 无法完全传递。

Another possibility that comes to mind is that your server may not be logging the request until it actually finishes trying to deliver it, which would make the time stamps in the server logs line up with when the connection was closed, rather than when it was opened.想到的另一种可能性是,您的服务器在实际完成尝试交付之前可能不会记录请求,这将使服务器日志中的时间戳与连接关闭的时间对齐,而不是连接打开的时间. (IIRC, that's what Apache does; I haven't worked with nginx, so I can't speak for its behavior.) If that's the case, then this is just a simple connection stall. (IIRC,这就是 Apache 所做的;我没有使用过 nginx,所以我不能说它的行为。)如果是这种情况,那么这只是一个简单的连接停顿。 As for why it is stalling, I couldn't guess.至于为什么会卡顿,我猜不出来。

Does the problem occur exclusively for HTTPS traffic?问题是否仅针对 HTTPS 流量发生? If you can reproduce it with HTTP, you don't need Charles Proxy;如果你可以用 HTTP 重现它,你就不需要 Charles Proxy; just use OS X's "Internet Sharing" feature, and capture the packets with tcpdump or wireshark, listening on the bridge interface.只需使用 OS X 的“Internet 共享”功能,并使用 tcpdump 或 wireshark 捕获数据包,监听桥接接口。 If you can't reproduce it with HTTP, my money would be on a problem with fetching the CRLs or performing the OCSP check while validating the server's certificate.如果你不能用 HTTP 重现它,我的钱将用于获取 CRL 或在验证服务器证书时执行 OCSP 检查的问题。

Is your app ending up with a huge number of threads as a result of excessive async dispatching to new queues, by any chance?您的应用程序是否会因向新队列过度异步调度而导致大量线程? Because that could easily cause all sorts of odd misbehavior.因为那很容易导致各种奇怪的不当行为。

How long is the timeout?超时时间是多久? If it is too short, your app might simply be running up against performance limitations of the hardware while processing the results of 400 requests delivered in only four seconds.如果它太短,您的应用程序可能只是在运行时遇到硬件的性能限制,同时处理仅在四秒钟内交付的 400 个请求的结果。

Also, are you trying to schedule these requests simultaneously?另外,您是否尝试同时安排这些请求? Because I seem to recall reading about a bug that causes NSURLSession to hit a brick wall if you start too many tasks in a single session at the same time.因为我似乎记得读过一个错误,如果你在一个会话中同时启动太多任务,它会导致 NSURLSession 撞到砖墙。 You might try adding tasks only after the number of tasks in a session drops below some threshold and see if that fixes the problem.您可以仅在会话中的任务数量低于某个阈值后尝试添加任务,看看是否能解决问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM