Asynchronous rollback transaction


Is it possible to complete a transaction without fail in one thread; but then to have an different thread determine failure and rollback the transaction?


No. Transactions are deeply anchored in the thread logic. You can of course have the transaction waiting on some synchronization primitive (like receiving a message). In general though, I’d try to collect all data required for the processing and then start doing the processing inside the transaction. That gets more important when transactions have to check the consistency with the outside world (transaction/3) before doing the commit.

Thank you.

Yes, waiting on a signal could work – although, i guess changes to the KB are not visible to other threads.

Same for signals, right? Engines don’t have signal queues?

It is unrelated but, as is, engines have everything a thread has as well. Only in the single threaded version there are no mutexes for obvious reasons. I’m actually wondering whether or not that is a good idea. Sending a signal to an engine will be pending until you do an engine_next/2 or engine_post/3. It might be better if engines join the signal queue of the thread that runs them. I don’t know whether we want message queues for the single threaded version. Most of all, I don’t know whether we want thread_local/1 predicates to be also local to engines as they are now. Same for Prolog flags. Getting rid of all that stuff makes engines more lightweight and possibly also provides cleaner semantics. As is, they are roughly threads that are allowed to be attached and detached from an OS thread.

Yes, but there are also cases where you don’t want that. For example, Paul Tarau (one of the pioneers using engines/interactors in Prolog) illustrates its use by implementing findall/3 using engines. You would like to have the findall/3 access the thread_local/1 predicates of the thread that uses the engine. He also proposed implementing the suspend for tabling using engines, same story.

A crucial difference is that in Prolog we have dynamic predicates, which are a bit like global variables and we have the Prolog stacks. One of the nice things about engines is that you can have a nice stack based coroutine for one or more threads that may or may not be involved in backtracking.

Possibly an option when creating the engine that tells whether it should use the database view of the thread that is running it or have its own view is enough?

Paul Tarau provides a shiny world, such as:

find_all(Template, Goal, List) :-
    engine_create(Template, Goal, Engine),
    collect_all(Engine, List0),
    List = List0.

collect_all(Engine, [X| Xs]) :-
    engine_next(Engine, X),
    collect_all(Engine, Xs).
collect_all(_, []).

It doesn’t have a lot of computational advantage, its 3 times slower:

?- time(find_all(X, between(1,100000,X), _)).
% 200,006 inferences, 0.047 CPU in 0.047 seconds (99% CPU, 4266795 Lips)

?- time(findall(X, between(1,100000,X), _)).
% 200,010 inferences, 0.016 CPU in 0.016 seconds (99% CPU, 12800640 Lips)

But if you want to use it with your fiber prototype, you would need to
improve it, and then it doesn’t look anymore that shiny simple. For example
if I use from your fiber prototype the engine_yield(heartbeat),

I get a currious result:

?- findall(X, (X=1,engine_yield(heartbeat);X=2), L).
ERROR: No permission to execute vmi `'I_YIELD'' (not an engine)

?- find_all(X, (X=1,engine_yield(heartbeat);X=2), L).
L = [heartbeat, 1, 2].

Edit 07.03.2023
In Dogelog Player the heartbeat is transparent, it amounts to
sleep(0) but executed via a yield:

?- findall(X, (X=1,'$YIELD'(0);X=2), L).
L = [1, 2].

So for fibers, there are more problems to worry than only
a context/1 parameter to engine_create/4. You can possibly solve

the problem if you have a different engine_next/2, so that the application
programmer don’t need to be bothered. A solution could be to make
engine_next_reified/2 the primary predicate, and extending its prototcoll.

For example, tail recursive to redo the engine_next/2:

engine_next(E, T) :- 
    engine_next_reified(E, T0),
     (T0 = heartbeat -> 
            engine_next(E, T); 
      T0 = the(T1) -> 
            T = T1;

engine_yield(T) :-

But I am not sure whether such an implementation is desired, can
it be circumvented? This means every engine_next/2 call gets more
complex, and auto-yielding gets more expensive, when used inside

engines, that call engines, that call engines, etc…

(Nothing of relevance here)

(Nothing of relevance here)