Why requeue only takes "names"

Hi,

I noticed requeue isn’t nearly as powerful as I hoped, since unlike normal entry calls it only accepts names, not random expressions designating an object with the entry.
I wanted this:

	entry In_Operation (Result: out Tupple'Class) when True is
	begin
		requeue with abort Matches(Result).In_Operation;
	end In_Operation;

Which would have provided dynamic re-routing. Now I don’t know what to do, but I’m sure it will get ugly. What is the reason of this limitation ?

IIRC, it’s because of the runtime imposition that a dynamic operation would impose. Being static was the least expensive operation, handling most needs.

So, to explain, every TASK has a queue for every entry, these entries are serviced with the accept — requeue is resetting back into the accept’s queue. (Again, IIRC.)

Though I don’t know if you really need a dynamic requeue, as it seems simple enough to ‘bounce’ the data.

Generic
   Type Element(<>) is private;
Package Bouncer is
  Task Instance is 
     Entry Input ( Object : in     Element );
     Entry Output( Object :    out Element );
  end task;
End Bouncer;

Generic
Function Bouncer.Retrieve return Element;
Function Bouncer.Retrieve return Element is
Begin
   Return Result : Element do
      Bouncer.Instance.Outpur( Result );
   End return;
End Bouncer.Retrieve;

You could also make the TASK you need to requeue from a formal parameter of the Retrieve subprogram and use the subprogram renaming to specify where to put the requeued data, instead of using the function’s return as a vehicle.

Any call requires a name. But the name can be a component, element of an array, dereference etc see ARM 4.1. For example:

   task type T is
      entry X;
   end T;
   function Route return access T;

Then

   task body T is
   begin
      loop
         select
            accept X do
               requeue Route.X with abort; -- Dynamically determined entry
            end X;
         or terminate;
         end select;
      end loop;
   end T;

Here is the Route:

   type Index is mod 10;
   package Seeds is new Ada.Numerics.Discrete_Random (Index);
   State : Seeds.Generator;

   A : array (Index) of aliased T;

   function Route return access T is
   begin
      return A (Seeds.Random (State))'Access;
   end Route;

Huh ? I don’t quite understand why your example works and not mine.

protected body RealtuppleSpace is
	function Matches (T: Tupple'Class) return LockedQueue is
	begin
		if not The_Map.Contains(T'Tag) then
			The_Map.Insert (T'Tag);
		end if;
		return The_Map.Element(T'Tag);
	end Matches;
	entry In_Operation (Result: out Tupple'Class) when True is
	begin
		requeue with abort Matches(Result).In_Operation;
	end In_Operation;
end RealtuppleSpace;

type AccessToClass is access Tupple'Class;
package newQueues is new Ada.Containers.Vectors (Positive, AccessToClass);
subtype Queue is newQueues.Vector;
protected type LockedQueue is new TuppleSpace with
	overriding
	procedure Out_Operation
	  (T : Tupple'Class);
	overriding
	entry In_Operation
	  (Pattern : Tupple'Class;
	   Result  : out Tupple'Class);
	overriding
	entry RD_Operation
	  (Pattern : Tupple'Class;
	   Result  : out Tupple'Class);
	overriding
	procedure Inp_Operation
	  (Pattern : Tupple'Class;
	   Result  : out Tupple'Class);
	overriding
	procedure RDP_Operation
	  (Pattern : Tupple'Class;
	   Result  : out Tupple'Class);
private
	PatternQueue: Queue;
end LockedQueue;
type AccessToProtected is access LockedQueue;
function Hash (A: Ada.Tags.Tag) return Ada.Containers.Hash_Type is (Ada.Strings.Hash (Ada.Tags.External_Tag(A)));
package newMaps is new Ada.Containers.Hashed_Maps (Key_Type => Ada.Tags.Tag, Element_type => AccessToProtected, Hash => Hash, Equivalent_Keys => "=");
type MyMapType is new newMaps.Map with null record;

Instead of an array, I have a map indexing patterns (represented by an access to a protected object, since they are limited) by the pattern’s tag. LockedQueue’s job is to find the match or create an entry if the pattern is not present, then requeue the call to the pattern’s entries.
I gave LockedQueue the same interface for the fun but don’t have to, it suffices for a requeue target to have the same profile or no parameters.

MyMapType.Element returns a defined type, like in your case. Why should my function call from a container be semantically different from your array component ? I can’t use an array because I don’t know how many patterns there would be. I wanted it to be general, but I guess givingn it fifty slots should be enough…

Beware, a Pickwickean function here! LockedQueue is a limited type. You declared it as

Semantically you cannot return a protected object. And you did not mean it. You meant a reference to an existing object. That is an access to.

P.S. As I said before, refrain from using suspicious language features. By-reference result /= in-place constructor. Ada does not have the former and mistakenly sugars the latter as a “function,” which is NOT. You mean one but get another.

Can someone explain what the dreaded accessibility checks come to do here ?
I finished my Linda space and it works like a charm. Except when you try to define a new tuple type in the program’s declarative part, which is to say, not in library level.

So I have something like:

procedure Essai is
  Space: RealTuppleSpace;
  type TestType is new tuppleSpacePackage.Tupple with record
	  A: String (1..10) := (others => ' ');
	  B: Integer := -10;
  end record;
  overriding procedure Copy_Into (A: Tupple'Class; B: out TestType);
begin
    Space.Out_Operation (Encode(“je ne sai”, -48));
end Essai;

You can see the details in this repo, but I checked and the value and variables are correct up to the point of inserting into the vector:

procedure Out_Operation (T: Tupple'Class) is
begin
	Put_line(T'Image);
	PatternQueue.Prepend (new Tupple'Class'(T));
end Out_Operation;

Then I get raised PROGRAM_ERROR : tupplespacepackage-realtupples.adb:87 accessibility check failed

The allocator should create an entire new value, copying T. Why would accessibility matter when I know it sees the variable up to that point !
…Naturally the correct approach is to forgo the whole conversion view and class-wide access types and instead store values stream elements then use the 'Read attribute. It came to me too late. Also implementing non-destructive reading with streams seems well above my skills, I can’t read this at all.


tupplespacepackage-realtupples.adb ,tupplespacepackage-realtupples.ads, essai.adb , tupplespacepackage.ads

I redid it with Ada.streams.Storage.Unbounded. It works just the same, but more elegantly. But I have an issue left with Finalization. Since 1) Vectors only take non-limited types 2) stream operations all need access types, I came up with this:

type FinalizedType is new Controlled with record
	Stream: AccessStream := new Stream_Type;
end record;
procedure Finalize (Finale: in out FinalizedType);
package newQueues is new Ada.Containers.Vectors
(Positive, FinalizedType);
subtype Queue is newQueues.Vector;

When elements are destroyed, Finalize should be called, in which I free Stream.
Then the basic operations go as follow:

procedure Out_Operation (T : Tupple'Class) is
  begin
     PatternQueue.Set_Length (PatternQueue.Length + 1);
     Tupple'Class'Output(PatternQueue.Last_Element.Stream, T);
  end Out_Operation;

  entry In_Operation (Result : out Tupple'Class) when PatternQueue.Length > 0 is
  begin
     Result := Tupple'Class'Input(PatternQueue.First_Element.Stream);
     PatternQueue.Delete_First;
  end In_Operation;

  entry RD_Operation (Result : out Tupple'Class) when PatternQueue.Length > 0 is
  begin
     Result := Tupple'Class'Input(PatternQueue.First_Element.Stream);
     Tupple'Class'Output(PatternQueue.First_Element.Stream, Result);
  end RD_Operation;

Works if Finalize is null, or I get raised PROGRAM_ERROR : finalize/adjust raised exception. Clearly I don’t know how finalization works yet. This was my first foray with streams and finalized types.

Links

essai.adb : essai.adb - Pastebin.com
tupplespacepackage-realtupples.ads: tupplespacepackage-realtupples.ads - Pastebin.com
tupplespacepackage.ads : tupplespacepackage.ads - Pastebin.com
tupplespacepackage-realtupples.adb: tupplespacepackage-realtupples.adb - Pastebin.com

  • You do not need dynamically allocated streams. You can take an access to a stream object if aliased.
  • Accessibility checks are about avoiding dangling pointers. Check the access type used in the queue. Avoid anonymous access types.
  • Do not use vectors or any dynamically allocated containers inside protected actions.
  • Do not use streams inside protected actions.

This is a plain error. You cannot change the constraint (discriminants, bounds, tag) of an object. When you pass Result : out Tupple'Class the actual parameter determines the tag. So Class’Input attribute is not applicable. Entries may not return values, where that could work. But you should not use streams in there anyway (unless in a task entry).

If you want interlocked streams, use mutex:

function Get (S : access Queue) return T'Class is
   Lock : Holder (S.Mutex'Access);
begin
   return T'Class'Input (S);
end Get;

A mutex can be implemented using a protected object.

I store an access to stream because streams are limited and vectors won’t hold them. What’s wrong with streams inside protected actions, or with dynamically allocated containers inside protected actions ? What do mutexes have to do here ? I already use a protected object inside a protected object.

And placing streams into a container does not make sense to me.

Protected action is logically instant. It shall never loop. When using a pool-backed container or even an Unbounded_String you must access a pool which normally involves some locking, that is a potentially blocking operation. Consider an Ada run-time implementation that uses one simple spin lock for everything. The lock will be turned on entering protected action. From there accessing the pool will try to take it again … and you are in a deadlock.

I do not understand the problem in first place. Why are you using streams? Streams are for serialization, I/O, marshalling. Why do you need marshal elements. It is basically wasting resources unless you need to decouple ends, that is kill object and restore it later.

Mutex is two protected actions:

   protected type Mutex is
      procedure Release;
      entry Seize;

You call Seize and then you are no more in a protected action and have much more freedom in what you do and how you do it. Release is another protected action when you are done. Of course, mutexes are much slower and exposed to nasty problems, but that is the price.

Make queue a stream. You write into the queue, you read from there, Any interlocking is inside the implementation. Yes, it is not nice as you will have to use a timeout parameter instead of timed entry call. But otherwise it is a cleaner and safer design.

Remember. Protected types are not composable. All that “inheritance” and “interface” stuff with them is putting lipstik on a pig.

What is the simplest way to get a non-destructive read (for one element) with a stream ?

I am not sure what you mean by that. Unget? In general streams are meant not to allow this. Consider a terminal input stream. You cannot look ahead or return.

If you wanted an equivalent of UNIX unget on steroids, you would need a buffer. Make your stream on top of another stream:

   type Buffered (Size : Storage_Element_Count; Stream : access Root_Stream'Class) is
      new Root_Stream with
   record
      Old    : Stream_Element_Offset := 0; -- Saved First
      First  : Stream_Element_Offset := 0;
      Last   : Stream_Element_Offset := -1;
      Buffer : Stream_Element_Array (1..Size); -- Ring buffer
   end record;

Then you implement Read as reading into Buffer and returning a copy of. You will have immense problems implementing logic correctly because Read/Write are chunked. Have fun! But better, reconsider your design.

Streams are a generalized IO; so they would work for, say, a radio-transmitter.
How, then, do you unget a radio-transmission? (You cannot.)

So then what you do is buffer; for an old parser, I needed a one-character lookahead, so how I implemented it was as a single-character buffer that would as-needed pull from a given stream — IIRC, I gave it ‘peek’ & ‘read’ as operations, and had to have two bits of state: one for if the current character was valid (i.e. if I had pulled anything from the stream yet) and one for if a read had caused an End_Error exception from the stream (in which case you might have good-data that you ‘unget’ [refill the buffer]).

Getting back to your original question:

I think you messed up the order here, try

requeue Matches(Result).In_Operation with abort ;

instead.

Basically, the compiler expects a name, but you provided a keyword…