Recursive Semaphores


In this article we show how to implement a "recursive" semaphore that
can be seized multiple times by its owner without deadlock.

Suppose we have a guarded stack with an equality operator:

  generic
     with function "=" (L, R : Item_Type) return Boolean is <>;
  package Stacks.Guarded_G is

     type Stack_Type is
       new Stacks.Stack_Type with record
          Semaphore : aliased Semaphore_Type;
       end record;

     function "=" (L, R : Stack_Type) return Boolean;

     ...

  end Stacks.Guarded_G;


In order to implement the equality operator, we need to seize the left
and right operands, and then compare the items in each stack.  Our
first, naive implementation would look something like this:

   function "=" (L, R : Stack_Type) return Boolean is

      L_Control : Semaphore_Control (<left operand>);

      R_Control : Semaphore_Control (<right operand>);

   begin

      if L.Top /= R.Top then
         return False;
      end if;

      for I in Integer range 1 .. L.Top loop
         if L.Items (I) /= R.Items (I) then
            return False;
         end if;
      end loop;

      return True;

   end "=";


To compare two guarded stacks, you just call the equality operator in
the normal way:

   declare
      S1, S2 : Guarded.Stack_Type;
   begin
     ...

     if S1 = S2 then ...;

   end;

The equality function first seizes S1 (the left operand), then seizes S2
(the right operand), and then compares the items.

But there is a problem.  Suppose we had two separate threads, both
comparing the same pair of stacks:

  package Global is

    S1, S2 : Guarded.Stack_Type;

  end Global;


  task body T1 is
  begin

     if S1 = S2 then ...;

  end T1;


  task body T2 is
  begin

    if S2 = S1 then ...;

  end T2;


Here is a possible sequence of actions:

T1: seize left (S1)
T2: seize left (S2)
T1: seize right (S2)
T2: seize right (S1)

The problem is that we're in a state of circular deadlock:

o T1 blocks waiting to seize S2, which has already been seized by T2.

o T2 blocks waiting to seize S1, which has already been seized by T1.


To prevent this form of deadlock, we introduce another semaphore for
binary operations (really any cardinality greater than one), that must
be seized first, prior to seizing any individual stacks:


  package Stacks.Guarded_G is

     ...

     Mutex : aliased Semaphore_Type;

  end Stacks.Guarded_G;



