Android - Could not read input channel file descriptors from parcel

     This is one crazy runtime failure that doesn't really give any straight forward hint to application developers. The stack trace is usually related to android's internal classes with the top of the frame failing in InputChannel.

java.lang.RuntimeException: Could not read input channel file descriptors from parcel.
       at android.view.InputChannel.nativeReadFromParcel(InputChannel.java)
       at android.view.InputChannel.readFromParcel(InputChannel.java:148)
       at android.view.InputChannel$1.createFromParcel(InputChannel.java:39)
       at android.view.InputChannel$1.createFromParcel(InputChannel.java:36)
       at com.android.internal.view.InputBindResult.<init>(InputBindResult.java:62)
       at com.android.internal.view.InputBindResult$1.createFromParcel(InputBindResult.java:102)
       at com.android.internal.view.InputBindResult$1.createFromParcel(InputBindResult.java:99)
       at com.android.internal.view.IInputMethodManager$Stub$Proxy.windowGainedFocus(IInputMethodManager.java:851)
       at android.view.inputmethod.InputMethodManager.startInputInner(InputMethodManager.java:1292)
       at android.view.inputmethod.InputMethodManager.onWindowFocus(InputMethodManager.java:1518)
       at android.view.ViewRootImpl$ViewRootHandler.handleMessage(ViewRootImpl.java:3550)
       at android.os.Handler.dispatchMessage(Handler.java:102)
       at android.os.Looper.loop(Looper.java:157)
       at android.app.ActivityThread.main(ActivityThread.java:5293)
       at java.lang.reflect.Method.invokeNative(Method.java)
       at java.lang.reflect.Method.invoke(Method.java:515)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1265)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1081)
       at dalvik.system.NativeStart.main(NativeStart.java)


 A look at the InputChannel's code reveals the scenario in which the exception is thrown.

            int dupFd = dup(rawFd);
            if (dupFd < 0) {
                ALOGE("Error %d dup channel fd %d.", errno, rawFd);
                jniThrowRuntimeException(env,
                        "Could not read input channel file descriptors from parcel.");
                return;
            }

    The dup() system call has failed and bailed out with a negative file descriptor. The error code is being logged as well. One of the cases where the dup() system call fails is when the number of open file descriptors exceeds a certain limit. The application process is probably leaking file descriptors including sockets and pipes due to some bug. In most applications, this kind of a leak adds on with time and it's too late when the maximum limit is reached. The exception by itself is not a problem instead is just an effect of an earlier cause.

    This can be demonstrated with Android SDK's public API to duplicate files. The brute force logic to duplicate file descriptor in a loop causes a similar failure. The initial loop ends up opening 970 file descriptors. 970 is just based on trial and error for the application running in Nexus 4 with Lollipop. At the end of the first loop, the process had around 1010 open file descriptors, 14 short of the maximum limit. The next loop tries to create dialogs. The dialog creation logic involves a bunch of file operations including the one in InputChannel and as expected the process crashes with a "Too many open files" error.

                mCount = 0;

                while( true )
                {
                    try {
                        Os.dup( FileDescriptor.out );
                        mCount++;
                        if( mCount >= 970 )
                        {
                            break;
                        }
                    } catch (ErrnoException e) {
                        throw new RuntimeException( "Exception [" + e.getMessage() + "]" );
                    }
                }

                mCount = 0;

                while( true )
                {

                    mainHander.post(new Runnable() {
                        @Override
                        public void run() {
                              AlertDialog.Builder builder = new AlertDialog.Builder( this );
                              builder.setMessage("Dialog, hoping to be created");
                              AlertDialog mDialog = builder.create();
                              mDialog.show();
                        }
                    });

                    try {
                        Thread.sleep( 500 );
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

No comments: