this pins a core to 100%
+1 outside the caveat that sometimes the safest thing you can do is crash immediately rather than let your software do something unpredictable.
+1
+1, using Ada Interfaces.C you could call the underlying APIs directly. I find it more ergonomic to do the boilerplate in C and call a single function.
I’ll have to learn about how they work.
+1, these remarks were just stressing that you need to manually keep track of all the resources you can potentially block on, which I see as error-prone. Maybe they can be wrapped in some controlled type (? which I haven’t yet learned about) or handled with the aforementioned generic
s
I’m not 100% certain what you mean by this. Would this be a task with entry points for adjusting/retrieving various bookkeeping? (and also potentially maintaining a list of active tasks to cancel/abort)
I like this pattern a lot. I’ll have to experiment with it.
+1, I’ve been trying to focus on solutions within the ada language abstraction.
I also don’t mean to imply that these problems aren’t present in C/C++, just that I hadn’t run into them because C/C++ will crash immediately on an assert/uncaught exception.
Try changing
if Got_It then
Put_Line ("Input=" & C);
end if;
with
if Got_It then
Put_Line ("Input=" & C);
else
delay 0.01; -- adjust this to a value that makes your cpu usage acceptable
end if;
input IO is buffered and since the average human response time is 0.250 (really fast folks around 0.100) you have some room here. This should quickly get all buffered values and then if none are available, take a short break.
True; but it’s easier to get this behavior out of a “frangible” system: have your task
have an outermost exception-handler that triggers your globally-visible shutdown procedure.
That’s because (a) the delay 0.0;
is a “yield if needed”, and (b) no other tasks are present to yield to (aside from the master), which means that you’re effectively running a tight-polling. — You’d probably want something like 0.2
or 3#0.1#
(1/5th and 1/3rd of a second, respectively).
There is a way to use a protected-object —or its entry/procedure— to attach to an interrupt. See: Annex C, Section 3 and AdaCore’s Handling Interrupts article.
For something like a keyboard (or controller), I wouldn’t recommend going all the way down to interrupt-handling unless you need to: you should be able to get good performance w/o dropping down to that low of a level. — I’d also recommend using an (synchronized) interface to program against for player-input, so that the rest of your program doesn’t-know and doesn’t care about how commands are coming, only what commands are coming.
Iterate over all your potentially blocking descriptors (sockets etc.) and tell them all to cancel.
Side note, in some cases you can let tasks cancel themselves. If the tasks are library level, you can update your loop around your select statement to have a very specific condition:
while Is_Callable(Environment_Task) loop
select
-- stuff here
end select;
end loop;
Both Is_Callable
and Environment_Task
are found in Ada.Task_Identification
. The environment task is the task with your main, so basically once the main completes, tasks can see that and stop executing. I don’t know if that helps you in your current use case, but something to keep in your back pocket. This is more for the polling style solutions, but just something to look into if you go that route in the end.
That’s because (a) the
delay 0.0;
is a “yield if needed”
And where is a problem?
(b) no other tasks are present to yield to (aside from the master), which means that you’re effectively running a tight-polling
Same question.
Yes, you can of course change the delay to 0.1 etc. But normally you would not need that. The reason to use a delay at all is that the time quant is not zero. It depends on the OS, e.g. under Windows it is 10ms which can be reduced to 1ms. If a task does not enter a wait or initiates blocking I/O (“non-blocking” Get_Immediate may actually do just that) it consumes all the quant. delay 0.0 causes a re-scheduling and if another task is waiting for the processor it will get it. With multiple cores there is even less need for that, obviously.
There is a way to use a protected-object —or its entry/procedure— to attach to an interrupt.
Right, but that is for a bare bone case when there is no driver and no OS.
P.S. Long ago, when computers were really slow, the OS offered asynchronous system traps (software interrupts) on keypresses. I remember RSX-11M had such a thing. These days it is simply not needed and Get_Immediate is sufficient.
These days it is simply not needed and Get_Immediate is sufficient.
Which is why I advised against going w/ a interrupt-handler.
And where is a problem?
It might not be, but the response indicated some surprise that the construct would devolve into a polling mechanism (pegged CPU core). — Obviously having more/different task
constructs is going to impact how things are scheduled… but he should also be made aware of the why of it.
Also, it could be considered “bad form” to consume the entire CPU/core, so a more coarse delta would probably yield more acceptable CPU-time… though @jere does bring up the point about input being buffered: if it is, it might be necessary to do a read/flush pair for correct behaivior.
It might not be, but the response indicated some surprise that the construct would devolve into a polling mechanism
Right, but in order to avoid polling there must be OS support. One of
- A handle to an OS event specified in the blocking I/O call to cancel I/O.
- An explicit cancel I/O API call.
- A close file call effectively cancelling I/O
It is true that Linux has very rudimentary OS API, but even under Linux I doubt that the option #3 does not work in terminal emulator.
The
-- Blocking call to get Item
part needs to be something well defined in Ada. If your blocking call is a system level OS call that blocks it may not abort.
Right, this is what I meant by “If this works”. If the asynchronouse transfer of control (ATC) can abort the blocking call, then it works; otherwise, it doesn’t.
A hybrid approach, when the blocking call is to an entry, is used in PragmARC.Job_Pools, where the logic is
loop
select
Job_Queue.Get (Item => Info);
Process (Info => Info);
or
delay Quit_Check_Interval;
exit when Time_To_Quit;
end select;
end loop;
One should clearly understand how a timed entry call works. When you call an entry the task enters the entry’s queue and gets blocked. Upon expiration of the timeout the task is removed from the queue and the delay alternative is selected.
However when the entry call gets accepted and pending, then it is too late to abort. Execution of the rendezvous (entry of a task) or protected action (entry of a protected object) is not aborted!
Note that the language considers doing blocking stuff from a protected action an error. But it is fully legal in a rendezvous. This is a major difference between protected object and task entries. So a rendezvous will not be aborted and can block as long as it wishes.
Furthermore an entry call can be requeued to another entry. So the task will continue waiting in some other queue. When requeuing is done without with abort qualifier then this waiting cannot be aborted too. The reason is that if you started some stateful action in the first entry you might not be able to roll it back, so you must enter another entry no matter what.
In short, timed entry call is meant for other things.