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
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:
and then cancel it the same way:
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).