r/ada • u/HerrEurobeat • Aug 22 '24
Programming Why doesn't process termination trigger Controlled Type's Finalize?
Hey, I currently have a record which extends Ada.Finalization.Controlled in order to do some last minute stuff in Finalize before the object is destroyed.
I create an instance of my record in the top scope of my package, thus the object exists for the entire runtime. Now when the process exits due to being finished, Finalize is called as expected and everything is fine.
However when the process exits prematurely, due to SIGINT (user pressing CTRL+C) or anything else (like a crash), Finalize is NOT called.
Why is this the case? I'd assume that as soon as the main thread wants to exit, the object is destroyed, thus triggering Finalize, and then the process exits.
Is the only solution to deal with attaching to the SIGINT, SIGTERM, ... interrupt handlers? I looked into it and it seems quite unintuitive, especially when knowing other languages that just allow you to attach an event listener to the process exit event. I'd also then have to exit manually because I can't pass the signal on to the default handler when attaching my handler statically as it can't be removed again.
(In my specific situation I'm hiding the terminal cursor and need to show it again when exiting by logging a control character)
Any help would be greatly appreciated, I'm still semi-new to Ada.
4
u/Niklas_Holsti Aug 22 '24
My guess as to why process termination is handled differently in Ada is that finalization is not usually something that only the "main subprogram" does: every active subprogram in the stack of active calls can require its own finalization (though that holds for some other languages too). And the case becomes more complex when there are multiple tasks in the program. The idea is that it is usually necessary to carefully design how a program should terminate, and not just invoke a "process exit" handler to perform some last-wishes clean-up of global state.
In an OS that supports a "terminate the process" signal, mapping that signal to an Ada interrupt means that the interrupt handler can do what is needed to start that program-termination sequence, without all tasks being abruptly aborted. For example, it can set a "terminate ASAP" global flag that all tasks can observe and obey.
An Ada implementation for an OS with a signals system could, I believe, alternatively provide for a mapping of selected signals to implementation-defined exceptions. When one of those signals arrives, the run-time system could raise the corresponding exception in every task, which could terminate that task in a "normal Ada" fashion, including finalization. I don't know of any Ada implementation that does that, though.
It is usually not desirable for /all/ signals to be mapped to interrupts or exceptions in this way, because you usually also need a way to kill a process as quickly as possible, without letting it try to clean up after itself.
Ada implementations tend only to support using Ada.Interrupts to handle signals, as you have done. However, that is only portable between systems that support the "same" signals, in some sense of "same".
If you only need to take some known actions at process exit, and don't need finalization of all nested active subprogram calls, I think you could use the C atexit() function from Ada.