Go an exception when aborting DCG processing .. but is anyone interested in these?

A DCG was not stopping, so I CTRL-C’d it and then typed ‘a’ for abort.

I suppose the with_output_to/2 comes from a debug/3 call that I inserted in DGC processing using call//1, which makes the list processing elements available for inspection. But this seems to change something, so that DCG processing no longer stops. Weird.

Thread 1 (main): foreign predicate system:with_output_to/2 did not clear exception: 
        $aborted
Thread 1 (main) at Mon Jul  6 15:15:33 2020] ../src/pl-gc.c:689: queryOfFrame: Assertion failed: qf->magic == QID_MAGIC
C-stack trace labeled "assert_fail":
  [0] save_backtrace() at /home/calvin/COMPILE/swipl-devel/build/../src/os/pl-cstack.c:332 [0x7fa6a9cd78ee]
  [1] __assert_fail() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-assert.c:106 [0x7fa6a9c97ec6]
  [2] queryOfFrame() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-gc.c:689 [0x7fa6a9c363d3]
  [3] mark_environments() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-gc.c:2355 [0x7fa6a9c366b8]
  [4] mark_query_stacks() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-gc.c:2371 [0x7fa6a9c38d33]
  [5] PL_next_solution() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-vmi.c:4677 [0x7fa6a9c01e1d]
  [6] query_loop() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-pro.c:144 [0x7fa6a9c4f0cf]
  [7] prologToplevel() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-pro.c:483 [0x7fa6a9c4f8ab]
  [8] PL_toplevel() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-fli.c:4586 [0x7fa6a9bfc5ce]
  [9] swipl(+0x10a5) [0x4010a5]
  [10] __libc_start_main() at ??:? [0x7fa6a99e5f43]
  [11] swipl(+0x10ee) [0x4010ee]
[FATAL ERROR: at Mon Jul  6 15:15:33 2020
        Received signal 6 (abrt) while in 30-th garbage collection]
[Thread 1 (main) at Mon Jul  6 15:15:33 2020] ../src/pl-attvar.c:169: assignAttVar: Assertion failed: isAttVar(*av)
C-stack trace labeled "assert_fail":
  [0] save_backtrace() at /home/calvin/COMPILE/swipl-devel/build/../src/os/pl-cstack.c:332 [0x7fa6a9cd78ee]
  [1] __assert_fail() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-assert.c:106 [0x7fa6a9c97ec6]
  [2] assignAttVar() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-attvar.c:170 (discriminator 1) [0x7fa6a9c8c61d]
  [3] bindConst__LD() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-inline.h:415 [0x7fa6a9c0fcf8]
  [4] query_loop() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-pro.c:144 [0x7fa6a9c4f0cf]
  [5] cleanupProlog() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-init.c:1369 [0x7fa6a9c8e2d2]
  [6] haltProlog() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-fli.c:4617 [0x7fa6a9bede8a]
  [7] PL_abort_process() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-fli.c:4649 [0x7fa6a9bfc61e]
  [8] vfatalError() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-init.c:1624 [0x7fa6a9c8eaa5]
  [9] fatalError() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-init.c:1502 [0x7fa6a9c8eb3c]
  [10] dispatch_signal() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-setup.c:480 [0x7fa6a9c6924c]
  [11] __restore_rt() at sigaction.c:? [0x7fa6a99f9ec0]
  [12] __GI_raise() at :? [0x7fa6a99f9e35]
  [13] __GI_abort() at :? [0x7fa6a99e4895]
  [14] __assert_fail() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-assert.c:107 [0x7fa6a9c97ed7]
  [15] queryOfFrame() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-gc.c:689 [0x7fa6a9c363d3]
  [16] mark_environments() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-gc.c:2355 [0x7fa6a9c366b8]
  [17] mark_query_stacks() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-gc.c:2371 [0x7fa6a9c38d33]
  [18] PL_next_solution() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-vmi.c:4677 [0x7fa6a9c01e1d]
  [19] query_loop() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-pro.c:144 [0x7fa6a9c4f0cf]
  [20] prologToplevel() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-pro.c:483 [0x7fa6a9c4f8ab]
  [21] PL_toplevel() at /home/calvin/COMPILE/swipl-devel/build/../src/pl-fli.c:4586 [0x7fa6a9bfc5ce]
  [22] swipl(+0x10a5) [0x4010a5]
  [23] __libc_start_main() at ??:? [0x7fa6a99e5f43]
  [24] swipl(+0x10ee) [0x4010ee]
