Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Chasing TransactionTooLargeException in The Wild

Chasing TransactionTooLargeException in The Wild

In this talk, I am talking about TransactionTooLargeException. What is TransactionTooLargeException (TTLE)? Why the exception occurs? How to fix it? I am digging in the background of the exception a bit deeper.

The sample code is at https://github.com/hkurokawa/TransactionTooLargeSample

Reference:
- https://developer.android.com/reference/android/os/TransactionTooLargeException
- https://developer.android.com/about/versions/nougat/android-7.0-changes#other
- https://gihyo.jp/book/2017/978-4-7741-8861-4
- https://github.com/karino2/InsideBinder/blob/master/README.md

Hiroshi Kurokawa

March 08, 2019
Tweet

More Decks by Hiroshi Kurokawa

Other Decks in Technology

Transcript

  1. java.lang.RuntimeException: android.os.TransactionTooLargeException: data parcel size 592196 bytes at android.app.ActivityThread$StopInfo.run(ActivityThread.java:3752) at

    android.os.Handler.handleCallback(Handler.java:751) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6077) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755) Caused by: android.os.TransactionTooLargeException: data parcel size 592196 bytes at android.os.BinderProxy.transactNative(Native Method) at android.os.BinderProxy.transact(Binder.java:615) at android.app.ActivityManagerProxy.activityStopped(ActivityManagerNative.java:3606) at android.app.ActivityThread$StopInfo.run(ActivityThread.java:3744) at android.os.Handler.handleCallback(Handler.java:751) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6077) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755) What’s TransactionTooLarge- Exception?
  2. What’s TTLE? • targetSdkVersion ≧ 24 • Mostly happens at

    Activity#onSaveInstanceState • Happens when the size of outState to save > 1MB
  3. Behind TTLE java.lang.RuntimeException: android.os.TransactionTooLargeException: data parcel size 592196 bytes at

    android.app.ActivityThread$StopInfo.run(ActivityThread.java:3752) at android.os.Handler.handleCallback(Handler.java:751) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6077) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755) at android.os.BinderProxy.transact(Binder.java:615) at android.app.ActivityManagerProxy.activityStopped(ActivityManagerNative.java:3606) at android.app.ActivityThread$StopInfo.run(ActivityThread.java:3744) at android.os.Handler.handleCallback(Handler.java:751) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6077) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755) Caused by: android.os.TransactionTooLargeException: data parcel size 592196 bytes at android.os.BinderProxy.transactNative(Native Method)
  4. Behind onSaveInstanceState Activity Record Activity Activity Activity App Process App

    Process Activity Record Activity Record ActivityManagerService SystemServer Process IPC = Binder
  5. The Binder transaction buffer has a limited fixed size, currently

    1Mb, which is shared by all transactions in progress for the process. Consequently this exception can be thrown when there are many transactions in progress even when most of the individual transactions are of moderate size. https://developer.android.com/reference/android/os/TransactionTooLargeException Binder buffer size limit
  6. How to avoid the exception? • No need to persist

    data across Activity/process life cycle?
 → Use LiveData instead of onSaveInstanceState • Much data to save and do not want to lose them?
 → Use local file or DB instead if possible ¯\_(π)_/¯
  7. How to investigate the exception? • Investigating the cause of

    TransactionTooLargeException is not easy • Stacktrace does not give a hint • Hard to reproduce - kind of an exception in wild
  8. Hint 2: TooLargeTool * MyFragment.onSaveInstanceState wrote: Bundle@161404279 contains 3 keys

    and measures 0.3 KB when serialized as a Parcel * androidx.lifecycle.BundlableSavedStateRegistry.key = 0.1 KB * android:user_visible_hint = 0.1 KB * android:view_state = 0.1 KB * fragment arguments = Bundle@246765028 contains 1 keys and measures 0.0 KB when serialized as a Parcel * num = 0.0 KB
  9. Hint 3: FragmentStatePagerAdapter • FragmentStatePagerAdapter saves the state of all

    the pages into a Bundle • Total size of Bundle
 = (Size of bundle on each page) * (the number of pages) • What if each page takes ~2KB and the ViewPager holds 500 pages?
  10. Where does 1Mb
 come from? The Binder transaction buffer has

    a limited fixed size, currently 1Mb, which is shared by all transactions in progress for the process. Consequently this exception can be thrown when there are many transactions in progress even when most of the individual transactions are of moderate size. https://developer.android.com/reference/android/os/TransactionTooLargeException
  11. Binder.cpp https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-8.1.0_r62/ core/jni/android_util_Binder.cpp#1182 case FAILED_TRANSACTION: { ALOGE("!!! FAILED BINDER TRANSACTION

    !!! (parcel size = %d)", parcelSize); const char* exceptionToThrow; char msg[128]; // TransactionTooLargeException is a checked exception, only throw from certain methods. // FIXME: Transaction too large is the most common reason for FAILED_TRANSACTION // but it is not the only one. The Binder driver can return BR_FAILED_REPLY // for other reasons also, such as if the transaction is malformed or // refers to an FD that has been closed. We should change the driver // to enable us to distinguish these cases in the future. if (canThrowRemoteException && parcelSize > 200*1024) { // bona fide large payload exceptionToThrow = "android/os/TransactionTooLargeException"; snprintf(msg, sizeof(msg)-1, "data parcel size %d bytes", parcelSize);
  12. IPCThreadState.cpp status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult) { int32_t cmd; int32_t

    err; while (1) { if ((err=talkWithDriver()) < NO_ERROR) break; err = mIn.errorCheck(); if (err < NO_ERROR) break; if (mIn.dataAvail() == 0) continue; cmd = mIn.readInt32(); switch (cmd) { case … case BR_FAILED_REPLY: err = FAILED_TRANSACTION; goto finish; https://android.googlesource.com/platform/frameworks/native/+/37b4496/libs/binder/ IPCThreadState.cpp#696
  13. binder.c t->buffer = binder_alloc_new_buf(&target_proc->alloc, tr->data_size, tr->offsets_size, extra_buffers_size, !reply && (t->flags

    & TF_ONE_WAY)); if (IS_ERR(t->buffer)) { /* * -ESRCH indicates VMA cleared. The target is dying. */ return_error_param = PTR_ERR(t->buffer); return_error = return_error_param == -ESRCH ? BR_DEAD_REPLY : BR_FAILED_REPLY; return_error_line = __LINE__; t->buffer = NULL; goto err_binder_alloc_buf_failed; } https://github.com/torvalds/linux/blob/master/drivers/android/binder.c#L3137
  14. • It seems Binder driver has a limit to the

    transaction buffer • Binder driver allocates some amount of memory and send/ receive a message to/from a process • Is there any kernel parameter?