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.
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.
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.
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]).