Segmentation fault (core dumped)

But is this informative? Is there anywhere to post these?

While others may be able to solve this with the info given could I get a reprex :grinning:

It’s not fully deterministic:

Code I’m working on (which doesn’t work) in version 8.1.32-10-g1885b056f:

:- use_module(library(clpfd)).

:- debug(dcg).

say(Where,List1,List2) :- \+ \+ debug(dcg,"~q ~q ~q",[Where,List1,List2]).

% greedily grab an "ab"
abba_char(AB,BA) --> 
   [a,b], !, 
   call(say("#1")), 
   abba_char(ABm,BA), {AB #= ABm+1}. 

% greedily grab an "ba"
abba_char(AB,BA) -->
   [b,a], !, 
   call(say("#2")),
   abba_char(AB,BAm), {BA #= BAm+1}.

% lookahead 1 char, it's not a "b", push it back!
abba_char(AB,BA), [C] -->
   [a,C], { C \== b }, !, 
   call(say("#3")),
   abba_char(AB,BA).

% lookahead 1 char, it's not an "a", push it back!
abba_char(AB,BA), [C] -->
   [b,C], { C \== a }, !, 
   call(say("#4")), 
   abba_char(AB,BA).

% characters neither "a" nor "b" are disregarded
abba_char(AB,BA) --> 
   [C], { \+ memberchk(C,[a,b]) }, !, 
   call(say("#5")), 
   abba_char(AB,BA).

% a final "a"
abba_char(0,0) --> 
   [a],!,
   call(say("#5")).

% a final "b"
abba_char(0,0) --> 
   [b],!,
   call(say("#6")).

% no character at all, which in this case means the list is now empty
abba_char(0,0) --> 
   [],
   call(say("#done")).

% helper that eats an atom and gives the parsing result

phrase_chars(X,AB,BA) :- atom_chars(X,Cs),phrase(abba_char(AB,BA),Cs).

:- begin_tests(dcg_chars).

test(0,[true(Truly)]) :- phrase_chars('',AB,BA),Truly = ([AB,BA] == [0,0]).
test(1,[true(Truly)]) :- phrase_chars('abbaabbaba',AB,BA),Truly = ([AB,BA] == [2,3]).
test(2,[true(Truly)]) :- phrase_chars('yabybayyyy',AB,BA),Truly = ([AB,BA] == [1,1]).
test(3,[true(Truly)]) :- phrase_chars('yyyyyyyyya',AB,BA),Truly = ([AB,BA] == [0,0]).
test(4,[true(Truly)]) :- phrase_chars('yyyyyyybaa',AB,BA),Truly = ([AB,BA] == [0,1]).
test(5,[true(Truly)]) :- phrase_chars('yyyyyyyba' ,AB,BA),Truly = ([AB,BA] == [0,1]).
test(6,[true(Truly)]) :- phrase_chars('yaybyayba' ,AB,BA),Truly = ([AB,BA] == [0,1]).
test(7,[true(Truly)]) :- phrase_chars('yaabby'    ,AB,BA),Truly = ([AB,BA] == [1,0]).

:- end_tests(dcg_chars). 

rt(dcg_chars) :- run_tests(dcg_chars).

You get the error as follows:

Run

phrase_chars('yaabby'    ,AB,BA).

then the Prolog Processor goes into an infinite loop. For some reason call//1 resets the lists? It is as yet a mystery to me.

Let it run a second, then call CTRL-C, and then press (a)bort.

In most cases, this ends well, but occasionally, you get the exception above.

The test code is likely irrelevant.

1 Like

Thanks.

It is reproducible.

2 Likes

The first problem I found was that say/3 needs some guard statements.

say(Where,List1,List2) :-
    must_be(ground,Where),
    must_be(ground,List1),
    must_be(ground,List2),
    \+ \+ debug(dcg,"~q ~q ~q",[Where,List1,List2]).

In

abba_char(AB,BA) -->
   [C], { \+ memberchk(C,[a,b]) }, !,
   call(say("#5")),
   abba_char(AB,BA).

which is translated to

abba_char(E, F, [A|B], H) :-
    \+ memberchk(A, [a, b]),
    C=B,
    !,
    D=C,
    call(say("#5"), D, G),
    abba_char(E, F, G, H).

executes

call(say("#5"), D, G)

with G unbound.

Thank you.

But does it really need the guard statements? I didn’t expect debug/3 to accidentally instantiate any uninstantiated variable, but to be doubly sure, I also added the \+ \+. That should be behind-covering right, shouldn’t it?

But if I comment out the call//1 calls, it definitely terminates (thought it wrongly doesn’t accept a perfectly valid string).

My knowledge of double negation, \+ \+ is so limited any answer I give has to be taken with a grain of salt.

I would say no, the double negation is not doing what you expect. I could be wrong.

I modified your code some what and it now runs all of the test without an infinite loop.

Sorry that it is messy, but it is for quick feedback.

% consult("C:/Users/Eric/Documents/Projects/Prolog/swi-discourse_047.pl").

% https://swi-prolog.discourse.group/t/go-an-exception-when-aborting-dcg-processing-but-is-anyone-interested-in-these/2606/3

% -----------------------------------------------------------------------------

:- use_module(library(clpfd)).

:- debug(dcg).

% say(Where,List1,List2) :-
%     \+ \+ debug(dcg,"~q ~q ~q",[Where,List1,List2]).

say(Where,List1,List2) :-
    must_be(ground,Where),
    must_be(ground,List1),
    must_be(ground,List2),
    \+ \+ debug(dcg,"~q ~q ~q",[Where,List1,List2]).

% greedily grab an "ab"
abba_char(AB,BA) -->
    { debug(dcg,'@1',[]) },
   [a,b], !,
%    call(say("#1")),
   abba_char(ABm,BA), {AB #= ABm+1}.

% greedily grab an "ba"
abba_char(AB,BA) -->
    { debug(dcg,'@2',[]) },
   [b,a], !,
%    call(say("#2")),
   abba_char(AB,BAm), {BA #= BAm+1}.

% lookahead 1 char, it's not a "b", push it back!
abba_char(AB,BA), [C] -->
    { debug(dcg,'@3',[]) },
   [a,C], { C \== b }, !,
%    call(say("#3")),
   abba_char(AB,BA).

% lookahead 1 char, it's not an "a", push it back!
abba_char(AB,BA), [C] -->
    { debug(dcg,'@4',[]) },
   [b,C], { C \== a }, !,
%    call(say("#4")),
   abba_char(AB,BA).

% characters neither "a" nor "b" are disregarded
abba_char(AB,BA) -->
    { debug(dcg,'@5',[]) },
   [C], { \+ memberchk(C,[a,b]) }, !,
%    call(say("#5")),
   abba_char(AB,BA).

% a final "a"
abba_char(0,0) -->
    { debug(dcg,'@6',[]) },
   [a],!,
%    call(say("#5")).
    abba_char(0,0),
    [].     % Think of this as adding true, but works for DCGs.

% a final "b"
abba_char(0,0) -->
    { debug(dcg,'@7',[]) },
   [b],!,
%    call(say("#6")).
    [].

% no character at all, which in this case means the list is now empty
abba_char(0,0) -->
    { debug(dcg,'@8',[]) },
   [].
%    call(say("#done")).


% helper that eats an atom and gives the parsing result

phrase_chars(X,AB,BA) :- atom_chars(X,Cs),phrase(abba_char(AB,BA),Cs).

:- begin_tests(dcg_chars).

test(0,[true(Truly)]) :- phrase_chars('',AB,BA),Truly = ([AB,BA] == [0,0]).
test(1,[true(Truly)]) :- phrase_chars('abbaabbaba',AB,BA),Truly = ([AB,BA] == [2,3]).
test(2,[true(Truly)]) :- phrase_chars('yabybayyyy',AB,BA),Truly = ([AB,BA] == [1,1]).
test(3,[true(Truly)]) :- phrase_chars('yyyyyyyyya',AB,BA),Truly = ([AB,BA] == [0,0]).
test(4,[true(Truly)]) :- phrase_chars('yyyyyyybaa',AB,BA),Truly = ([AB,BA] == [0,1]).
test(5,[true(Truly)]) :- phrase_chars('yyyyyyyba' ,AB,BA),Truly = ([AB,BA] == [0,1]).
test(6,[true(Truly)]) :- phrase_chars('yaybyayba' ,AB,BA),Truly = ([AB,BA] == [0,1]).
test(7,[true(Truly)]) :- phrase_chars('yaabby'    ,AB,BA),Truly = ([AB,BA] == [1,0]).

:- end_tests(dcg_chars).

rt(dcg_chars) :- run_tests(dcg_chars).

% phrase_chars('yaabby'    ,AB,BA).

Test 6 and 7 still fail.

1 Like

I expect \+ \+ to rollback any accidental instantiations. It better do.

Ah yes, I can use debug directly. Thanks. Ok, I will explore further.

Here is a variation that I like more because it tells you when the predicate was called, @X.0, when the predicate passed the guard statements, @X.1, and when the predicate complted, @X.2.

% greedily grab an "ab"
abba_char(AB,BA) -->
    { debug(dcg,'@1.0',[]) },
   [a,b], !,
    { debug(dcg,'@1.1',[]) },
   abba_char(ABm,BA), {AB #= ABm+1},
   { debug(dcg,'@1.2',[]) }.

% greedily grab an "ba"
abba_char(AB,BA) -->
    { debug(dcg,'@2.0',[]) },
   [b,a], !,
    { debug(dcg,'@2.1',[]) },
   abba_char(AB,BAm), {BA #= BAm+1},
   { debug(dcg,'@2.2',[]) }.

% lookahead 1 char, it's not a "b", push it back!
abba_char(AB,BA), [C] -->
    { debug(dcg,'@3.0',[]) },
   [a,C], { C \== b }, !,
    { debug(dcg,'@3.1',[]) },
   abba_char(AB,BA),
   { debug(dcg,'@3.2',[]) }.

% lookahead 1 char, it's not an "a", push it back!
abba_char(AB,BA), [C] -->
    { debug(dcg,'@4.0',[]) },
   [b,C], { C \== a }, !,
    { debug(dcg,'@4.1',[]) },
   abba_char(AB,BA),
   { debug(dcg,'@4.2',[]) }.

% characters neither "a" nor "b" are disregarded
abba_char(AB,BA) -->
    { debug(dcg,'@5.0',[]) },
   [C], { \+ memberchk(C,[a,b]) }, !,
    { debug(dcg,'@5.1',[]) },
   abba_char(AB,BA),
   { debug(dcg,'@5.2',[]) }.

% a final "a"
abba_char(0,0) -->
    { debug(dcg,'@6.0',[]) },
   [a],!,
    { debug(dcg,'@6.1',[]) },
    abba_char(0,0),
   { debug(dcg,'@6.2',[]) }.

% a final "b"
abba_char(0,0) -->
    { debug(dcg,'@7.0',[]) },
   [b],!,
   { debug(dcg,'@7.1',[]) },
   { debug(dcg,'@7.2',[]) }.

% no character at all, which in this case means the list is now empty
abba_char(0,0) -->
    { debug(dcg,'@8.0',[]) },
    { debug(dcg,'@8.1',[]) },
   [],
    { debug(dcg,'@8.2',[]) }.

Personally when I add this many debug/1 statements I step back and look at what I am doing and typically take a different approach this just adds more to the confusion than helping me to understand the correct model for the problem. In other words it drags me into thinking imperative instead of thinking declarative.

Another way to solve your problem that might be more to your liking would be to modify say/3

say(Where,List1,List2) :-
    is_of_type(ground,Where),
    is_of_type(ground,List1),
    is_of_type(ground,List2),
    \+ \+ debug(dcg,"~q ~q ~q",[Where,List1,List2]).

say(Where,List1,List2) :-
    is_of_type(ground,Where),
    is_of_type(ground,List1),
    is_of_type(var,List2),
    \+ \+ debug(dcg,"~q ~q",[Where,List1]).

say(Where,List1,List2) :-
    is_of_type(ground,Where),
    is_of_type(var,List1),
    is_of_type(var,List2),
    \+ \+ debug(dcg,"~q",[Where]).

I did not check this code but you should get the idea.

I think this is now fixed by patch 9882bb28baf3fca136e73602b9c78bd52abdb820. The problem was when the ^C arrives as output from a message is flushed. This raises an exception, but this exception was not properly propagated. This was hinted by

As ^C in the end calls thread_signal/2, I’ve tested using this, which now runs happily.

?- forall(between(1, 1000, _),
          catch(call_with_time_limit(0.01, phrase_chars('yaabby',AB,BA)),
                time_limit_exceeded, true)).

2 Likes