When using thread_signal(ThreadID, throw(my_exception)): foreign predicate system:>/2 did not clear exception

I am trying to support cancelling a long running goal in a thread. Moreover, I want to have the goal automatically cancelled by a timeout, but support manual cancellation earlier as well.

I started by running sleep(20000) in a thread started by thread_create/3 with the thread alias goal1.

I then try to cancel it by calling:

% Note that I don't throw abort/0 even though that is a more reliable
% "mostly uncatchable" exception because I want the thread to stick
% around to run more goals
thread_signal(goal1, throw(cancel_goal))

This works as expected and the exception bubbles up the thread properly.

However, if I wrap the goal first in call_with_time_limit/2 like this:

call_with_time_limit(10, sleep(20000))

and then cancel it the same way:

thread_signal(goal1, throw(cancel_goal))

I get this error spit out to the top level:

Thread 4 (goal1): foreign predicate system:>/2 did not clear exception: 
	cancel_goal

and the error that bubbles up is actually time_limit_exceeded, which is the exception that would be thrown normally by the call_with_time_limit/2 predicate on timeout (even though I am well before the timeout).

Am I doing something incorrectly?

[Edit: Here’s the actual code I’m using in case that helps. The issue seems timing related since it doesn’t always happen…]

% A successful test session, I don't seem to be able to repro now...
?- startGoalThread.
true.

?- sendGoalToGoalThread(20000, sleep(10000), []).
true.

?- cancelGoal.
error(cancel_goal)
true.

?- sendGoalToGoalThread(20000, sleep(1), []).
true.

?- cancelGoal.
true([[]])
true.
startGoalThread :-
   thread_self(CommunicationThreadID),
   thread_create(
                goal_thread(CommunicationThreadID),
                _,
                [alias(goal1)]
            ).

sendGoalToGoalThread(Timeout, Goal, BindingList) :-
    thread_send_message(goal1, goal(Goal, BindingList, Timeout)).

cancelGoal :-
    % Insert a known exception into the thread
    thread_signal(goal1, throw(cancel_goal)),
    % Wait for whatever response occurs
    thread_self(SelfID),
    thread_get_message(SelfID, result(Result)),
    writeln(Result).

% The worker predicate for the Goal thread.  Looks for a message, processes it, then recurses.
% Goals always run in the same thread in case the user is setting thread
% local information.
goal_thread(RespondToThreadID) :-
    thread_self(SelfID),
    thread_get_message(SelfID, goal(Goal, BindingList, QueryTimeout)),
    (
        % Exceptions that occur while executing the goal should be returned
        % run all commands in the context of the 'user' module so it acts like
        % the Prolog top level
        QueryTimeout == -1
        ->
        (
            ThreadGoal = catch(findall(BindingList, @(Goal, user), Answers), E, true)
        )
        ;
        (
            ThreadGoal = catch(call_with_time_limit(QueryTimeout, findall(BindingList, @(Goal, user), Answers)), E, true)
        )
    ),
    debug(prologServer(command), "Executing Goal: ~w", [Goal]),
    ThreadGoal,
    (
        var(E)
        ->
        (
            debug(prologServer(command), "Completed Goal: ~w", [Goal]),
            Answers == []
            ->
            (
                thread_send_message(RespondToThreadID, result(false))
            )
            ;
            (
                thread_send_message(RespondToThreadID, result(true(Answers)))
            )
        )
        ;
        (
            debug(prologServer(command), "Exception in Goal: ~w, ~w", [Goal, E]),
            thread_send_message(RespondToThreadID, result(error(E)))
        )
    ),
    % Use tail recursion to avoid growing the stack
    goal_thread(RespondToThreadID).

I can now reliably reproduce getting this message when shutting down using halt(abort):

Thread 6 (language_server1_conn2_comm): foreign predicate system:>/2 did not clear exception: $aborted

It looks like it is output as a system “warning” (based on the color of the text :slight_smile: )

@jan Is this something expected and not to worry about? or a bug I should log?

Worst case I’d love to at least get rid of the warning because it happens during normal shutdown of the code I’m working on and it at least looks scary in the logs.

It should be fixed. It mostly means an exception is lost. That doesn’t need to be terrible, surely when it is part of the shutdown anyway :slight_smile: When I’m at evaluation and testing I’ll probably find and fix it. These aren’t very hard to fix: set an gdb breakpoint on PL_raise_exception(), possibly with some conditions, examine the context and where the FALSE return code is not propagated.