Unable to create application data OkHttp崩溃bug

tech2024-01-28  73

解决办法

降低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();
最新回复(0)