Hello. Does anybody know the GNAT behavior during exception raise or catch with respect to errno global variable? I assume that it might call some C function that might set it and trash previous C function errno state?
Exceptions donāt do anything with errno. Why would you expect them to? Exceptions and errno are entirely unrelated concepts (from different languages no less).
GNAT might inject some C library call that could change it.
It does use C because it uses GCCās exception machinery, however I canāt imagine any possible implementation that would make use of errno. At most youād hit it as a side effect if thereās a malloc somewhere in there that fails, though Iād expect a crash at that point anyway. If youād like to trace through how it works then you probably want to start from System.Exceptions.Machine.
Since exceptions and error-nos are separate mechanisms, I would be surprised if there were any non-explicit interactions.
Ah, well, thereās several simple ways to architect around this sort of thing, consider, for example:
-- Specification:
-- This calls C function IMPORTED, and CHECK_ERROR, and SET_ERROR.
Function Example( Object : Some_Record ) return Other_Record;
-- Implementation:
Function Example( Object : Some_Record ) return Other_Record is
WHATEVER_ERROR : Constant Interfaces.C.Int := 3;
NO_ERROR : Constant Interfaces.C.Int := 0;
Procedure SET_ERROR( Value : Interfaces.C.Int )
with Import, Convention => C, Link_Name => "SET_ERROR";
Function CHECK_ERROR return Interfaces.C.Int
with Import, Convention => C, Link_Name => "CHECK_ERROR";
Procedure IMPORTED( Source : in Some_Record; Target : in out Other_Record )
with Import, Convention => C, Link_Name => "IMPORTED";
Begin
-- Call DO_CALL here, if using the "task-trick".
Return Result : Aliased Other_Record do
HANDLE_ERROR:
declare
Err : Interfaces.C.Int renames CHECK_ERROR;
begin
if Err = WHATEVER_ERROR then
SET_ERROR( NO_ERROR );
-- Call FINISH_CALL here, if using the Task-trick.
Raise Appropriate_Exception;
end if;
end HANDLE_ERROR;
End return;
End Example;
Task Task_Trick is
Entry DO_CALL;
Entry FINISH_CALL;
Entry DONE;
End Task_Trick;
-- Implementation:
Task Task_Trick is
Finished : Boolean := False;
Begin
loop
When Finished Exit;
Select
Accept Done; -- Set the Finished flag,
Finished:= True;
OR
Accept DO_CALL; -- Lock calls,
Accept FINISH_CALL; -- Free to accept another call.
End Select;
End loop;
End Task_Trick;
FWIW, āgnat.OS_Libā contains subprograms relating to āerrnoā ā¦
function Errno return Integer;
pragma Import (C, Errno, "__get_errno");
-- Return the task-safe last error number
function Errno_Message
(Err : Integer := Errno;
Default : String := "") return String;
-- Return a message describing the given Errno value. If none is provided
-- by the system, return Default if not empty, else return a generic
-- message indicating the numeric errno value.
procedure Set_Errno (Errno : Integer);
pragma Import (C, Set_Errno, "__set_errno");
-- Set the task-safe error number
I believe the Florist POSIX binding also handles āerrnoā.
Yes, Iām using GNAT.OS_Lib to capture errno
Perhaps you can say how errno is related to exception propagation?
If you propagate an exception from bindings you must store errno or GetLastError as soon as possible before making any calls to any subprogram, e.g. before rendering the error code as text etc.
Iām using a ZeroMQ binding that raises a single exception for any kind of C zeromq API call that returns error. I want to unwrap to proper Ada errors to raise and handle at the right place.
I could change the binding library to add the additional exceptions but I opted instead to catch that generic ZMQ_Error, read the errno and raise the proper exception on my own code instead. Then of course I had that doubt that something in the Ada exception machinery could set errno to something else.
Maybe errno is likely lost at this point already. Does the library set errno instead of returning the code explicitly? If so it is an awful idea exactly because errno is extremely volatile, not meant for higher level protocols, not meant for tasking, non-portable even across UNIX clones and so on.
Why do not you just implement it in Ada? OK, some protocols are messed up beyond imaginable like AMQP.
(I considered implementing ZeroMQ at some point, but decided not to. I do not remember details).
Or, a better question, why do you use it at all? Is it so hard to serialize and send data over a socket?
Maybe errno is likely lost at this point already. Does the library set errno instead of returning the code explicitly? If so it is an awful idea exactly because errno is extremely volatile, not meant for higher level protocols, not meant for tasking, non-portable even across UNIX clones and so on.
The binding does not set errno, the C ZeroMQ library does on each function, and returns -1 to signal the error. The binding raises ZMQ_Error regardless of the error kind, which makes difficult to handle the proper kind of error than if it raised different exceptions for different C errors.
I keep asking myself that question. I use ZeroMQ for IPC between an Ada backend and a GUI. I could use GNAT.Sockets, I guess, and reimplement all the ZeroMQ logic I need. ZeroMQ nicely wraps all the platform specific details and allows to select different transports for local or remote.
Then the bindings should do it this way:
if Result = -1 then
declare
Code : constant int := Errno;
begin
raise ZMQ_Error with ZMQ_Error_Text (Code) & " [" & Image (Code) & "]";
end;
end if;
Why not using shared memory for IPC?
If the GUI is in Ada, then perhaps ipDSA might be an option ? (It uses Dmitryās shared memory
).
That only works locally, which is fine for my current purposes, but it would prevent me from connecting a GUI to a remote machine if I ever wanted to do that. Of course, I could implement it as separate ātransportsā. Food for thought.
I wish the GUI was in Ada, but itās in C++/Qt6. When I considered implementing the IPC fully in Ada, it would have been a shared ibrary that C++ and Ada would consume to handle intercomms. But ZeroMQ looked like a ready to use alternative. The more I use it, the more I see some of the shortcomings and potential edge cases.
Just a side comment: I serialize all messages using ASN.1. Reason for this format is I like it as IDL and can be used from Ada and C. But I considered many alternatives.
Would it be better to use a ācaseā statement on āCodeā and raise separate, appropriately named exceptions for each āCodeā value ?
I did consider doing that, and I do that on my own code. But since this is someone elseās library, Iām not sure they would like it.
Interesting. I implemented full ASN.1, mainly for the sake LDAP and Cryptography. It was a torture. ITU-I, Videotext, ISO 646 such a joy!
I use this compiler GitHub - esa/asn1scc: ASN1SCC: An open source ASN.1 compiler for embedded systems Ā· GitHub
It compiles to Ada or C.
I saw on your Simple-Components that you had it, but I wanted code generation. I donāt remember if your implementation generates code or only de/serialize from Ada types.
Only if you have a limited and closed number of. To me it is usually a compromise. There are non-error codes like EAGAIN I do not like to be exceptions. There are errors I want to group together etc.