Prolog has a couple of ways to stop the current computation. halt/1 is the most drastic. Next to that we have thread_exit/1 to terminate a thread immediately providing some value and abort/0 to stop the current execution without leaving Prolog.
Long ago, abort/0 simply discarded the current Prolog stacks and started new ones. With the introduction of threads one needs proper cleanup to ensure no locks are left behind. As we can abandon a computation safely using an exception, abort/0 got implemented as throw(‘$aborted’). But, for several reasons outside the scope of this, many people write code like
catch(MyGoal, _, fail)
If the abort is raised in MyGoal
, the abort is aborted This was solved in catch/3 by dynamically wrapping the recovery call in call_cleanup/2 if the ball is '$aborted'
. That has done its job for decades.
thread_exit/1 used to be implemented based on pthread_exit()
, but this too is problematic as it bypasses Prolog cleanup. The predicate was deprecated and the use of exceptions was suggested. But again, we may have to deal with the exception being caught for the wrong reason.
This post introduced another issue
Python deals with interrupts and sys:exit() using exceptions. But, as Python calls Prolog, signals are not processed and exceptions may not be propagated.
To deal with this in a general way there is now a prototype doing
- Handle any exception of the shape
unwind(Term)
as'$aborted'
now: force the catch/3 to re-throw it. - Use
unwind(abort)
for the current'$aborted'
- Introduce
unwind(halt(Status))
and `unwind(thread_exit(Term))`` for the obvious purposes. - Map halt/1 to throw
unwind(halt(Status))
and start the old cleanup after the exception has bubbled to the top. - If other threads need to be killed, raise
unwind(halt(Status))
in them. The current system uses'$aborted'
for the same purpose.
For Janus (Python), we improve on the situation by
- Mapping
SystemExit(code)
to/fromunwind(halt(Code))
while unwinding the nested Python → Prolog → Python → … stack - Do the same for
KeyboardInterrupt
to/fromunwind(keyboard_interrupt))
- Added
janus.heartbeat(count)
, which calls a dummy Python function everycount
inferences. This allows Python to handle signals while Prolog is working.
With the above. the interrupt problem raised by @pampelmuse76 can be solved simply by calling this somewhere during the initialization (e.g., right after importing janus
).
janus.heartbeat()
Possibly we should do so automatically? It causes a slowdown of about 1% using the default setting to call Python every 10,000 inferences.
I post this as an RFC to see whether someone can suggest further improvements or changes that make it all nicer. The code is in the current master. Except for some issues such as reported by @josderoo, this should have little consequences.