While maybe not the heart of darkness, ARM 6.1.2 seems to be in the penumbra. ARM 6.1.2(31) seems to be saying that Global
doesn’t apply to library packages or generic library packages. So why is it present for Ordered_Maps
? (I guess I just haven’t grasped some vital subtlety).
As explained in 6.1.2(13/5), when you apply Global or Nonblocking to a library unit, you are establishing the default for subprograms, types, etc. enclosed within it.
-Tuck
— 6.1.2(13/5) —
If not specified or otherwise defined below, the aspect defaults to the Global aspect for the enclosing library unit if the entity is declared at library level, and to Unspecified otherwise. If not specified for a library unit, the aspect defaults to Global => null for a library unit that is declared Pure, and to Global => Unspecified otherwise.
Ok, I am too clumsy now to test myself (and finding your code hard to read at my level), but please confirm this: When a task goes knocking at the entry with the True guard, then reaches requeue, can it use a selective entry call on it ?
A guard condition really depending on parameters would count as blocked until said guard is lifted, with this scheme, the call is taken immediately so the delay will not be considered regardless of how long the task really waits. Is it right ?
“Hard to read” is mutual. I do not understand the question. When a task calls to an entry by whatever means it gets blocked until this entry or entry it is requeued to executed. This may include several entries requeuing the task further and further. With abort means that when a task sits in the queue of an entry specified in the requeue statement it can be removed from there, e.g. when a timed entry call timeout expires.
Again, I do not understand this statement. A guard is an expression evaluated each time the state of the protected object is changed.
Ditto. When an entry is called and the guard is true, the entry is entered. When the guard is false the task is put into the queue of the entry.
Ok, I’m just embarrassing myself. Concurrency is the worst topic so far for me, by a lot. I’ll make it simple (for me and you), just see this:
task body B is
begin
select
A.E1;
or
delay 1.0;
Do_Something;
end select;
end B;
Say that unbeknownst to B the entry E1 in A requeues the call immediately in less than a second to another public entry E2. So even if B is again in a queue, the select statement won’t see it at all and Do_Something will never be executed. Is this right ? The queuing to E2 becomes invisible to task B.
edit: corrected.
You cannot call yourself. But maybe you meant rather this:
protected X is
entry A;
entry B;
end X;
protected body X is
entry A when True is
begin
requeue B with abort;
end A;
entry B when False is
begin
null;
end B;
end X;
Then, because of with about this one:
select
X.A;
or delay 1.0;
end select;
will be aborted (regardless which entry) and the delay alternative triggered.
Same with a task:
task Y is
entry A;
entry B;
end Y;
task body Y is
begin
loop
select
accept A do
requeue B with abort;
end A;
or when False => accept B do
Put_Line ("Never");
end B;
or terminate;
end select;
end loop;
end Y;
Yes, but the task is known to the protected object. There are many ways a timed call can be implemented but any of them would know which queue the task sits in.
Oh. Fantastic, I got the last two examples, it does what I hope it would do. “with abort” informs the select statement to continue counting until the task is accepted in B. Perfect. I see why it’s considered a most powerful Ada feature. I just couldn’t make sense of the texts before.
With abort tells that the ongoing operation can be aborted. The operation can be performed by a sequence of entries requeuing the caller task changing the state of involved protected objects and/or tasks.
For example let the entry A change the internal state violating the invariant of the protected object or task. The invariant is then restored by a consecutive entry B. If you abort in between you will corrupt the object(s)/task(s). Then you omit with abort. You can allow aborting later on if the operation consists of further interruptible parts.
Here is an example, useless because Put_Line is actually blocking, nevertheless:
task IO is
entry Start (Text : String);
private
entry Wait (Text : String);
end IO;
task body IO is
Failed : Boolean := False;
Fault : Exception_Occurrence;
Buffer : Unbounded_String;
begin
loop
select
accept Start (Text : String) do
Buffer := To_Unbounded_String (Text);
requeue Wait; -- No abort!
end Start;
begin
Put_Line (To_String (Buffer));
Failed := False;
exception
when Error : others =>
Save_Occurrence (Fault, Error);
Failed := True;
end;
or terminate;
end select;
begin
accept Wait (Text : String) do
if Failed then
Reraise_Occurrence (Fault); -- Popagates here, too
end if;
end Wait;
exception
when others => -- Ignore exception we have raised
null;
end;
end loop;
end IO;
Once Start is accepted there is no abort. The caller will wait until the I/O completes this way or another. Of course, in practice there ways to cancel pending I/O but for simplicity this is a case for no abort.