The implementation of our modified equality operator now looks like
this:

    function "=" (L, R : Stack_Type) return Boolean is

       M_Control : Semaphore_Control (Mutex'Access);

       L_Control : Semaphore_Control (<left operand>);

       R_Control : Semaphore_Control (<right operand>);

    begin

       ...

    end "=";


This solves the circular deadlock that can result when different threads
compete for the same set of resources, because all threads have to pass
through the mutex first.

But there is still a problem.  Suppose a client non-chalantly invokes
the equality function using the same argument for both operands:

  if S1 = S1 then ...;

Gulp!  Here's what happens:

  seize left (S1)
  seize right (S1)

and of course we promptly deadlock.

We could handle this as a special case by testing to see if the operands
designate the same object:

  function "=" (L, R : Stack_Type) return Boolean is
  begin

    if L'Address = R'Address then
       return True;
    end if;

    <as before>

  end "=";


Using 'Address to determine whether the operands are the same object is
legitimate per RM95 13.3 (16), because our stack is a by-reference type.

However, we shall seek another solution, because we would prefer to not
have to handle special cases, and because using low-level facilities
(like 'Address) to solve a high-level problem lacks aesthetic appeal.

Let's implement the guarded stack using a "recursive" semaphore that
allows the current owner to seize the same stack again without deadlock.

The first thing we need is a way to record which task is the current
owner of the semaphore, and a way to compare callers to the current
owner.  The package Ada.Task_Identification in the Systems Programming
Annex (C.7.1) provides all the facilities we need.

That package provides a non-limited Task_Id type to identify a task
(sort of like a Memento).  There's also an attribute, E'Caller, that
returns the id of the task being serviced by an entry.

The declaration of the semaphore looks like this:

   protected type Semaphore_Type is

      procedure Release;

      entry Seize;

   private

      entry Waiting;

      Owner  : Task_Id;
      Count  : Natural := 0;

   end Semaphore_Type;


The implementation of the Seize entry looks like this:

      entry Seize when True is
      begin
         if Seize'Caller = Owner then
            Count := Count + 1;
         else
            requeue Waiting with abort;
         end if;
      end;


Seize checks to see if its caller already owns the semaphore.  If so, we
just increment the count of the number of times Seize has been called.
The caller/owner is allowed to pass immediately, without being blocked.

Note that we have been careful to use the attribute Seize'Caller to get
the id of the task being serviced.  This is different from the function
Current_Task, and it is a bounded error to use the latter function from
inside an entry body, per RM95 C.7.1 (17).

We allow Seize to be called any number of times by the same thread, but
we also require that Release be called the same number of times.  When
all the seizes have been canceled by releases, this means a new thread
can assume ownership of the semaphore.

If the caller is different from the owner, we have to somehow keep the
caller waiting until the semaphore becomes available.  We do this by
using requeue to put the caller on a separate wait queue, which gets
serviced when the current owner has relinquished ownership.

Seize itself doesn't ever block its callers, so its barrier is True.  It
has to be a protected entry because you can only requeue a caller from
an entry, not a procedure.  Note that we requeue with abort to allow a
caller to leave the queue early.

The protected procedure Release just decrements the count:

      procedure Release is
      begin
         Count := Count - 1;
      end;

After Release completes, the entry barriers are immediately reevaluated.
As long as the Count is greater than 0, then nothing else happens.  It
just means the current owner still owns the semaphore, and other threads
waiting for the resource continue to wait.

When the Count drops to 0, this signifies that the current owner has
relinquished control.  This opens the barrier for the Waiting entry:

      entry Waiting when Count = 0 is
      begin
         Count := 1;
         Owner := Waiting'Caller;
      end;

In effect a waiting thread is allowed a "pass through" the barrier, to
become the new owner of the semaphore.  We record the id of the thread,
and set Count to 1 to indicate that the resource has been claimed.

There's one last issue to deal with.  As in previous articles, I like to
use a semaphore control object to make sure the semaphore is always
released.

A semaphore control object binds to its semaphore via an access
discriminant, which requires a variable view.  The problem is that our
equality function takes in-mode parameters, which only provide a
constant view:

   function "=" (L, R : Stack_Type) return Boolean is


How do we get a variable view of the stack, so that we can declare a
semaphore control object that binds to the stack's semaphore?  The
solution is to use our friend System.Address_To_Access_Conversions.

By instantiating that package on the stack type, we get operations to
convert a stack address to an access-to-variable access object.  As we
explained earlier, taking the address of the stack operand produces a
useful value because the stack is a by-reference type.

Here's the complete declarative region, showing the address to access
conversions too:

   function "=" (L, R : Stack_Type) return Boolean is

      M_Control : Semaphore_Control (Mutex'Access);


      LA : constant Object_Pointer := To_Pointer (L'Address);

      L_Control : Semaphore_Control (LA.Semaphore'Access);


      RA : constant Object_Pointer := To_Pointer (R'Address);

      R_Control : Semaphore_Control (RA.Semaphore'Access);

   begin

      <compare L and R>

   end "=";


There are two test programs:

o test_semaphores:

The environment task seizes a recursive semaphore multiple times, and
then releases it.

o test_integer_stacks

Compares a pair stacks to themselves and to each other.


--STX
package body Binary_Semaphores.Controls is

   procedure Initialize (Control : in out Semaphore_Control) is
   begin
      Control.Semaphore.Seize;
   end;

   procedure Finalize (Control : in out Semaphore_Control) is
   begin
      Control.Semaphore.Release;
   end;

end Binary_Semaphores.Controls;
with Ada.Finalization;

package Binary_Semaphores.Controls is

   pragma Elaborate_Body;

   type Semaphore_Control (Semaphore : access Semaphore_Type) is
      limited private;

private

   use Ada.Finalization;

   type Semaphore_Control (Semaphore : access Semaphore_Type) is
     new Limited_Controlled with null record;

   procedure Initialize (Control : in out Semaphore_Control);

   procedure Finalize (Control : in out Semaphore_Control);

end Binary_Semaphores.Controls;
package body Binary_Semaphores is

   protected body Semaphore_Type is

      procedure Release is
      begin
         Count := Count - 1;
      end;

      entry Seize when True is
      begin
         if Seize'Caller = Owner then
            Count := Count + 1;
         else
            requeue Waiting with abort;
         end if;
      end;

      entry Waiting when Count = 0 is
      begin
         Count := 1;
         Owner := Waiting'Caller;
      end;

   end Semaphore_Type;

end Binary_Semaphores;
with Ada.Task_Identification;  use Ada.Task_Identification;

package Binary_Semaphores is

   pragma Elaborate_Body;


   protected type Semaphore_Type is

      procedure Release;

      entry Seize;

   private

      entry Waiting;

      Owner  : Task_Id;
      Count  : Natural := 0;

   end Semaphore_Type;

end Binary_Semaphores;
with Stacks.Guarded_G;

package Integer_Stacks.Guarded is
  new Integer_Stacks.Guarded_G;
with Stacks;

package Integer_Stacks is
  new Stacks (Integer, Max_Depth => 10);

with Binary_Semaphores.Controls;
with System.Address_To_Access_Conversions;

package body Stacks.Guarded_G is

   package Address_To_Access_Conversions is
     new System.Address_To_Access_Conversions (Stack_Type);

   use Address_To_Access_Conversions;
   use Controls;


   function "=" (L, R : Stack_Type) return Boolean is

      M_Control : Semaphore_Control (Mutex'Access);


      LA : constant Object_Pointer := To_Pointer (L'Address);

      L_Control : Semaphore_Control (LA.Semaphore'Access);


      RA : constant Object_Pointer := To_Pointer (R'Address);

      R_Control : Semaphore_Control (RA.Semaphore'Access);

   begin

      if L.Top /= R.Top then
         return False;
      end if;

      for I in Integer range 1 .. L.Top loop
         if L.Items (I) /= R.Items (I) then
            return False;
         end if;
      end loop;

      return True;

   end "=";

end Stacks.Guarded_G;
with Binary_Semaphores;  use Binary_Semaphores;

generic
   with function "=" (L, R : Item_Type) return Boolean is <>;
package Stacks.Guarded_G is

   type Stack_Type is
     new Stacks.Stack_Type with record
        Semaphore : aliased Semaphore_Type;
     end record;

   function "=" (L, R : Stack_Type) return Boolean;

   Mutex : aliased Semaphore_Type;

end Stacks.Guarded_G;




package body Stacks is


   procedure Push
     (Item : in     Item_Type;
      On   : in out Stack_Type) is
   begin
      On.Top := On.Top + 1;
      On.Items (On.Top) := Item;
   end;


   procedure Pop
     (Stack : in out Stack_Type) is
   begin
      Stack.Top := Stack.Top - 1;
   end;


   function Get_Top
     (Stack : Stack_Type) return Item_Type is
   begin
      return Stack.Items (Stack.Top);
   end;


   procedure Set_Top
     (Stack : in out Stack_Type;
      Item  : in     Item_Type) is
   begin
      Stack.Items (Stack.Top) := Item;
   end;


   function Is_Empty (Stack : Stack_Type) return Boolean is
   begin
      return Stack.Top = 0;
   end;


   procedure For_Every_Item (Stack : in out Stack_Type'Class) is

      Done : Boolean := False;

   begin

      for I in reverse 1 .. Stack.Top loop

         Process (Stack.Items (I), Done);

         exit when Done;

      end loop;

   end For_Every_Item;


end Stacks;





generic

   type Item_Type is private;

   Max_Depth : in Positive;

package Stacks is

   pragma Preelaborate;

   type Stack_Type is tagged limited private;


   procedure Push
     (Item : in     Item_Type;
      On   : in out Stack_Type);

   procedure Pop
     (Stack : in out Stack_Type);

   function Get_Top
     (Stack : Stack_Type) return Item_Type;

   procedure Set_Top
     (Stack : in out Stack_Type;
      Item  : in     Item_Type);

   function Is_Empty (Stack : Stack_Type) return Boolean;


   generic

      with procedure Process
        (Item : in out Item_Type;
         Done : in out Boolean);

   procedure For_Every_Item (Stack : in out Stack_Type'Class);

private

   subtype Item_Array_Range is Positive range 1 .. Max_Depth;

   type Item_Array is array (Item_Array_Range) of Item_Type;

   type Stack_Type is
      tagged limited record
         Items : Item_Array;
         Top   : Natural := 0;
      end record;

end Stacks;
with Ada.Text_IO;             use Ada.Text_IO;
with Integer_Stacks.Guarded;

procedure Test_Integer_Stacks is

   S1, S2 : Integer_Stacks.Guarded.Stack_Type;

   use Integer_Stacks;
   use Guarded;

begin

   Put_Line (Boolean'Image (S1 = S1));
   Put_Line (Boolean'Image (S2 = S2));
   Put_Line (Boolean'Image (S1 = S2));
   New_Line;

   Push (1, On => S1);
   Put_Line (Boolean'Image (S1 = S1));
   Put_Line (Boolean'Image (S2 = S2));
   Put_Line (Boolean'Image (S1 = S2));
   New_Line;

   Push (1, On => S2);
   Put_Line (Boolean'Image (S1 = S1));
   Put_Line (Boolean'Image (S2 = S2));
   Put_Line (Boolean'Image (S1 = S2));
   New_Line;

   Pop (S1);
   Put_Line (Boolean'Image (S1 = S1));
   Put_Line (Boolean'Image (S2 = S2));
   Put_Line (Boolean'Image (S1 = S2));
   New_Line;

   Pop (S2);
   Put_Line (Boolean'Image (S1 = S1));
   Put_Line (Boolean'Image (S2 = S2));
   Put_Line (Boolean'Image (S1 = S2));
   New_Line;

end Test_Integer_Stacks;



with Binary_Semaphores;  use Binary_Semaphores;
with Ada.Text_IO;        use Ada.Text_IO;

procedure Test_Semaphores is

   S : Semaphore_Type;

   task Another_Task is
      entry Seize_Semaphore;
   end;

   task body Another_Task is
   begin
      accept Seize_Semaphore;

      Put_Line ("Another task is seizing semaphore");
      S.Seize;

      Put_Line ("Another task is done waiting.");
   end;

begin

   Put_Line ("env task is seizing semaphore");
   S.Seize;

   Another_Task.Seize_Semaphore;
   delay 1.0;

   for I in Integer range 2 .. 10 loop
      Put_Line ("Env task seize" & Integer'Image (I));
      S.Seize;
   end loop;

   Put_Line ("Env task has seized semaphore 10 times; releasing.");

   for I in Integer range 1 .. 10 loop
      Put_Line ("Env task release" & Integer'Image (I));
      S.Release;
   end loop;

end Test_Semaphores;

Contributed by: Matthew Heaney
Contributed on: May 24, 1999
License: Public Domain

Back