1

Okay, Ada tasking is quite new and confusing to me. I have a classic problem with a protected object that stores events by IDs as key. The idea is that a producer task fills it with incoming events and one or more consumer tasks need to wait until an event of a given id arrives, i.e., they should block until it is stored in the map, and then return that event.

Here is the structure so far:

 package Reply_Storage is new Ada.Containers.Indefinite_Ordered_Maps
 (Key_Type     => Command_Id_Type,
  Element_Type => Event_Type);

 protected type Reply_Queue is
      procedure Put (Event : Event_Type);
      entry Take (Id : Command_Id_Type; Event : out Event_Type);
   private
      Storage : Reply_Storage.Map;
 end Reply_Queue;

 protected body Reply_Queue is
      procedure Put (Event : Event_Type) is
         Id : Command_Id_Type := Event_Command_Id (Event);
      begin
         Storage.Insert (Id, Event);
      end Put;
      entry Take (Id : Command_Id_Type; Event : out Event_Type) 
       when not Storage.Is_Empty is
      begin
         if Storage.Contains(Id) then
           Event := Storage.Element (Id);
            Storage.Delete (Id);
         end if;
      end Take;
 end Reply_Queue;

Basically, instead of when not Storage.Is_Empty, I would need a barrier when Storage.Contains(Id) in the body of entry Take. This is not allowed, of course, because barriers are checked independently of the entry call.

But how to achieve the desired synchronization?

Jacob Sparre Andersen
  • 6,733
  • 17
  • 22
  • Sounds like you need an entry family ([RM 9.5.2(20)](http://www.ada-auth.org/standards/rm12_w_tc1/html/RM-9-5-2.html#I4077)), which is kind of like an array of entries (and I think at least GNAT implements them as such) – egilhh Aug 23 '16 at 17:28
  • IIRC barriers are evaluated after completion of procedures/entries, so your problem is not that they're evaluated independently, but that you can't use the argument Id on the barrier condition. – Álex Aug 23 '16 at 17:38
  • 1
    Hit enter too fast. I wanted to add that if you only have a consumer you could perhaps manage some workaround by requeuing. – Álex Aug 23 '16 at 17:40

1 Answers1

2

So, what you need is an entry family (only works for discrete types), like this:

package Reply_Storage is new Ada.Containers.Indefinite_Ordered_Maps
 (Key_Type     => Command_Id_Type,
  Element_Type => Event_Type);

 protected type Reply_Queue is
      procedure Put (Event : Event_Type);
      entry Take (Command_Id_Type) (Event : out Event_Type); -- entry family
   private
      Storage : Reply_Storage.Map;
 end Reply_Queue;

 protected body Reply_Queue is
      procedure Put (Event : Event_Type) is
         Id : Command_Id_Type := Event_Command_Id (Event);
      begin
         Storage.Insert (Id, Event);
      end Put;
      entry Take (for Id in Command_Id_Type) (Event : out Event_Type) -- entry family
       when Storage.Contains(Id) is -- family designator (entry index) in barrier
      begin
         Event := Storage.Element (Id);
         Storage.Delete (Id);
      end Take;
 end Reply_Queue;
Jacob Sparre Andersen
  • 6,733
  • 17
  • 22
egilhh
  • 6,464
  • 1
  • 18
  • 19
  • 2
    For a comparison between entry families and Ada 95's “more powerful” **requeue** (mentioned in c.l.ada), cf. [Rationale 95](http://www.adaic.org/resources/add_content/standards/95rat/rat95html/rat95-p2-9.html#2), explaining “preference control”. This part of the rationale is continuing [Rationale 83](http://archive.adaic.com/standards/83rat/html/ratl-13-02.html#13.2.11) on the subject. – B98 Aug 23 '16 at 19:59
  • Thanks a lot! This seems to work, but there is weird curiosity. *Command_Id_Type* was originally a simple subtype of Natural and with that it refused to compile because of a static constraint check failure. Defining it as *Command_Id_Type is new Integer range 0 .. Integer'Last-1;* works, but gcc spits out a number of strange messages: *In function `bham__connection_handlerTKB': bham.adb:(.text+0x1300a): relocation truncated to fit: R_X86_64_32 against `.bss'* and at many other places. What do they mean? – Eric '3ToedSloth' Aug 23 '16 at 21:15
  • 2
    Rather than exploring the implementation's way of handling an enormous amount of queues (perhaps a job for the implementer), I'd consider the number of actual Command IDs of the problem at hand, and define a type reflecting that. Using `Natural` or `Integer'Last - 1`, makes this (a) dependent on the implementation (~`2**15`, ~`2**31`, or ~`2**63`), and (b) looses the aforementioned preciseness. So, perhaps `range 1000 .. 1450` or some such? – B98 Aug 24 '16 at 07:02
  • That's good advice. Unfortunately in this case Command IDs are consecutive numbers for which I need a large space, not a small fixed set of numbers. In the current GNAT, a modular 2 ** 24 integer type works for entry families. It's probably enough for my application, but the implementation-dependency of this solution indeed bothers me and I will explore a requeue solution when I have the time. In any case, egilhh's reply prefectly answers my question, as I left it unclear how many command ids there actually are. – Eric '3ToedSloth' Aug 25 '16 at 11:09