解决办法
降低http请求的频率针对Android 8.1.0,level 27 设备,做单独处理cancel掉多余的请求(待验证,后期再写结果)
相关Bug背景分析,为啥是8.1.0呢?
结论
页面存在过多的 https 请求,每次申请ssl资源时,创建一个pipe,最后耗光了 可打开文件描述符的数量明确 页面的https 资源消耗情况是不是没有调用cancel?(原因看下文 4. OKHttp里用pipe )
我们是监听EditText输入,有文字变更就 https请求。可能某些情境下,突破了OkHttp的逻辑(socket的复用、https的复用了)。
什么是文件描述符 File Descriptor
Pipe相关
崩溃发生在创建pipe的系统调用时。pipe一般用于IPC通信。如Linux shell 将第一个命令输出,作为第二个命令输入。adb logcat | grep "ActivityManager",其中 | 管道符号 ,就是用的pipeint fdArray[2]; pipe[fdArray];fdArray文件描述符数组,fdArray[0]读,fdArray[1]写。然后调用IOwrite(fdArray[1], input_str, strlen(input_str)+1);就可以通信了。源码OKHttp里用pipe
取消connection ( 所以源码fd叫 fdsEmergency,紧急停止按钮):connection被 select()阻塞,select系统调用是 非阻塞IO调用(和Looper.loop的那个epoll一样),同时监听多个fd。(推测1个fd是某个io,io可读 connection继续往下走;1个fd 就是fdsEmergency[0] ,可读就是取消connection)第二个用来解决 race condition(线程不安全里的 竞争条件,这个我就不翻译了太长)
* (3) The pipe is used primarily as a means of cancelling a blocking select()
* when we want to close the connection (aka "emergency button"). It is also
* necessary for dealing with a possible race condition situation: There might
* be cases where both threads see an SSL_ERROR_WANT_READ or
* SSL_ERROR_WANT_WRITE. Both will enter a select() with the proper argument.
* If one leaves the select() successfully before the other enters it, the
* "success" event is already consumed and the second thread will be blocked,
* possibly forever (depending on network conditions).
*
* The idea for solving the problem looks like this: Whenever a thread is
* successful in moving around data on the network, and it knows there is
* another thread stuck in a select(), it will write a byte to the pipe, waking
* up the other thread. A thread that returned from select(), on the other hand,
* knows whether it's been woken up by the pipe. If so, it will consume the
* byte, and the original state of affairs has been restored.
*
* The pipe may seem like a bit of overhead, but it fits in nicely with the
* other file descriptors of the select(), so there's only one condition to wait
* for.
原因
直接崩溃Log E NativeCrypto: AppData::create pipe(2) failed: Too many open files,这个log不在崩溃的方法栈内,在崩溃前的日志输出(bugly可以看日志)崩溃前日志大概率显示[error:java.io.IOException: Cannot run program "logcat": error=24, Too many open files],因为fd耗光了,所以日志也无法导出了native fd (FileDescriptor) 资源被耗尽 什么是fd ,导致pipe 打开文件出问题OkHttp issue类似案例
这个error=24, Too many open files。create pipe(2) failed: Too many open files怎么来的呢?
C的错误不是抛出异常,而是通过ErrorCode来设置的。所以JNI很坑,有时native调用错了,方法照样继续跑。文件异常ErrorCode列表 #define EMFILE 24 /* Too many open files */
可能导致其他问题
因为fd (FileDescriptor)被耗尽,所以fd相关调用都会有问题。 比如 文件IO、IPC(binder的操作,binder视为特殊file) 1.AppData::create pipe(2) failed: Too many open files 2.javax.net.ssl.SSLException: Unable to create application data 3.RunTimeException-Could not read input channel file descriptors from parcel
待验证
崩溃统计 异常100%发生在 Android 8.1.0,level 27,推测是 Android 8.1.0的fd最大值 比较小
通过命令 ulimit -n可以查看 单进程fd最大数sysctl -a或cat /proc/sys/fs/file-max查看系统fd最大数
报错源码
报错代码地址app_data类为null
unique_ptr 不可拷贝、赋值,只可以new对象复制,release后释放 对象指针的控制权
native层报错
static AppData* create() {
//***
if (pipe(appData.get()->fdsEmergency) == -1) {
CONSCRYPT_LOG_ERROR("AppData::create pipe(2) failed: %s", strerror(errno));
return nullptr;
}
//**
static jlong NativeCrypto_SSL_new(JNIEnv* env, jclass, jlong ssl_ctx_address,
CONSCRYPT_UNUSED jobject holder) {
//****
AppData* appData = AppData::create();
if (appData == nullptr) {
conscrypt::jniutil::throwSSLExceptionStr(env, "Unable to create application data");
ERR_clear_error();
JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_new appData => 0", ssl_ctx);
return 0;
}
NativeCrypto.java Provides the Java side of our JNI glue for OpenSSL.
NativeCrypto.java
static native long SSL_new(long ssl_ctx, AbstractSessionContext holder) throws SSLException;
java.lang.RuntimeException: javax.net.ssl.SSLException: Unable to create application data
at com.android.org.conscrypt.ConscryptFileDescriptorSocket.newSsl(ConscryptFileDescriptorSocket.java:161)
at com.android.org.conscrypt.ConscryptFileDescriptorSocket.<init>(ConscryptFileDescriptorSocket.java:152)
at com.android.org.conscrypt.OpenSSLSocketFactoryImpl.createSocket(OpenSSLSocketFactoryImpl.java:149)
at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.java:357)
at okhttp3.internal.connection.RealConnection.establishProtocol(RealConnection.java:325)
at okhttp3.internal.connection.RealConnection.connect(RealConnection.java:197)
at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.java:249)
at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.java:108)
at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.java:76)
at okhttp3.internal.connection.RealCall.initExchange$okhttp(RealCall.java:245)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:32)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:100)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:96)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:100)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:83)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:100)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:76)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:100)
at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.java:197)
at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.java:502)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
Caused by: javax.net.ssl.SSLException: Unable to create application data
at com.android.org.conscrypt.NativeCrypto.SSL_new(NativeCrypto.java)
at com.android.org.conscrypt.SslWrapper.newInstance(SslWrapper.java:58)
at com.android.org.conscrypt.ConscryptFileDescriptorSocket.newSsl(ConscryptFileDescriptorSocket.java:159)
... 22 more
javax.net.ssl.SSLException: Unable to create application data
at com.android.org.conscrypt.NativeCrypto.SSL_new(NativeCrypto.java)
at com.android.org.conscrypt.SslWrapper.newInstance(SslWrapper.java:58)
at com.android.org.conscrypt.ConscryptFileDescriptorSocket.newSsl(ConscryptFileDescriptorSocket.java:159)
at com.android.org.conscrypt.ConscryptFileDescriptorSocket.<init>(ConscryptFileDescriptorSocket.java:152)
at com.android.org.conscrypt.OpenSSLSocketFactoryImpl.createSocket(OpenSSLSocketFactoryImpl.java:149)
at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.java:357)
at okhttp3.internal.connection.RealConnection.establishProtocol(RealConnection.java:325)
at okhttp3.internal.connection.RealConnection.connect(RealConnection.java:197)
at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.java:249)
at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.java:108)
at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.java:76)
at okhttp3.internal.connection.RealCall.initExchange$okhttp(RealCall.java:245)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:32)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:100)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:96)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:100)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:83)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:100)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:76)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:100)
at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.java:197)
at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.java:502)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
错误备注
Java层
OkHttpClient的sslSocketFactory, systemDefaultSslSocketFactory 默认的SSlSocketFactory根据 机器属性获取 类名,反射得出
String clsName = getSecurityProperty("ssl.SocketFactory.provider");
String s = java.security.Security.getProperty(name);
SSLSocketFactory fac = (SSLSocketFactory)cls.newInstance();