Zygote
Hands-on labsnerd

Lab 13 — Binder and lock contention

A thread that is blocked is not a thread that is working. Find synchronous Binder round trips in a trace, see the server side of the call, and identify lock contention from the thread state track.

6 minAPI 1–37 · 17verified 2026-10-27

A Binder call is a function call that crosses a process boundary. Most of the time it is fast enough to be invisible. When it is not, it shows up as a thread that is blocked, and the trace can tell you which side was slow.

TL;DR#

  • A synchronous Binder call blocks the caller until the server replies. The trace shows it as a slice on the caller's thread whose duration is the whole round trip.
  • The server side is visible too, on a thread in the server process. Both halves can be measured separately.
  • oneway calls do not wait, so they do not block the caller and do not appear as a long slice.
  • Lock contention shows as sleeping or blocked time without a Binder slice. The blocked_function field on the thread state names the lock if the kernel knows it.
  • The most expensive pattern is a main-thread binder call to a service that is itself waiting on something else.

Mental model#

Think of a phone call to a support line.

The caller dials and waits on hold. That wait is the caller's blocked time.

The agent answers and works on the request. That work is the server's slice.

If the agent has to check with a supervisor (a second Binder call), the caller waits for the whole chain, and the trace shows a nested wait inside the agent's work.

Where the analogy breaks#

A phone call has one caller and one agent. Binder calls can be concurrent, the server has a pool of threads, and the same caller thread may be handling an incoming callback while waiting for a reply. The trace shows all of it, which is why it is more informative than a stack trace.

Deep dive#

The shape of a synchronous call#

The caller's sequence is: write a parcel, call transact, block, receive a reply, return. The blocking part is where the time goes, and it is why a synchronous Binder call on the main thread is a jank risk: the main thread is asleep, not working.

In a trace, with the binder_driver category enabled, the call appears as a slice named for the transaction. The duration is the round trip, including the scheduling latency of the server thread.

Finding the server#

The server side lives in the process that registered the service. For framework services that is usually system_server; for media and graphics it is a native daemon.

The method:

  1. Find the caller's Binder slice on the app's main thread.
  2. Note the time window.
  3. Find the system_server thread that was running in the same window.
  4. Look for a slice whose name matches the interface method.

The server thread is usually from the Binder thread pool, named Binder:<pid>_<n> or similar. The exact naming depends on the platform version.

Oneway and asynchronous calls#

A oneway call returns as soon as the transaction is queued. The caller does not block, so there is no long slice; the server processes it later, on a pool thread.

That changes the shape of the trace and the diagnosis. A oneway call cannot cause jank on the caller, but it can flood the server, and a flood shows up as a busy Binder pool and a queue of transactions. If the app is slow and there is no blocking slice, look for a oneway flood.

Lock contention#

A thread waiting on a lock is not in a Binder slice and not running. Its thread state is sleeping or blocked, and on Linux the kernel may record the function it is blocked in — the blocked_function field on thread_state.1

Three patterns:

  • A monitor held for too long. One thread holds a lock while doing I/O or calling a slow method; others queue behind it.
  • Lock ordering. Two threads acquire the same two locks in opposite orders and deadlock, or nearly deadlock and stall.
  • False sharing / contention on a hot lock. A short critical section that is entered so often that the lock itself becomes the bottleneck.

The trace shows the waiting threads as blocked; the holder is the thread that is running inside the critical section. Finding the holder is the whole game.

Walkthrough#

  1. Record a trace with binder and sched

    Use the lab 10 config, adding binder_driver and sched/sched_switch.

    You should seeA trace with binder slices and scheduling data.
  2. Find the app's main thread

    Search for the package name in the UI and expand the process.

    You should seeA thread track with slices.
  3. Look for Binder slices

    Filter the timeline by the binder keyword.

    You should seeSlices named for transactions, often with the interface in the name.
  4. Find the matching server slice

    Select the caller slice, note the time, and inspect the system_server threads in that window.

    You should seeA slice in system_server at the same time.
  5. Check the thread state

    Look at the thread state track under the main thread.

    You should seeSleeping or blocked intervals without a Binder slice for lock waits.
Trace with Binder
adb shell perfetto --help
cat binder.cfg | adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/binder.pftrace
adb pull /data/misc/perfetto-traces/binder.pftrace
Example output
Illustrative — requires a config file and a device.

Challenges#

Challenge 1 — Measure the IPC overhead#

For one synchronous call, measure the caller's slice and the server's slice and compute the difference.

Reveal the answer

Select the caller slice and note dur. Find the server slice in the same window and note its dur. The difference is the round-trip overhead: waking the server thread, copying the parcel, and delivering the reply. A large difference with a small server slice means the overhead or scheduling dominated; a small difference means the method itself was the cost.

Challenge 2 — Find a oneway flood#

Identify a service that is receiving many oneway calls, and explain why the caller does not block.

Reveal the answer

Look for a server process with many short Binder slices and a caller with no corresponding long slices. Oneway calls return after the transaction is queued, so the caller's slice is short or absent. The server's queue is the thing to watch: if the pool is saturated, the calls pile up and the service falls behind without any single call being slow.

Challenge 3 — Find the lock holder#

Find a thread that is blocked on a lock, then find the thread holding it.

Reveal the answer

The blocked thread shows a sleeping or blocked state, possibly with blocked_function naming the lock. The holder is the thread that is running in the same window inside the critical section. If several threads block at the same time and one is running, that one is the holder. If no thread is running, the holder may be blocked on something else, which is the start of a chain.

What you learned#

You can tell a blocked thread from a busy one, measure both sides of a Binder call, and find a lock holder. That covers most of the "why is the main thread asleep?" questions that traces are used for.

Next#

Lab 14 — SQL on traces turns these visual questions into queries.

Sources#

Sources

Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.

  1. docsPerfetto documentationperfetto.devaccessed 2026-10-27↩
  2. docsBinder overviewsource.android.comaccessed 2026-10-27↩
  3. docsOverview of system tracingdeveloper.android.comaccessed 2026-10-27↩
  4. docsAndroid tracing with Perfettoperfetto.devaccessed 2026-10-27↩
  5. docsBinder threadingsource.android.comaccessed 2026-10-27↩
  6. docsAndroid Interface Definition Language (AIDL) | Background workdeveloper.android.comaccessed 2026-10-27↩