| Thread Priorities in Android |
| ---------------------------- |
| |
| [TODO(b/389104950) Some of the following is currently more aspirational than fact. Implementation |
| is in progress. ] |
| |
| Unfortunately, this topic relies on several different kinds of thread "priority", which we will |
| try to distinguish as follows: |
| |
| 1. The priority argument passed to Posix `setpriority()`, which affects Linux threads using |
| `SCHED_OTHER` scheduling. These range in value from -20 to 19, with larger values correspond to |
| *lower* scheduling priority. We will refer to these as "niceness". A difference of 1 in |
| niceness corresponds to something like a factor of 1.25 in processor share when cores are |
| overcommitted. Details may change as the Linux scheduler changes. |
| |
| 2. Java thread priorities. In practice, these range from 1 to 10, with higher values corresponding |
| to higher scheduling priority. We refer to these as "java-priorities". |
| |
| 3. The value of the `sched_priority` field, for example in the `sched_param` struct passed to |
| `sched_setparam()`. This affects only `SCHED_FIFO` and `SCHED_RR` scheduling policies. The |
| latter is not normally used in Android. The former has some limited uses as described below. |
| These threads always have higher scheduling priority than normal `SCHED_OTHER` threads, and |
| they can effectively preclude even essential system functions from running. Larger values |
| correspond to higher scheduling priority. We will refer to these as "rt-priorities". |
| |
| ART initially assumed that Java threads always used `SCHED_OTHER` Linux thread scheduling. Under |
| this discipline all threads get a chance to run, even when cores are overcommitted. However the |
| share of cpu time allocated to a particular thread varies greatly, by up to a factor of thousands, |
| depending on its "niceness", as set by `setpriority()` (or the swiss-army-knife `sched_setattr()`). |
| |
| In general java-priorities are mapped, via the platform supplied `libartpalette` to corresponding |
| niceness values for `SCHED_OTHER` threads, These niceness values are then installed with |
| `setpriority()`. |
| |
| The above assumptions are still generally true, but there are two important exceptions: |
| |
| 1. ART occasionally assigns niceness values for internal daemon threads that are obtained by |
| interpolating between java-priorities. These niceness values do not map perfectly back onto |
| java-priorities. Thread.getPriority() rounds them to the nearest java-priority. |
| |
| 2. Frameworks application management code may temporarily promote certain threads to `SCHED_FIFO` |
| scheduling with an associated rt-priority. As mentioned above, such threads cannot be preempted |
| by `SCHED_OTHER` threads. There are currently no clear rules about which threads may run under |
| `SCHED_FIFO` scheduling. In order to prevent deadlocks, all ART, libcore, and frameworks code |
| should be written so that it can safely run under `SCHED_FIFO`. This means that wait loops that |
| either spin consuming CPU time, or attempt to release it by calling `sched_yield()` or |
| `Thread.yield()` are *incorrect*. At a minimum they should eventually sleep for short periods. |
| Preferably, they should call an OS waiting primitive. Mutex acquisition, or Java's |
| `Object.wait()` do so internally. |
| |
| How to set priorities |
| --------------------- |
| |
| Java code is arguably allowed to expect that `Thread.getPriority()` returns the last value passed |
| to `Thread.setPriority()`, at least unless the application itself calls Android-specific code or |
| native code documented to alter thread priorities. This argues that `Thread.getPriority()` should |
| cache the current priority. Historically, this has also been done for performance reasons. We |
| continue to do so, though we now cache niceness rather than java-priority. |
| |
| In order to keep this cache accurate, applications should try to set priorities via |
| `Thread.setPriority(int)`. `android.os.Process.setThreadPriority(int)` may be used to set any |
| niceness value, and interacts correctly with the `Thread.getPriority()`. (This treatment |
| of `android.os.Process.setThreadPriority(int)` is currently still future work.) |
| |
| All other mechanisms, including `android.os.Process.setThreadPriority(int, int)` (with a thread id |
| parameter) bypass this cache. This has two effects: |
| |
| 1. The change of priority is invisible to `Thread.getPriority()`. |
| |
| 2. If the effect is to set a positive (> 0, i.e. low priority) niceness value, that effect may be |
| temporary, since ART, or the Java appliction may undo the effect. |
| |
| Avoiding races in setting priorities |
| ------------------------------------ |
| |
| Priorities may be set either from within the process itself, or by external frameworks components. |
| If multiple threads or external components try to set the priority of a thread at the same time, |
| this is prone to races. For example: |
| |
| Consider a thread *A* that temporarily wants to raise its own priority from *x* to *y* and then |
| restore it to the initial priority *x*. If another thread happens to also raise its priority to |
| *y* while it is already temporarily raised to *y*, resulting in no immediate change, *A* will have |
| no way to tell that it should not lower the priority back to *x*. The update by the second thread |
| can thus be lost. |
| |
| In order to minimize such issues, all components should avoid racing priority updates and play by |
| the following rules: |
| |
| 1. The application is entitled to adjust its own thread priorities. Everything else should |
| interfere with that as little as possible. The application should adjust priorities using |
| either `Thread.setPriority(int)` or `android.os.Process.setThreadPriority(int)`. JNI code that |
| updates the thread priority only for the duration of the call, may be able to adjust its |
| own priorities using OS calls, using something like the ART protocol outlined below, |
| so long as the JNI code does not call back into Java code sensitive to thread priorities. |
| The application should generally restore its priority to an expected value, such as a saved |
| value previously read via `Thread.getPriority()`, so that the value it restores is unaffected by |
| by priority adjustment from another process. |
| |
| 2. ART may temporarily change the priority of an application thread with positive niceness to a |
| lower non-negative niceness value (usually zero), and then restore it it to the cached value as |
| detailed below. This happens only from within that thread itself. This is necessary, since |
| threads with large positive niceness may become esentially unresponsive under heavy load, and |
| may thus block essential runtime operations. |
| |
| 3. External process priority updates, usually by another process, should not use mechanisms that |
| affect the cached value, specifically `android.os.Process.setThreadPriority(int)` or |
| `Thread.setPriority(int)`, so that such temporary updates do not result in the application |
| restoring the wrong value after a temprary priority adjustment. This is normally automatic, |
| since a cross-process niceness change doesn't affect the cached value. |
| |
| 4. Priority updates using means other than `android.os.Process.setThreadPriority(int)` or |
| `Thread.setPriority(int)` should set a positive niceness value only when it is OK for ART (or |
| the application) to overwrite that with the value cached by Java at any point. ART will not |
| overwrite negative niceness values, unless the negative value is installed exactly between ART's |
| priority check and priority update. (There is no atomic priority update in Linux.) Hence, |
| ideally: |
| |
| 5. In order to minimize interference with applications directly using OS primitive to |
| adjust priorities, external processes should avoid updating priorities of threads |
| executing managed code, or that may therwise be concurrently adjusting its own priorities. |
| If they do, ART can at any moment overwrite their updates and restore its cached value. |
| That is good, because it is not always clear how to enforce such timing constraints. |
| |
| ART may use the following to temporarily increase priority by lowering niceness to zero: |
| |
| ``` |
| current_niceness = getpriority(<me>); |
| if (current_niceness <= 0 || current_niceness != <cached niceness>) |
| <do not change niceness>; |
| else |
| setpriority(<me>, 0); |
| <do high priority stuff that does not alter priority / niceness> |
| if (<we changed niceness> && getpriority(<me>) == 0) { |
| // Either there were no external intervening priority/niceness changes, or an |
| // external process restored our 0 niceness in the interim. Thread.setPriority(0) |
| // may have been called by another thread in the interim. In any of those cases, |
| // it is safe to restore priority with: |
| setpriority(<me>, <niceness cached by Java>); |
| // setpriority(<me>, current_niceness) would be incorrect with an intervening |
| // Thread.setPriority(0). |
| } |
| ``` |
| |
| The above requires sufficient concurrency control to ensure that no `Thread.setPriority()` |
| calls overlap with the priority restoration at the end. |
| |
| Java priority to niceness mapping |
| --------------------------------- |
| |
| The priority mapping is mostly determined by libartpalette, which is a platform component |
| outside ART. Traditionally that provided `PaletteGetSchedPriority` and `PaletteSetSchedPriority`, |
| which returned and took Java priority arguments, thus attempting to entirely hide the mapping. |
| This turned out to be impractical, because some ART components, e.g. ART's system daemons, |
| were tuned to use niceness values that did not correspond to Java priorities, and thus used |
| Posix/Linux `setpriority` calls directly. This scheme also prevents us from easily caching |
| priorities set by `android.os.Process.setThreadPriority(int)`, which traffics in Linux niceness |
| values. |
| |
| Thus libartpalette now also provides `PaletteMapPriority()`, which is used in connection |
| with Linux `setpriority(), and is preferred when available. |
| |
| Kernel-supported priority inheritance |
| ------------------------------------ |
| |
| ART often suffers from priority inversions: A high priority thread *A* waits for a lower |
| priority thread *B*. This may happen because *B* holds a lock that *A* needs, because *A* is |
| waiting for a condition variable that *B* will eventually signal, or because *A* is waiting |
| for *B* to pass a barrier, etc. The reason ART needs to explicitly adjust priorities is |
| mostly to work around this kind of situation. |
| |
| In the case of locks, there is a well-established technique for addressing this, which is |
| partially supported by the Linux kernel: If *A* blocks on a lock held by *B*, we can temporarily |
| raise *B*'s priority to *A*'s until the lock is released. The Linux kernel provides |
| `FUTEX_LOCK_PI` and pthreads provides `PTHREAD_PRIO_INHERIT` to support this. The implementation |
| avoids the race issues we discussed above, along with some others. It is thus quite attractive. |
| |
| We plan to make use of this facility in the future. There are ongoing discussions about expanding |
| it to cover some non-lock cases, which are equally important to us. However, there are currently |
| some obstacles in the way of such use: |
| |
| 1. The kernel supports priority inheritance for non-real-time-threads only in 6.12 and later. Pure |
| real-time priority inheritance does not help us much, since it is, for good reason, rarely used. |
| |
| 2. User-level support currently only exists for `pthread_mutex`, and not for various kinds of locks |
| used internally by ART, nor those by Java synchronized blocks, nor the Java-implemented locks |
| used by java.util.concurrent. For the former two, this would require at least a major redesign, |
| possibly with some negative performance consequences. For the latter, it's currently impossible |
| because the implementation builds directly on `park()` / `unpark()`, which do not expose the |
| thread being waited for to the kernel. |
| |
| 3. Even for `pthread_mutex_lock()` we would have to be consistent about using |
| `PTHREAD_PRIO_INHERIT`everywhere. This may have negative performance consequences, and the Posix |
| standard appears to prohibit defaulting to it. |
| |
| Thus, at the moment, we basically do not use kernel-supported priority inheritance. But stay |
| tuned. |
| |
| Historical Notes |
| ---------------- |
| |
| In the past (and still as of April 2025) `android.os.Process.setThreadPriority(int)` was not |
| reflected in the `Thread.getpriority()` cache. This is viewed as a misfeature to be corrected. |
| Unfortunately it can only be corrected for the version that sets the current thread's priority. |
| |
| Before Android T (a.k.a. 13), `PaletteSetSchedPriority()`, invoked by `Thread.setPriority()`, but |
| not by `android.os.Process.setThreadPriority(int)`, was only partially safe for use in frameworks |
| code that might be called from the zygote process. It appears that under some circumstances, |
| `SetTaskProfiles()` would be invoked and leave a file descriptor open, which is unacceptable for |
| zygote code. As of April 2025, this still matters for changes to the ART module, and requires an |
| unpleasant workaround. Even more recently, `android.os.Process.setThreadPriority(int)` and |
| `Thread.setPriority()` differed in their effect on Linux cgroups. Now neither has an effect |
| beyond setting niceness. |
| |
| On Android S, we assume a default Java priority to niceness mapping, and insure that |
| Thread.setPriority() calls continue to result in `PaletteSetSchedPriority()` calls, generally |
| preserving the platform behavior. (In unusual cases, the cached niceness value may not reflect |
| what `PaletteSetSchedPriority()` actually sets, and ART attempts to temporarily raise priority may |
| have no impact due to the mismatch.) On later API versions, we just call setpriority with the |
| correct niceness value. |
| |