Why blame the platform for our parricidal tendencies. In general, isn't it short-sighted to kill whoever called us? Wouldn't he intend to do more work after that?
I simply think that a cross platform library should try to archive the same behavior on all platforms. I don't expect anyone to read into all the different underlying implementations of all the different libraries (I mean this is the whole point of a cross platform library). If it is sensical to do a certain thing is a different question, but (at least to the degree that is possible) it should be equally (non)sensical on all platforms.
But using both OnTerminate and FreeOnTerminate=True can have other dangerous implications. One of them is that the thread actually can outlive the instance of the OnTerminate handler, then calling it will result in dangling Self. That can be the case in a dynamically created child form which started a long running thread, then form cancelled, tread terminated with terminate (no waitfor) then form destroyed.
Well lazarus managed forms will be created on program start and will be freed on program end. Even when you close a child form it will not be freed. So you must actively free the form for this to be a problem. And yes, you should not free memory that is still in use by the thread, but thats a more general rule, not just for events, and not just if you use FreeOnTerminate.
Also events may not just be Form Methods, in my current project I create a special object on which an event is called (so the thread is not bound to a certain form), and to make sure that this object will not outlive the thread, it registers itself in the OnTerminate to Free itself.
So I wouldn't consider this specifically an OnTerminate and FreeOnTerminate problem, but more generally a "don't free things still in use" problem.
Another issue that came to my mind a few month ago is, when Method Pointer as events are used, they consist of 2 pointers. This means it is not guaranteed that it will be atomically written or read. So this means that you need to encapsulate all event pointer accesses with a critical section. I did this in one of my projects, but it is really tedious, and it is usally (looking at RTL and LCL sources) not done. But technically this means that the use of OnTerminate is always unsafe.
Can you make a small example project demonstrating this, I doubt Windows will kill a program waiting on TThread.WaitFor in a GUI thread. Because of: https://gitlab.com/freepascal.org/fpc/source/-/blob/main/rtl/win/tthread.inc#L106
You are right, windows won't kill it, it will just freeze the form (which is already bad enough). That said since a few versions KDE also added the kill freezed applications feature.
But never the less you should not freeze the main thread for multiple seconds even if windows would not kill it. And if it is just because the user will not like it.
The problem with WaitFor is that there is not really any clean solution. The Application.ProcessMessages loop is probably the closest, but as soon as you have nested Application.ProcessMessages it breaks down. The self queing event doesn't have the problem but is really hacky and unreadable code.
OnTerminate as you pointed out only works on Windows, so after all FreeOnTerminate is by far the cleanest solution, that said, as you pointed out, it can be quite dangerous, and should only be used if there is just one reference to that thread and that it is either nilled once FreeOnTerminate is set, or is nilled during the OnTerminate event.
The latter is my favorite way of using a thread. Having the thread set FreeOnTerminate, and the OnTerminate event then nilling the reference to indicate to the main thread that it is not used anymore. But this is not always possible