Am Sa., 29. Okt. 2022 um 17:38 Uhr schrieb John Cowan <xxxxxx@ccil.org>: > > > > On Sat, Oct 29, 2022 at 4:13 AM Marc Nieper-Wißkirchen <xxxxxx@gmail.com> wrote: > >> >> I see your point. And adding an extra timeout parameter to >> thread-terminate! would make the interface more complicated. The only >> problem I see is that this change would introduce a silent >> incompatibility with SRFI 18. Thus, it may be better to drop the name >> thread-terminate! and replace it with a different name, like >> thread-kill!. > > > I agree in principle, although I find the use of `kill` in APIs distasteful, and would prefer `exit`. I don't mind `kill' in APIs as long as the context is clear. But `exit' is anyway better because the procedure to terminate the program is also called `exit'. > However, I continue to think that non-cooperative termination is a Bad Idea, because any data structure or resource referred to by the forcibly terminated thread is left in an undefined condition. Java historically had such a feature but removed it; Python and C# have never had one. The ability to raise an exception in a thread is sufficient, though it is convenient if a thread can forcibly terminate itself. Thanks to SRFI 18's notion of abandoned mutexes, you can arrange it so that data structures won't be in an undefined condition. This is, in principle, no harder than arranging that data structures can be shared between two threads. Marc's thread-interrupt! together with a primitive for a thread to forcibly terminates itself is strictly more powerful than thread-exit!. > >> I don't yet see how this is equally powerful. What I have in mind is >> an implementation of a Scheme REPL where the user starts a program (in >> some thread) that goes astray and wishes to abnormally terminate it. >> This must work with no cooperation from the program thread. > > > Why must it? After all, there is no primitive operation "start a program in a thread". The REPL can and should arrange for the necessary cooperation before executing the program; a simple approach is to modify the code to insert cancel points, if the REPL's evaluator compiles to machine code. This would be a thread-exit! just through other measures. >> Also, thread-interrupt! breaches abstraction barriers. Given the code >> (begin foo1 foo2) and assuming that evaluating foo1 does not raise any >> exception (nor invokes a previously captured continuation), there is a >> guarantee that foo2 will always be evaluated once after foo1 (bar >> abnormal termination). > > > I agree with Marc F that there is no such guarantee in practical concurrent programming: exceptions may be asynchronously introduced. In the current semantic model of Scheme (and of SRFI 18 bar extensions), exceptions are synchronously raised in the sense that an exception is only raised during specific evaluations, and which exceptions are raised is documented in the various specifications. Concurrent programming is not impossible in this model. So to implement what you have in mind, the semantic model would have to be extended so that every evaluation step can, in principle, cause an arbitrary exception to be raised. I have to think about it. But if you agree to this extension, I wonder about your reluctance concering thread-exit!; getting asynchronous exceptions right is, at least, not simpler. >> In principle, a rogue thread could thread-interrupt! itself all the >> time: (let f () (thread-interrupt! (current-thread) f)). Depending >> where safe points are, the thread will never be forced to kill itself. > > > Quite so. But this is no worse than a (simple) REPL being asked to execute (let loop () (loop)). Chibi's fancy REPL uses thread-interrupt!, which is provided in the (chibi ast) library. I would like to be able to kill a thread that loops forever. > I meant to mention that when a thread running under the control of a thread-runner terminates on an unhandled exception e (synchronous or asynchronous), the thread-runner must force an exception to occur in all the other threads. This maintains the dynamic (not lexical as I wrote before) scope property that only when all the threads have terminated will the with-thread-runner procedure return. The result is that e is raised in the dynamic context of with-thread-runner as if by thread-join!. > > Note also that once a thread-runner's threads have all terminated, it should be closed, meaning that even if the object is still accessible no new threads can be created on it (this is analogous to files being closed). I will return to this once, and your thread-runner idea, which I agree should be included. Marc