An Implementation of the Priority Inheritance Protocol for Real-time Java

David Holmes

In "Priority Inheritance Protocols and their Implementation" we discussed the behaviour of different priority inheritance protocols and their basic implementations under a strict set of execution restrictions. We also discussed how these restrictions (no blocking, no programmatic changing of priority etc) were not met in a real-time Java environment, and that consequently implementation of some of those protocols -- notably the Basic Priority Inheritance Protocol (BPIP) -- was greatly complicated compared to the simple schemes outlined in the literature. In this document we discuss an implementation approach for the BPIP within a real-time Java environment.

Basic Concepts and Definitions

The base priority of a thread is the priority assigned to the thread by the application program. In terms of the Realtime Specification for Java, this is the priority defined in the PriorityParameters object bound to the realtime thread and which we assume can be changed at any time.

The active priority of a thread is its current execution priority as seen by the scheduler. This is the priority value used to establish execution eligibility and to order the thread on any system queues that are ordered by priority (such as monitor lock acquisition queues, and monitor wait-set queues).

The owner of a monitor is the thread that currently holds the monitor lock.

The lock-set of a monitor is the set of all threads blocked trying to acquire that monitor (ordered by active priority).

The top of the lock-set is the thread with the highest active priority.

The lock-set.top thread bequests its priority to the monitor owner. If the bequested priority is greater than the owners active priority then the owner inherits the bequested priority as its active priority. The lock-set.top thread is known as a priority source for the monitor owner.

The owned-set is the set of monitors owned by a thread.

The inheritance-queue of a thread is the ordered set of priority sources for that thread. The top of the inheritance-queue is the thread with the highest active priority.

Properties and Invariants

Invariant: for all threads t

Invariant: for all threads t

Invariant: for all threads t, for all monitors m in t.ownedSet:

Invariant: a thread can exist in only one lock-set at a time, and so only in one inheritance queue.

Invariant: for all monitors m:

Invariant: for all threads t:

Property: when a thread t is started:

Property: when a thread t terminates:

Basic Operation

There are four actions that affect the operation of the priority inheritance protocol:
  1. A thread blocks trying to acquire a monitor lock (either directly through entry to a synchronized method or statement, or indirectly when returning from a call to Object.wait()) and so may become a priority source for the owning thread
  2. A thread moves to the top of the lock-set for a monitor (because the previous top thread has either acquired the monitor or abandoned its attempt) and so becomes a priority source for the current owner
  3. A thread releases a monitor lock (and so loses the priority source from that monitor)
  4. A thread has its priority changed. Depending on the state of the thread this might cause it to become a priority source, or cease to be a priority source; or simply require a change to the active priority of the thread for which it is a priority source.

Iin all cases correct operation simply involves maintaining the invariants that were previously listed, for all threads. We define two helper functions to express the basic actions that must occur in each case: maintainPriority and propagatePriority.

MaintainPriority: Causes a thread to check that it’s active priority invariant is met, and if not to change its active priority so that the invariant is met. An implementation can optimise things by checking for actual changes in active priority.

PropagatePriority: For a thread t this has the following effect:

      if t is blocked acquiring a monitor m then 
           m.lockSet.reposition(t); // ensure the lock set is correctly ordered 
           if m.lockSet.top != old m.lockSet.top then
               m.owner.inheritanceQueue.remove(old m.lockSet.top);
               m.owner.inheritanceQueue.insert(m.lockSet.top); 
               m.owner.maintainPriority();
               m.owner.propagatePriority(); 
           else if t == m.lockSet.top then
               m.owner.inheritanceQueue.reposition(t); 
               m.owner.maintainPriority();
               m.owner.propogatePriority(); 
           else 
               nop 
       else if t is runnable/running
           reorder ready queue
       else
           nop 
  

Monitor Acquisition

If a thread t tries to acquire a monitor m and that monitor already has an owner other than t, then t is placed in the lock-set of m and the following occurs:
     if (t == m.lockSet.top) then
         m.owner.inheritanceQueue.remove(old m.lockSet.top); 
         m.owner.inheritanceQueue.insert(t);
         m.owner.maintainPriority(); 
         m.owner.propagatePriority(); 
     else 
         nop
When t eventually acquires the monitor then the following happens:
      t.inheritanceQueue.insert(m.lockSet.top);
      t.maintainPriority(); 
      t.propagatePriority(); 
  

Monitor Release

When a thread t releases a monitor m, such that t is no longer the owner of m, then the following occurs:

      t.inheritanceQueue.remove(m.lockSet.top);
      t.maintainPriority(); 
      t.propagatePriority();

Priority Change

If a thread t has its priority changed to a value p then the following occurs:

     t.basePriority = p;
     t.maintainPriority(); 
     t.propagatePriority(); 

Implementation in OVM

The OVM implementation follows the basic operation previously described. It is provided by three classes in the s3.services.realtime package (for raw ED functionality): and three corresponding classes in the s3.services.java.realtime package: In each case, the thread class maintains the inheritance queue and determines its active priority, the dispatcher deals with priority changes (to the base priority) and provides the means to propagate any priority changes, and the monitor ensures monitor acquisition and release do the right thing (in each case by overriding hook methods from their superclass monitor implementation). The main operation in the dispatcher is called maintainPriorityRelations and combines the action of maintainPriority (by asking the thread) and propagatePriority.