[FATAL ERROR: Too many stacked strings] when using the C++ version 1 interface

Hi, I’m using the prolog C++ interface for some a recursive C++ program. Basically, the program keeps calling “assertz”, “retractall”, and do some query. But after around 20 seconds, the program would throw the [FATAL ERROR: Too many stacked strings] error. I read some posts and tried those methods such as pl_strings_mark() + pl_strings_release(). But they cannot fix my problem. When I used the python interface it didn’t have such an issue. I’m wondering, how can I fix the problem in the C++ version 1 interface?

Thanks.

Without seeing your code, it’s difficult to know what’s going wrong.

C++ version 1 is deprecated and is unlikely to have any fixes. I strong suggest converting to version 2. Amongst other things, it has better error checking (both at compile and run time), which might help you find your problem.

Instead of using the C macros PL_STRINGS_MARK() etc., I suggest using the PlStringBuffers class. This makes sure that the “mark” and “release” calls are properly nested, using RAII.

In general, it’s best to avoid the char* APIs and instead use std::string – this can avoid lifetime issues: a char* might point to garbage whereas a std::string contains a copy of the value. Also, use of the std::string interfaces usually avoids the need for PlStringBuffers (or PL_STRINGS_MARK()).

[There are a few places in the C++ version 2 API where char* is used instead of std::string … I’m in the process of fixing those. But you’re unlikely to encounter them if you use the non-deprecated methods.]

Thanks for your reply. After I switch to version 2, the error did not appear as the original. However, it changed to PlExceptionFail after the program ran for like 50 seconds. In these 50 seconds, the program did 4e6 many assertz/1 and 4e6 many retractall/1, however, the total number of facts never exceeded 20 (i.e. assertz/1 call minus retractall/1 call at any time never exceed 20). I’m wondering, is there a limit on the total call to assertz/1 even if I remove the fact with retractall/1 later? If not, how should I fix this issue, if my program needs massive call to assertz/1 and retractall/1?

Thread 1 (main): foreign predicate system:retractall/1 did not clear exception:
        error(resource_error(stack),stack_overflow{choicepoints:1,depth:1,environments:2,globalused:760499,localused:233612,stack:[frame(1,system:call(retractall/1),[]),frame(0,system: $c_call_prolog,[])],stack_limit:1048576,trailused:53330})

I don’t know what’s causing your stack overflow or how to isolate that; and without seeing your full code, it’s not clear to me how to figure that out.

The “did not clear exception” message indicates that a Prolog error happened and you didn’t handle it. If any of your PlCall()s are outside a try-catch, or if any of them don’t check the return code, then that could be the cause (I should probably put [[nodiscard]] on PlCall()).

I’ll have to think about the best way of doing error handling in your situation – I’ve been concentrating on error handling for C++ code that’s called by Prolog, not the C++ calling Prolog. But at minimum, you should check the return code from PlCall().

PlCall() doesn’t have as nice of an interface because there are two possible sets of return codes (see the documentation on PL_next_solution() if you want to know more), although you’re using the simple one, namely success/failure (1 or 0). It’s a bit more complicated because 0 can also mean an error. If you look at the code for PlQuery::next_solution(), you’ll see that it uses PlEx_fail(), which checks the Prolog return code and then throw a C++ error if there was a Prolog error.
[I’ve been thinking about how to improve `PlCall()`, but haven’t done anything with it so far.]

I tried to recreate the error with a simple example. Suppose the prolog program is:

hello(X) :- fact(X).

The C++ program would face an error after i ~ 4e6.

#include <bits/stdc++.h>
#include <SWI-Prolog.h>
#include <SWI-cpp2.h>
#include <SWI-cpp2.cpp>

std::vector<std::string> run_query() {
    PlTermv av(1);
    PlQuery q("hello", av);
    std::vector<std::string> ret;
    try {
        while( q.next_solution() ) {
            
            auto res = av[0].as_string();
            ret.push_back(res);
        }
    } catch ( PlExceptionBase &ex ) { 
        std::cerr << ex.what() << std::endl;
    }

    return ret;
}

// g++ -std=c++17 trial.cpp -o trial  -L/usr/lib/swi-prolog/lib/x86_64-linux -lswipl

int main(int argc, char **argv)
{
    Plx_initialise(argc, argv);
    PlCall("consult('example.pl')");
    PlCall("assertz(fact(1))");
    for (int i = 0 ; i < 100000000; ++i) {
        if (i % 10000 == 0) {
            std::cout << i << std::endl;
        }
        PlCall("assertz(fact(2))");
        PlCall("assertz(fact(3))");
        run_query();
        PlCall("retractall(fact(2))");
        PlCall("retractall(fact(3))");
    }
    
    PL_halt(0);
    return 0;
}

I have a feeling that the problem is that there’s no “close” for any query that doesn’t try all of the solutions (because the close would throw away bindings, even though you don’t have any), so stack frames accumulate. I need to go back through some notes I made about how this should be handled. Unfortunately, there are a lot of variations of how queries are done, and they don’t map nicely into C++ … I also need to look at how PL_Q_CATCH_EXCEPTION works with this.

Looking at your code, your PlQuery gets all its solutions, and that should call PlQuery::close_destroy() or PlQuery::cut() when there are no more solutions; I suspect that the PlCall()s are the problem because they don’t try all solutions. I’ll try some variations tomorrow, but I think that if you change your PlCall()s to something like the following, it’ll work:

{ PlQuery q("assertz(fact(2))");
  PlCheckFail(q::next_solution());
  q.close_destroy(); // or q.cut()
}

(I’m on Pacific Daylight Time, and it’s too late in the evening for looking into this further today.)

1 Like

I think this needs to be like this. We need to create a Prolog “foreign frame” such that the PlTermv av is reclaimed. Otherwise each invocation of run_query() will push more term references to the stack, eventually causing an overflow.

std::vector<std::string> run_query() {
    PlFrame fr();
    PlTermv av(1);
    PlQuery q("hello", av);

As for @peter.ludemann , I guess the PlQuery destructor deals with cutting the query, no?

1 Like

Yes.
But the problem is that the the result of the query may need to outlive the query; will a call to PL_cut_query() preserve the result? (PL_close_query() will, I think). And is PL_cut_query() sufficient, or is PL_close_query() also required? (I think you answered this, but I need to look through my notes)

I think that this isn’t a problem usually because if the query is done withing a PREDICATE, things will get cleaned up (although if a lot of queries are done inside a PREDICATE, the same problem could arise, I suppose).

PL_cut_query() discards the query itself. PL_close_query() does the same, but also discards any bindings created. As long as the PlTerms that provide access to these bindings are recovered, GC will get rid of them in the normal Prolog way. So no, there is no need for PL_close_query() and it is in general undesirable as e.g., changes to backtrackable global variables are lost. Only, notably before global variables existed, in most scenarios the two have the same effect and PL_close_query() avoids the need for GC. There was a time that didn’t exist either :slight_smile:

1 Like